======== はじめに ======== 最も低いレベルで、DoctrineはPHPクラスの一式でデータベーススキーマを表現します。これらのクラスはスキーマとモデルの振る舞いを定義します。 ウェブアプリケーションのユーザーを表す基本モデルは次のようになります。 class User extends Doctrine\_Record { public function setTableDefinition() { $this->hasColumn('username', 'string', 255); $this->hasColumn('password', 'string', 255); } :: public function setUp() { $this->actAs('Timestampable'); } } **NOTE** 実際には上記のクラスは使いません。これらは単なる例です。この章の後の方で既存のデータベーステーブルから最初のクラスの定義を生成します。 ``Doctrine\_Record``のそれぞれの子クラスは``setTableDefinition()``と``setUp()``メソッドを持ちます。``setTableDefinition()``メソッドはカラム、インデックスとテーブルのスキーマに関するその他の情報を定義するためにあります。``setUp()``メソッドはビヘイビアと``Doctrine_Record``子クラスの間のリレーションを定義するためにあります。上記の例ではautomagic機能を追加するTimestampableビヘイビアを有効にしています。[doc defining-models :name]の章でこれらのメソッドすべてが使われている例を学びます。 ================ モデルを生成する ================ Doctrineは使い始めを楽にするためにこれらのクラスを生成する方法を提供します。 **NOTE** 既存のデータベースの生成は始めるための利便性のみしか意味しません。データベースを生成した後で必要に応じて調整と整頓をしなければなりません。 ------------------ 既存のデータベース ------------------ よくある事例はORMにアクセスするデータベースとコードはより巨大/複雑になることです。SQLを手で書くよりも信頼のおけるツールが必要です。 Doctrineは既存のデータベースから``Doctrine\_Record``クラスを生成する機能をサポートします。ドメインモデル用にすべての``Doctrine_Record``クラスを手動で書く必要はありません。 ^^^^^^^^^^^^^^^^^^^^^^ 最初のインポートを行う ^^^^^^^^^^^^^^^^^^^^^^ ``doctrine_test``という名前のデータベースと``user``という名前の単独のテーブルがある場合を考えてみましょう。``user``テーブルは次のSQL文で作成されます: CREATE TABLE user ( id bigint(20) NOT NULL auto\_increment, first\_name varchar(255) default NULL, last\_name varchar(255) default NULL, username varchar(255) default NULL, password varchar(255) default NULL, type varchar(255) default NULL, is\_active tinyint(1) default '1', is\_super\_admin tinyint(1) default '0', created\_at TIMESTAMP, updated\_at TIMESTAMP, PRIMARY KEY (id) ) ENGINE=InnoDB これを``Doctrine_Record``クラスに変換することを考えます。Doctrineによってこれは簡単です![doc getting-started :name]章で作成したテストスクリプトを覚えていますか?これを利用してモデルを生成します。 最初にSQLiteのメモリの代わりにMySQLデータベースを使うために``bootstrap.php``を修正する必要があります: // bootstrap.php // ... $conn = Doctrine\_Manager::connection('mysql://root:mys3cr3et@localhost/doctrine\_test', 'doctrine'); // ... **NOTE** データベースがまだ存在せず接続ユーザーがデータベースを作成するパーミッションを持つ場合データベースを作成するために``$conn->createDatabase()``メソッドを使うことができます。テーブルを作成するために``CREATE TABLE``文を使用します。 生成クラスを置く場所が必要なので``doctrine_test``ディレクトリの中で``models``という名前のディレクトリを作りましょう: $ mkdir doctrine\_test/models モデルクラスを生成するには``test.php``スクリプトにコードを追加することだけが必要です: // test.php // ... Doctrine\_Core::generateModelsFromDb('models', array('doctrine'), array('generateTableClasses' => true)); **NOTE** ``generateModelsFromDb``メソッドは1つのパラメータのみを必要としこのパラメータはディレクトリです(生成レコードが書き込まれるディレクトリ)。2番目の引数はモデルを生成するためのデータベースの接続名の配列で、3番目はモデルのビルド用のオプションの配列です。 これだけです!``doctrine_test/models/generated``ディレクトリで``BaseUser.php``という名前のファイルがあります。ファイルは次のようになります: // models/generated/BaseUser.php /\*\* \* This class has been auto-generated by the Doctrine ORM Framework \*/ abstract class BaseUser extends Doctrine\_Record { public function setTableDefinition() { $this->setTableName('user'); $this->hasColumn('id', 'integer', 8, array('type' => 'integer', 'length' => 8, 'primary' => true, 'autoincrement' => true)); $this->hasColumn('first\_name', 'string', 255, array('type' => 'string', 'length' => 255)); $this->hasColumn('last\_name', 'string', 255, array('type' => 'string', 'length' => 255)); $this->hasColumn('username', 'string', 255, array('type' => 'string', 'length' => 255)); $this->hasColumn('password', 'string', 255, array('type' => 'string', 'length' => 255)); $this->hasColumn('type', 'string', 255, array('type' => 'string', 'length' => 255)); $this->hasColumn('is\_active', 'integer', 1, array('type' => 'integer', 'length' => 1, 'default' => '1')); $this->hasColumn('is\_super\_admin', 'integer', 1, array('type' => 'integer', 'length' => 1, 'default' => '0')); $this->hasColumn('created\_at', 'timestamp', null, array('type' => 'timestamp', 'notnull' => true)); $this->hasColumn('updated\_at', 'timestamp', null, array('type' => 'timestamp', 'notnull' => true)); } } ``doctrine_test/models``ディレクトリで``User.php``という名前のファイルもあります。ファイルは次のようになります: // models/User.php /\*\* \* This class has been auto-generated by the Doctrine ORM Framework \*/ class User extends BaseUser { } Doctrineは``doctrine\_test/models/UserTable.php``で``Doctrine_Table``スケルトンクラスを自動生成します。``true``の値を持つ``generateTableClasses``オプションを渡したからです。ファイルは次のようになります: // models/UserTable.php /\*\* \* This class has been auto-generated by the Doctrine ORM Framework \*/ class UserTable extends Doctrine\_Table { } モデルの機能をカスタマイズするために``User``と``UserTable``クラスの中でカスタムメソッドを設置できます。下記のコードは例です: // models/User.php // ... class User extends BaseUser { public function setPassword($password) { return :code:`this->_set('password', md5(`\ password)); } } 適切に動作させるために``password``アクセサをオーバーライドするには``bootstrap.php``ファイルで``auto\_accessor_override``属性を有効にしなければなりません。 // bootstrap.php // ... $manager->setAttribute(Doctrine\_Core::ATTR\_AUTO\_ACCESSOR\_OVERRIDE, true); ユーザーパスワードを設定しようとするとmd5に暗号化されます。最初に``models``ディレクトリからモデルをオートロードするために次のように``bootstrap.php``ファイルを修正する必要があります: // bootstrap.php // ... Doctrine\_Core::loadModels('models'); **NOTE** モデルのロードはこの章の[doc introduction-to-models:autoloading-models :name]セクションで説明されます。 ``User``モデルに行った変更をテストするコードを含めるために``test.php``を修正します: // test.php // ... $user = new User(); $user->username = 'jwage'; $user->password = 'changeme'; echo $user->password; // changemeではなくmd5ハッシュを出力する ターミナルから``test.php``を実行するとき次の内容が表示されます: $ php test.php 4cb9c8a8048fd02294477fcb1a41191a ``UserTable``クラスに追加するカスタムメソッドの例は次の通りです: // models/UserTable.php // ... class UserTable extends Doctrine\_Table { public function getCreatedToday() { $today = date('Y-m-d h:i:s', strtotime(date('Y-m-d'))); return $this->createQuery('u') ->where('u.created\_at > ?', $today) ->execute(); } } カスタムの``Doctrine\_Table``クラスをロードするには``bootstrap.php``ファイルで``autoload\_table_classes``属性を有効にしなければなりません。 // boostrap.php // ... $manager->setAttribute(Doctrine\_Core::ATTR\_AUTOLOAD\_TABLE\_CLASSES, true); ``UserTable``インスタンスを扱っているときにこのメソッドにアクセスできます: // test.php // ... $usersCreatedToday = Doctrine\_Core::getTable('User')->getCreatedToday(); ---------------- スキーマファイル ---------------- 代わりにYAMLスキーマファイルでモデルを管理してそれらのファイルからPHPクラスを生成できます。最初に作業をやりやすくするために手元にある既存のモデルからYAMLスキーマファイルを生成しましょう。次のコードを内部に取り込むために``test.php``を変更します: // test.php // ... Doctrine\_Core::generateYamlFromModels('schema.yml', 'models'); ``test.php``スクリプトを実行します: $ php test.php ``doctrine_test``ディレクトリのrootに作成された``schema.yml``という名前のファイルを見ます。内容は次の通りです: User: tableName: user columns: id: type: integer(8) primary: true autoincrement: true is\_active: type: integer(1) default: '1' is\_super\_admin: type: integer(1) default: '0' created\_at: type: timestamp(25) notnull: true updated\_at: type: timestamp(25) notnull: true first\_name: string(255) last\_name: string(255) username: string(255) password: string(255) type: string(255) 有効なYAMLスキーマファイルが手元にあるので、ここからスキーマを維持管理してPHPクラスを生成できます。``generate.php``という名前の新しいPHPスクリプトを作りましょう。このスクリプトはすべてを再生成しスクリプトが呼び出されるたびにデータベースを再インスタンス化します: // generate.php require\_once('bootstrap.php'); Doctrine\_Core::dropDatabases(); Doctrine\_Core::createDatabases(); Doctrine\_Core::generateModelsFromYaml('schema.yml', 'models'); Doctrine\_Core::createTablesFromModels('models'); ``schema.yml``を変更してターミナルから次のコマンドを実行してモデルを再生成できます: $ php generate.php YAMLスキーマファイルをセットアップしてスキーマファイルを再生成したのでファイルの内容を少し整頓してDoctrineの力を利用しましょう: User: actAs: [Timestampable] columns: is\_active: type: integer(1) default: '1' is\_super\_admin: type: integer(1) default: '0' first\_name: string(255) last\_name: string(255) username: string(255) password: string(255) type: string(255) **NOTE** **変更の注意点:** 1.) デフォルトなので明示的な``tableName``の定義を削除した。 2.) ``Timestampable``ビヘイビアを添付した。 3.) 主キーが定義されていない場合自動的に追加されるので``id``カラムを削除した。 4.) ``Timestampable``ビヘイビアで自動的に処理できるので``updated\_at``と``created_at``カラムを削除した。 デフォルトを利用することでYAMLはきれいになりコアのビヘイビアを活用するほど自分自身で行わなければならない作業は少なくなります。 YAMLスキーマファイルからモデルを再生成します: $ php generate.php [doc yaml-schema-files 専用の章]でYAMLスキーマファイルに関する詳しい内容を学びます。 ============ モデルを書く ============ オプションとしてすべてのコンビニエンスメソッドをスキップして独自のPHPコードだけでモデルを書くことができます。[doc defining-models :name]の章でモデルの構文のすべてを学びます。 ======================== モデルをオートロードする ======================== Doctrineはモデルをロードするための方法を2つ:コンサーバティブ(遅延)ロード、アグレッシブロードを提供します。コンサーバティブロードは初期にはPHPファイルを必要としません。代わりにクラスの名前へのパスをキャッシュしこのパスはspl\_autoload\_register()で初期に登録した``Doctrine_Core::autoload()``で使われます。両方のモデルのロード方法を利用した例は次の通りです。 ---------------- コンサーバティブ ---------------- コンサーバティブ(conservative - 慎重な・控えめな)なモデルロードは本番環境では理想的なモデルのロードメソッドになりつつあります。このメソッドはモデルのロードが実行されるときすべてのモデルをロードする代わりに遅延ロードします。 コンサーバティブなモデルロードはそれぞれが1つのクラスを持ち、ファイルの名前はクラスから名付けなければなりません。例えば、``User``というクラスがある場合、``User.php``という名前のファイルに含まれなければなりません。 コンサーバティブなモデルロードを使うにはモデルロードの属性をコンサーバティブにする必要があります: $manager->setAttribute(Doctrine\_Core::ATTR\_MODEL\_LOADING, Doctrine\_Core::MODEL\_LOADING\_CONSERVATIVE); **NOTE** 以前のステップで``bootstrap.php``ファイルでこの変更をすでに行っているので再度同じ変更する必要はありません。 ``Doctrine_Core::loadModels()``の機能を使うとき見つかるすべてのクラスは内部でキャッシュされるのでオートローダーは後でそれらを読み込むことができます。 Doctrine\_Core::loadModels('models'); 新しいクラス、例えば``User``クラスをインスタンス化するとき、オートローダーが起動しクラスが読み込まれます。 // Doctrine\_Core::autoload()の呼び出しが行われクラスが読み込まれる $user = new User(); 上記でクラスをインスタンス化することで``Doctrine\_Core::autoload()``の呼び出しが行われ``Doctrine_Core::loadModels()``のコールで見つかったクラスが読み込まれ利用可能になります。 **NOTE** 必要がないときにモデルをクラスをすべて読み込むと不要なオーバーヘッドが生じるので、必要なときだけ読み込みたい場合、とりわけ本番環境でコンサーバティブなモデルロードは推奨されます。 ------------ アグレッシブ ------------ アグレッシブ(aggressive - 積極的な)なモデルロードはデフォルトのモデルロードメソッドでとても便利です。``.php``拡張子を持つファイルをすべて探し読み込みます。Doctrineは継承を満たすことができないで、モデルが別のクラスを継承する場合、正しい順序でそれらのクラスを読み込むことはできません。なのですべての依存関係がそれぞれのクラスで満たされるようにするのはあなたの仕事です。 アグレッシブなモデルロードではファイルごとに複数のクラスを用意しファイルの名前はファイル内部のクラスの名前と関連する必要はありません。 アグレッシブなモデルロードの欠点はすべてのPHPファイルがすべてのリクエストに含まれるので、たくさんのモデルがある場合コンサーバティブなモデルロードを使うことをお勧めします。 アグレッシブなモデルロードを使うにはモデルロード属性をアグレッシブに設定する必要があります: $manager->setAttribute(Doctrine\_Core::ATTR\_MODEL\_LOADING, Doctrine\_Core::MODEL\_LOADING\_AGGRESSIVE); **TIP** アグレッシブなモデルロードはデフォルトのロード属性なので使う場合は明示的に設定する必要はありません。 ``Doctrine_Core::loadModels()``の機能を使うとき見つかるすべてのクラスは直ちに読み込まれます: Doctrine\_Core::loadModels('/path/to/models'); ====== まとめ ====== この章はこれまでで最もハードだと思いますが良い内容です。モデルの使い方、既存のデータベースからモデルを生成する方法、独自のモデルを書く方法、とモデルをYAMLスキーマファイルとして管理する方法を少し学びました。モデルディレクトリからモデルをロードする機能を実装するためにDoctrineのテスト環境も修正しました。 Doctrineのモデルのトピックは非常に大きいので開発者がすべての情報を吸収しやすいように章を3つのピースに分割します。[doc defining-models 次の章]においてモデルを定義するために使うAPIに入ります。