You are currently reading the 1.2 documentation. Switch to 2.2  2.1  2.0 

Introduction to Models

はじめに

最も低いレベルで、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');
    }
}

実際には上記のクラスは使いません。これらは単なる例です。この章の後の方で既存のデータベーステーブルから最初のクラスの定義を生成します。

Doctrine_Recordのそれぞれの子クラスはsetTableDefinition()setUp()メソッドを持ちます。setTableDefinition()メソッドはカラム、インデックスとテーブルのスキーマに関するその他の情報を定義するためにあります。setUp()メソッドはビヘイビアとDoctrine_Record子クラスの間のリレーションを定義するためにあります。上記の例ではautomagic機能を追加するTimestampableビヘイビアを有効にしています。Defining Modelsの章でこれらのメソッドすべてが使われている例を学びます。

モデルを生成する

Doctrineは使い始めを楽にするためにこれらのクラスを生成する方法を提供します。

既存のデータベースの生成は始めるための利便性のみしか意味しません。データベースを生成した後で必要に応じて調整と整頓をしなければなりません。

既存のデータベース

よくある事例は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によってこれは簡単です!Getting Started章で作成したテストスクリプトを覚えていますか?これを利用してモデルを生成します。

最初にSQLiteのメモリの代わりにMySQLデータベースを使うためにbootstrap.phpを修正する必要があります:

// bootstrap.php

// ...
$conn = Doctrine_Manager::connection('mysql://root:mys3cr3et@localhost/doctrine_test', 'doctrine');
// ...

データベースがまだ存在せず接続ユーザーがデータベースを作成するパーミッションを持つ場合データベースを作成するために$conn->createDatabase()メソッドを使うことができます。テーブルを作成するためにCREATE TABLE文を使用します。

生成クラスを置く場所が必要なのでdoctrine_testディレクトリの中でmodelsという名前のディレクトリを作りましょう:

$ mkdir doctrine_test/models

モデルクラスを生成するにはtest.phpスクリプトにコードを追加することだけが必要です:

// test.php

// ...
Doctrine_Core::generateModelsFromDb('models', array('doctrine'), array('generateTableClasses' => true));

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.phpDoctrine_Tableスケルトンクラスを自動生成します。trueの値を持つgenerateTableClassesオプションを渡したからです。ファイルは次のようになります:

// models/UserTable.php

/**
 * This class has been auto-generated by the Doctrine ORM Framework
 */
class UserTable extends Doctrine_Table
{

}

モデルの機能をカスタマイズするためにUserUserTableクラスの中でカスタムメソッドを設置できます。下記のコードは例です:

// models/User.php

// ...
class User extends BaseUser
{
    public function setPassword($password)
    {
        return $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');

モデルのロードはこの章の[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)

変更の注意点:
1.) デフォルトなので明示的なtableNameの定義を削除した。
2.) Timestampableビヘイビアを添付した。
3.) 主キーが定義されていない場合自動的に追加されるのでidカラムを削除した。
4.) Timestampableビヘイビアで自動的に処理できるのでupdated_atcreated_atカラムを削除した。
デフォルトを利用することでYAMLはきれいになりコアのビヘイビアを活用するほど自分自身で行わなければならない作業は少なくなります。

YAMLスキーマファイルからモデルを再生成します:

$ php generate.php

専用の章でYAMLスキーマファイルに関する詳しい内容を学びます。

モデルを書く

オプションとしてすべてのコンビニエンスメソッドをスキップして独自のPHPコードだけでモデルを書くことができます。Defining Modelsの章でモデルの構文のすべてを学びます。

モデルをオートロードする

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);

以前のステップでbootstrap.phpファイルでこの変更をすでに行っているので再度同じ変更する必要はありません。

Doctrine_Core::loadModels()の機能を使うとき見つかるすべてのクラスは内部でキャッシュされるのでオートローダーは後でそれらを読み込むことができます。

Doctrine_Core::loadModels('models');

新しいクラス、例えばUserクラスをインスタンス化するとき、オートローダーが起動しクラスが読み込まれます。

// Doctrine_Core::autoload()の呼び出しが行われクラスが読み込まれる
$user = new User();

上記でクラスをインスタンス化することでDoctrine_Core::autoload()の呼び出しが行われDoctrine_Core::loadModels()のコールで見つかったクラスが読み込まれ利用可能になります。

必要がないときにモデルをクラスをすべて読み込むと不要なオーバーヘッドが生じるので、必要なときだけ読み込みたい場合、とりわけ本番環境でコンサーバティブなモデルロードは推奨されます。

アグレッシブ

アグレッシブ(aggressive - 積極的な)なモデルロードはデフォルトのモデルロードメソッドでとても便利です。.php拡張子を持つファイルをすべて探し読み込みます。Doctrineは継承を満たすことができないで、モデルが別のクラスを継承する場合、正しい順序でそれらのクラスを読み込むことはできません。なのですべての依存関係がそれぞれのクラスで満たされるようにするのはあなたの仕事です。

アグレッシブなモデルロードではファイルごとに複数のクラスを用意しファイルの名前はファイル内部のクラスの名前と関連する必要はありません。

アグレッシブなモデルロードの欠点はすべてのPHPファイルがすべてのリクエストに含まれるので、たくさんのモデルがある場合コンサーバティブなモデルロードを使うことをお勧めします。

アグレッシブなモデルロードを使うにはモデルロード属性をアグレッシブに設定する必要があります:

$manager->setAttribute(Doctrine_Core::ATTR_MODEL_LOADING, Doctrine_Core::MODEL_LOADING_AGGRESSIVE);

アグレッシブなモデルロードはデフォルトのロード属性なので使う場合は明示的に設定する必要はありません。

Doctrine_Core::loadModels()の機能を使うとき見つかるすべてのクラスは直ちに読み込まれます:

Doctrine_Core::loadModels('/path/to/models');

まとめ

この章はこれまでで最もハードだと思いますが良い内容です。モデルの使い方、既存のデータベースからモデルを生成する方法、独自のモデルを書く方法、とモデルをYAMLスキーマファイルとして管理する方法を少し学びました。モデルディレクトリからモデルをロードする機能を実装するためにDoctrineのテスト環境も修正しました。

Doctrineのモデルのトピックは非常に大きいので開発者がすべての情報を吸収しやすいように章を3つのピースに分割します。次の章においてモデルを定義するために使うAPIに入ります。


Questions and Feedback

If you find a problem with the documentation or have a suggestion, please register and open a ticket.

If you need support or have a technical question, you can post to the user mailing-list.