Doctrine2.0がアルファリリースされたので使ってみた

こんにちは、小川です。
本日Doctrine2.0のAlphaバージョンがリリースされました。簡単なチュートリアルも公開されているので、本日は実際に動かしてみたいと思います。

今回のリリースについては以下の記事をご覧ください。
Doctrine - Doctrine 2 Preview Release

以前僕が書いた、「Doctrine2.0について」という記事をご覧になった方でDoctrine1.xをご存じの方はわかるかと思うのですが、Doctrine2.0は全くの別物です。
上記の記事にも書いてあるとおり、実に90%以上ものコードが書き直されています。とはいえ現在はAlphaバージョン。実装されているのはまだまだ最低限の機能でしかありません。CLIやビヘイビアといった機能が実装されるのはまだ先のことでしょう。

さて、本題に入っていきましょう。実際に動かすにあたって、PHP5.3が必要になります。データベースはSQLite3を使用します。
チュートリアルはDocument内の以下のページに書いてあります。
Doctrine ORM for PHP - Introduction - Sandbox Quickstart

まずはインストールです。とりあえずSubversionを使います。


$ svn co http://svn.doctrine-project.org/trunk doctrine2
$ cd doctrine2/tools/sandbox

doctrine2という名前でチェックアウトしてきました。このなかのtools/sandboxに設定済みのファイルやディレクトリが配置されています。
内部のファイル構成は以下のようになっています。


sandbox/
  Entities/
    - モデルクラスを格納するためのディレクトリ
  xml/
    - XML形式のマッピングファイルが作成されるディレクトリ 
  yaml/
    - YAML形式のマッピングファイルが作成されるディレクトリ 
  cli-config.php
    - CLI用の設定ファイル
  config.php
    - アプリケーション用の設定ファイル
  doctrine
    - コマンドファイル
  index.php
    - アプリケーションファイル

xmlとyamlディレクトリは設定次第で使ったり使わなかったりという感じです。僕はphp5.3をphp-5.3.0という名前の実行ファイルとして作成しているので、doctrineコマンドファイルを以下のように変更しました。


#!/usr/bin/env php-5.3.0
<?php
include('doctrine.php');

試しにコマンドを実行すると以下のように、利用可能なタスクの一覧が表示されます。


$ ./doctrine
Doctrine Command Line Interface
Available Tasks:
run-sql --file=<path> | --sql=<SQL>
schema-tool --create | --drop | --update [--dump-sql] [--classdir=<path>]
version

現在はSQLを実行するrun-sql、データベースの生成などを行うschema-tool、バージョンを確認するversionの3つのタスクが有効になっています。このタスクは後ほど、必要に応じて使用していきます。

ではモデルクラスを作成します。Entitiesディレクトリに以下のクラスファイルを作成して、User.phpという名前で保存します。


<?php
namespace Entities;
/**
 * @Entity
 */
class User
{
  /**
   * @Id
   * @Column(type="integer")
   * @GeneratedValue(strategy="AUTO")
   */
  private $id;
  /**
   * @Column(type="string")
   */
  private $name;
  public function getId()
  {
    return $this->id;
  }
  public function getName()
  {
    return $this->name;
  }
  public function setName($name)
  {
    $this->name = $name;
  }
}

これでモデルクラスの定義完了です。ではこのモデルを元にデータベースを作成します。以下のコマンドを実行しましょう。


$ ./doctrine schema-tool --create
Doctrine Command Line Interface
Creating database schema...
Database schema created successfully.

これでデータベースを見てみました。SQLiteを使用している場合はdatabase.sqliteというファイルが作成されているでしょう。データベースとかの設定はconfig.phpに記述してあります。
データベースを直接みてみます。


$ sqlite3 database.sqlite
sqlite> .tables
User
sqlite> .schema User
CREATE TABLE User (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL);
sqlite> .quit

先ほどクラス定義時に特に指定は行っていないので、クラス名と同じUserという名前のテーブルが作成されています。
個人的には小文字の方が好きなので小文字に変えてみます。Entities/User.phpを開き、クラスのアノテーションを少し変更します。


<?php
...
/**
 * @Entity
 * @Table(name="user")
 */
class User ...

@Tableというアノテーションを追加しました。これでもう一度Schemaの作成を行います。


$ ./doctrine schema-tool --drop
$ ./doctrine schema-tool --create

これで先ほどと同じ手順で確認するときちんと小文字になっています。アノテーションの種類はたくさんあり、Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.phpの中にアノテーションが1つひとつクラスとして定義されています。@Columnや@Tableに引数のようなものを渡していますが、これは各アノテーションクラスごとにプロパティとして定義されています。ここらへんがどう展開されているかなどは同じディレクトリ内のAnnotationDriver.phpに記述されています。
DoctrineAnnotations.phpをみるとスキーマやリレーションの情報以外に、PreUpdateやPostRemoveなどのフックメソッドや、Doctrineエクステンション用のDoctrineXなどが用意されているようです。Doctrineエクステンションはいわゆるビヘイビアですが、Doctrine2.0でどう扱われるかはまだわかりません。実際に記述してもパースする処理は見当たりませんでした。

少し話がそれましたが、モデルの定義が完了してデータベースも完成したのでいよいよそれを使ってデータベースを操作していきましょう。
ではindex.phpを以下のように変更して、User情報を作成してみましょう。


<?php
require 'config.php';
$user = new \Entities\User;
$user->setName('fivestar');
$em->persist($user);
$em->flush();
echo "User saved!\n";

先ほどUser.phpを作成したときにnamespace Entity;を指定したので名前空間を指定して作成します。オブジェクトを作る流れは特に変わりませんが、保存の流れが変わっています。
$emにはDoctrine\ORM\EntityManagerクラスのインスタンスが格納されており、config.phpで作成されています。このEntityManagerクラスはDoctrine2.0において非常に重要な役割をもっているクラスです。
他の方法があるかはまだ詳しく調べていないのですが、基本的な保存の方法はこのEntityManagerを経由して行います。

EntityManagerに対してpersistメソッドを実行してUserクラスを保存対象にし、flushメソッドを実行することでデータベースへコミットを行っています。EntityManagerの内部で_flushModeというプロパティを持っており、これがFLUSHMODE_IMMIDIATEになっていればpersistした瞬間にflushが行われるようになりますが、デフォルトではFLUSHMODE_COMMITになっているので明示的にflushを行う必要があります。
トランザクションを発行する場合はEntityManagerに従来と同じくbeginTransaction, commit, rollbackといったメソッドが用意されています。commitメソッドは内部でflushも呼んでいるので、そこの流れは基本的に変わらないと思います。


$ php-5.3.0 index.php
User saved!

「User Saved!」が表示されたら保存完了です。保存されたか見てみましょう。直接データベースをみるのではなく、コマンドラインからSQLを実行することが可能です。


$ ./doctrine run-sql --sql="select * from user"
Doctrine Command Line Interface
array(1) {
  [0]=>
  array(2) {
    ["id"]=>
    string(1) "1"
    ["name"]=>
    string(8) "fivestar"
  }
}

現在は配列をそのまま出しているだけですが、とりあえず正しくインサートできている(らしい)ことが確認できました。
ではこのデータをアプリケーションで取得する方法を書いていきます。index.phpを以下のように上書きします。


<?php
require 'config.php';
$q = $em->createQuery('select u from Entities\User u where u.name = ?1');
$q->setParameter('1', 'fivestar');
$user = $q->getSingleResult();
echo "Hello: " . $user->getName() . "!\n";


$ php-5.3.0 index.php
Hello: fivestar!

EntityManagerにcreateQueryというメソッドを発行してDQLを渡しています。このcreateQueryはDoctrine\ORM\Queryクラスを作成するメソッドです。プレースホルダが?1というふうになっていますが、この?のあとの数字をキーにパラメータを指定するようになっています。現在の実装ではsetParameterかsetParametersメソッドで値を渡してあげる必要があるようです。getSingleResultメソッドはfetchOneメソッドと同じようにクエリを実行し単一のエンティティを返すメソッドです。getResultやgetArrayResult、getSingleScalarResultなどのメソッドがあり、いくつかの方法でクエリの実行内容を取得することが可能なようです。

実装をみていたら今までのDoctrine_QueryのメソッドチェーンでDQLを組み立てる実装はこのQueryクラスには実装されていませんでした。Doctrine_Queryクラスに対応するのはDoctrine\ORM\QueryBuilderクラスになります。先ほどの内容をQueryBuilderを使って行います。


<?php
require 'config.php';
$qb = $em->createQueryBuilder()->select('u')->from('Entities\User u')->where('u.name = ?1');
$qb->setParameter('1', 'fivestar');
$user = $qb->getQuery()->getSingleResult();
echo "Hello: " . $user->getName() . "!\n";


$ php-5.3.0 index.php
Hello: fivestar!

QueryBuilderは正確にはDQLを作成するためのクラスで、getQueryメソッドを実行して内部でビルドしたDQLを元にQueryクラスを作成する、というところまで行ってくれます。Doctrine_Queryと違いwhereなどの単位でパラメータを設定できなかったり、selectが必須だったりしています。他にもQuery\Exprというクラスがあり、それを用いてクエリの構築を行うような仕組みになっていたりしています。このあたりは今後実装されていくのではと思います。

チュートリアルはここまでになります。最近はあまりソースを追っていなかったので深いところまでは書けないのですが、現状ではまだまだ使えるレベルにはなっていないというのが率直な感想です。リリースはまだまだ先の話で、ロードマップでは2010年の3月にリリース予定のようです。まだ半年先のことですのでこれからどんどん機能が実装されていくでしょう。

現在DoctrineのブログでもDoctrine2.0の話が増えてきており、今後も様々な情報が配信されると思います。今はまだまだ評価できないですが、リリースが待ち遠しいです。