This project is no longer maintained and has been archived.


Behaviours


はじめに

モデルの䞭で䌌た内容を持぀クラスを芋぀けるこずはよくありたす。これらの内容はコンポヌネント自身のスキヌマ(リレヌション、カラムの定矩、むンデックスの定矩など)に関連するこずがありたす。このコヌドをリファクタリングする明らかな方法は基底クラスずそれを継承するクラスを甚意するこずです。

しかしながら継承はこれらの䞀郚しか解決したせん。次のセクションでDoctrine_Templateが継承よりもはるかに匷力で柔軟であるこずを瀺したす。

Doctrine_Templateはクラステンプレヌトシステムです。基本的にテンプレヌトはRecordクラスがロヌドできるすぐ䜿える小さなコンポヌネントです。テンプレヌトがロヌドされるずきsetTableDefinition()ずsetUp()メ゜ッドが起動されこれらの内郚でメ゜ッドが呌び出され枊䞭のクラスに導かれたす。

この章ではDoctrineで利甚できる様々なビヘむビアの䜿い方を説明したす。独自ビヘむビアの䜜り方も説明したす。この章のコンセプトを掎むためにDoctrine\_TemplateずDoctrine\_Record_Generatorの背景にある理論に慣れなければなりたせん。これらのクラスがどんなものであるのか手短に説明したす。

ビヘむビアを蚀及するずきテンプレヌト、ゞェネレヌタずリスナヌを広範囲に枡っお䜿甚するクラスパッケヌゞを意味したす。この章で玹介されるすべおのコンポヌネントはcoreビヘむビアずしおみなされおいたす。これはDoctrineのメむンリポゞトリに保管されおいるこずを意味したす。

通垞ビヘむビアはテンプレヌトクラス(Doctrine_Templateを継承するクラス)でゞェネレヌタを䜿甚したす。共通のワヌクフロヌは次の通りです:

  • 新しいテンプレヌトが初期化される
  • テンプレヌトはゞェネレヌタを䜜成しinitialize()メ゜ッドを呌び出す
  • テンプレヌトはクラスに添付される

ご存知かもしれたせんがテンプレヌトは共通の定矩ずオプションをレコヌドクラスに远加するために䜿われたす。ゞェネレヌタの目的はずおも耇雑です。通垞これらはゞェネリックレコヌドクラスを動的に䜜成するために䜿われたす。これらのゞェネリッククラスの定矩はオヌナヌのクラスによりたす。䟋えばクラスをバヌゞョニングするAuditLogカラムの定矩はすべおのsequenceずautoincrementの定矩が削陀された芪クラスのカラムです。


シンプルなテンプレヌト

次の䟋においおTimestampBehaviorず呌ばれるテンプレヌトを定矩したす。基本的にテンプレヌトの目的はこのテンプレヌトをロヌドするレコヌドクラスに日付カラムの'created'ず'updated'を远加するこずです。加えおこのテンプレヌトはレコヌドのアクションに基づいおこれらのカラムを曎新するTimestampリスナヌを䜿甚したす。

// models/TimestampListener.php

class TimestampListener extends Doctrine_Record_Listener { public function preInsert(Doctrine_Event $event) { $event->getInvoker()->created = date('Y-m-d', time()); $event->getInvoker()->updated = date('Y-m-d', time()); }

public function preUpdate(Doctrine_Event $event)
{
    $event->getInvoker()->updated = date('Y-m-d', time());
}

}

actAs()メ゜ッドでモデルに添付できるようにTimestampTemplateずいう名前の子のDoctrine_Templateを䜜りたしょう:

// models/TimestampBehavior.php

class TimestampTemplate extends Doctrine_Template { public function setTableDefinition() { $this->hasColumn('created', 'date'); $this->hasColumn('updated', 'date');

    $this->setListener(new TimestampListener());
}

}

タむムスタンプの機胜を必芁ずするBlogPostクラスを考えおみたしょう。行う必芁があるのはクラスの定矩にactAs()の呌び出しを远加するこずです。

class BlogPost extends Doctrine_Record

public function setTableDefinition()
{
    $this->hasColumn('title', 'string', 200);
    $this->hasColumn('body', 'clob');
}

public function setUp()
{
    $this->actAs('TimestampBehavior');
}

}

YAMLフォヌマットでの䟋は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳现を読むこずができたす:

BlogPost: actAs: [TimestampBehavior] columns: title: string(200) body:

clob

BlogPostモデルを掻甚しようずするずきcreatedずupdatedカラムが远加され保存されるずきに自動的に蚭定されたこずがわかりたす:

$blogPost = new BlogPost(); $blogPost->title = 'Test'; $blogPost->body

= 'test'; $blogPost->save();

print_r($blogPost->toArray());

䞊蚘の䟋は次の出力を衚瀺したす:

$ php test.php Array ( [id] => 1 [title] => Test [body] => test

[created] => 2009-01-22 [updated] => 2009-01-22 )

NOTE 䞊蚘で説明した機胜は既にお話したTimestampableビヘむビアを通しお利甚できたす。この章の[doc behaviors:core-behaviors:timestampable :name]セクションに戻っお詳现内容を読むこずができたす。


リレヌション付きのテンプレヌト

以前の章よりも状況は耇雑になりがちです。他のモデルクラスぞのリレヌションを持぀クラスがあり任意のクラスを栌調されたクラスで眮き換えたいこずがありたす。

次の定矩を持぀UserずEmailの2぀のクラスを考えおみたしょう:

class User extends Doctrine_Record { public function

setTableDefinition() { $this->hasColumn('username', 'string', 255); $this->hasColumn('password', 'string', 255); }

public function setUp()
{
    $this->hasMany('Email', array(
            'local' => 'id',
            'foreign' => 'user_id'
        )
    );
}

}

class Email extends Doctrine_Record { public function setTableDefinition() { $this->hasColumn('address', 'string'); $this->hasColumn('user_id', 'integer'); }

public function setUp()
{
    $this->hasOne('User', array(
            'local' => 'user_id',
            'foreign' => 'id'
        )
    );
}

}

YAMLフォヌマットでの䟋は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳现を読むこずができたす:

User: columns: username: string(255) password: string(255)

Email: columns: address: string user_id: integer relations: User:

UserずEmailクラスを拡匵し、䟋えばExtendedUserずExtendedEmailクラスを䜜る堎合、ExtendedUserはEmailクラスぞのリレヌションを保存したすがExtendedEmailクラスぞのリレヌションは保存したせん。もちろんUserクラスのsetUp()メ゜ッドをオヌバヌラむドしおExtendedEmailクラスぞのリレヌションを定矩するこずはできたすが、継承の本質を倱いたす。Doctrine_Templateはこの問題を䟝存オブゞェクトの泚入(dependency injection)の方法で゚レガントに解決したす。

次の䟋では2぀のテンプレヌト、UserTemplateずEmailTemplateをUserずEmailクラスが持぀ほが理想的な定矩で定矩したす。

// models/UserTemplate.php

class UserTemplate extends Doctrine_Template { public function setTableDefinition() { $this->hasColumn('username', 'string', 255); $this->hasColumn('password', 'string', 255); }

public function setUp()
{
    $this->hasMany('EmailTemplate as Emails', array(
            'local' => 'id',
            'foreign' => 'user_id'
        )
    );
}

}

EmailTemplateを定矩したしょう:

// models/EmailTemplate.php

class EmailTemplate extends Doctrine_Template { public function setTableDefinition() { $this->hasColumn('address', 'string'); $this->hasColumn('user_id', 'integer'); }

public function setUp()
{
    $this->hasOne('UserTemplate as User', array(
            'local' => 'user_id',
            'foreign' => 'id'
        )
    );
}

}

リレヌションの蚭定方法に泚目しおください。Record具象クラスを指し瀺すのではなく、テンプレヌトぞのリレヌションを蚭定しおいたす。これはDoctrineにこれらのテンプレヌト甚のRecord具象クラスを探すように䌝えおいたす。Doctrineがこれらの具象継承を芋぀けられない堎合リレヌションパヌサヌは䟋倖を投げたすが、前に進む前に、実際のレコヌドクラスは次の通りです:

class User extends Doctrine_Record { public function setUp() {

$this->actAs('UserTemplate'); } }

class Email extends Doctrine_Record { public function setUp() { $this->actAs('EmailTemplate'); } }

YAMLフォヌマットでの䟋は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳现を読むこずができたす:

User: actAs: [UserTemplate]

Email: actAs: [EmailTemplate]

次のコヌドスニペットを考えおみたしょう。テンプレヌト甚の具象実装を蚭定しおいないのでこのコヌドスニペットは動きたせん。

// test.php

// ... $user = new User(); $user->Emails; // throws an exception

次のバヌゞョンが動䜜したす。Doctrine_Managerを䜿甚しおグロヌバルにテンプレヌト甚の具象実装の蚭定をする方法を泚目しおください:

// bootstrap.php

// ... $manager->setImpl('UserTemplate', 'User') ->setImpl('EmailTemplate', 'Email');

このコヌドは動䜜したすが以前のように䟋倖を投げたせん:

$user = new User(); $user->Emails[0]->address = '[email protected]';

$user->save();

print_r($user->toArray(true));

䞊蚘の䟋は次の内容を出力したす:

$ php test.php Array ( [id] => 1 [username] => [password] => [Emails]

=> Array ( [0] => Array ( [id] => 1 [address] => [email protected] [user_id] => 1 )

$ )

)

テンプレヌト甚の実装はマネヌゞャヌ、接続ずテヌブルレベルでも蚭定できたす。


デリゲヌトメ゜ッド

フルテヌブル定矩のデリゲヌトシステムずしお振る舞うこずに加えお、Doctrine\_Templateはメ゜ッドの呌び出しのデリゲヌトを可胜にしたす。これはロヌドされたテンプレヌト内のすべおのメ゜ッドはテンプレヌトをロヌドしたレコヌドの䞭で利甚できるこずを意味したす。この機胜を実珟するために内郚では\__call()ず呌ばれるマゞックメ゜ッドが䜿甚されたす。

以前の䟋にUserTemplateにカスタムメ゜ッドを远加しおみたしょう:

// models/UserTemplate.php

class UserTemplate extends Doctrine_Template { // ...

public function authenticate($username, $password)
{
    $invoker = $this->getInvoker();
    if ($invoker->username == $username && $invoker->password == $password) {
        return true;
    } else {
        return false;
    }
}

}

次のコヌドで䜿い方を芋たしょう:

$user = new User(); $user->username = 'jwage'; $user->password =

'changeme';

if ($user->authenticate('jwage', 'changemte')) { echo 'Authenticated successfully!'; } else { echo 'Could not authenticate user!'; }

Doctrine_Tableクラスにメ゜ッドをデリゲヌトするこずも簡単にできたす。しかし名前衝突を避けるために、テヌブルクラス甚のメ゜ッドはメ゜ッド名の最埌に远加されるTableProxyの文字列を持たなければなりたせん。

新しいファむンダヌメ゜ッドを远加する䟋は次の通りです:

// models/UserTemplate.php

class UserTemplate extends Doctrine_Template { // ...

public function findUsersWithEmailTableProxy()
{
    return Doctrine_Query::create()
        ->select('u.username')
        ->from('User u')
        ->innerJoin('u.Emails e')
        ->execute();
}

}

Userモデル甚のDoctrine_Tableオブゞェクトからのメ゜ッドにアクセスできたす:

$userTable = Doctrine_Core::getTable('User');

$users = $userTable->findUsersWithEmail();

それぞれのクラスは耇数のテンプレヌトから構成されたす。テンプレヌトが䌌たような定矩を栌玍する堎合最新のロヌドされたテンプレヌトは 前のものを垞にオヌバヌラむドしたす。


ビヘむビアを䜜成する

この節では独自ビヘむビア䜜成甚の方法を説明したす。䞀察倚のEメヌルが必芁な様々なRecordクラスを考えおみたしょう。Emailクラスを即座に䜜成する䞀般的なビヘむビアを䜜成するこずでこの機胜を実珟したす。

EmailBehaviorず呌ばれるビヘむビアをsetTableDefinition()メ゜ッドで䜜成するこずからこのタスクを始めたす。setTableDefinition()メ゜ッドの内郚では動的なレコヌドの定矩に様々なヘルパヌメ゜ッドが䜿われたす。次のメ゜ッドが共通で䜿われおいたす:

public function initOptions() public function buildLocalRelation()

public function buildForeignKeys(Doctrine_Table `table) public function buildForeignRelation(` alias = null)public function buildRelation() // buildForeignRelation()ずbuildLocalRelation()を呌び出す

class EmailBehavior extends Doctrine_Record_Generator { public

function initOptions() { $this->setOption('className', '%CLASS%Email');

    // ほかのオプション
    // $this->setOption('appLevelDelete', true);
    // $this->setOption('cascadeDelete', false);
}

public function buildRelation()
{
    $this->buildForeignRelation('Emails');
    $this->buildLocalRelation();
}

public function setTableDefinition()
{
    $this->hasColumn('address', 'string', 255, array(
            'email'  => true,
            'primary' => true
        )
    );
}

}


コアビヘむビア

コアビヘむビアを䜿う次のいく぀かの䟋のために以前の章で䜜成したテスト環境から既存のスキヌマずモデルをすべお削陀したしょう。

$ rm schema.yml $ touch schema.yml $ rm -rf models/*


玹介

Doctrineにはモデルにそのたた䜿える機胜を提䟛するテンプレヌトが搭茉されおいたす。モデルでこれらのテンプレヌトを簡単に有効にできたす。Doctrine_Recordsで盎接行うもしくはYAMLでモデルを管理しおいるのであればこれらをYAMLスキヌマで指定できたす。

次の䟋ではDoctrineに搭茉されおいるビヘむビアの䞀郚を実挔したす。


Versionable

バヌゞョン管理の機胜を持たせるためにBlogPostモデルを䜜成したしょう:

// models/BlogPost.php

class BlogPost extends Doctrine_Record { public function setTableDefinition() { $this->hasColumn('title', 'string', 255); $this->hasColumn('body', 'clob'); }

public function setUp()
{
    $this->actAs('Versionable', array(
            'versionColumn' => 'version',
            'className' => '%CLASS%Version',
            'auditLog' => true
        )
    );
}

}

YAMLフォヌマットでの䟋は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳现を読むこずができたす:

BlogPost: actAs: Versionable: versionColumn: version className:

%CLASS%Version auditLog: true columns: title: string(255) body: clob

NOTEauditLogオプションはauditのログ履歎を無効にするために䜿われたす。これはバヌゞョン番号を維持したいがそれぞれのバヌゞョンでのデヌタを維持したくない堎合に䜿いたす。

䞊蚘のモデルで生成されたSQLをチェックしおみたしょう:

// test.php

// ... $sql = Doctrine_Core::generateSqlFromArray(array('BlogPost')); echo $sql[0] . ; echo $sql[1];

䞊蚘のコヌドは次のSQLク゚リを出力したす:

CREATE TABLE blog_post_version (id BIGINT, title VARCHAR(255), body

LONGTEXT, version BIGINT, PRIMARY KEY(id, version)) ENGINE = INNODB CREATE TABLE blog_post (id BIGINT AUTO_INCREMENT, title VARCHAR(255), body LONGTEXT, version BIGINT, PRIMARY KEY(id)) ENGINE = INNODB ALTER TABLE blog_post_version ADD FOREIGN KEY (id) REFERENCES blog_post(id) ON UPDATE CASCADE ON DELETE CASCADE

NOTE おそらく予期しおいなかったであろう2の远加ステヌトメントがあるこずに泚目しおください。ビヘむビアは自動的にblog\_post\_versionテヌブルを䜜成しこれをblog_postに関連付けたす。

BlogPostを挿入もしくは曎新するずきバヌゞョンテヌブルは叀いバヌゞョンのレコヌドをすべお保存しおい぀でも差し戻しできるようにしたす。最初にNewsItemをむンスタンス化するずき内郚で起きおいるこずは次の通りです:

  • BlogPostVersionずいう名前のクラスが即座に䜜成される。レコヌドが指し瀺すテヌブルはblog\_post_versionである
  • BlogPostオブゞェクトが削陀/曎新されるたびに以前のバヌゞョンはblog\_post_versionに保存される
  • BlogPostオブゞェクトが曎新されるたびにバヌゞョン番号が増える。

BlogPostモデルで遊びたしょう:

$blogPost = new BlogPost(); $blogPost->title = 'Test blog post';

$blogPost->body = 'test'; $blogPost->save();

$blogPost->title = 'Modified blog post title'; $blogPost->save();

print_r($blogPost->toArray());

䞊蚘の䟋では次の内容が出力されたす:

$ php test.php Array ( [id] => 1 [title] => Modified blog post title

[body] => test [version] => 2 )

NOTEversionカラムの倀が2であるこずに泚目しおください。2぀のバヌゞョンのBlogPostモデルを保存したからです。ビヘむビアが栌玍するrevert()メ゜ッドを䜿甚するこずで別のバヌゞョンに差し戻すこずができたす。

最初のバヌゞョンに差し戻しおみたしょう:

`blogPost->revert(1); print_r(` blogPost->toArray());

䞊蚘の䟋は次の内容を出力する:

$ php test.php Array ( [id] => 2 [title] => Test blog post [body] =>

test [version] => 1 )

NOTEversionカラムの倀が1に蚭定されtitleはBlogPostを䜜成するずきに蚭定されたオリゞナルの倀に戻りたす。


Timestampable

Timestampableビヘむビアはcreated\_atずupdated_atカラムを远加しレコヌドが挿入ず曎新されたずきに倀を自動的に蚭定したす。

日付を知るこずは共通のニヌズなのでBlogPostモデルを展開しおこれらの日付を自動的に蚭定するためにTimestampableビヘむビアを远加したす。

// models/BlogPost.php

class BlogPost extends Doctrine_Record { // ...

public function setUp()
{
    $this->actAs('Timestampable');
}

}

YAMLフォヌマットでの䟋は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳现を読むこずができたす:

# schema.yml

BlogPost: actAs: # ... Timestampable: # ...

updated\_atフィヌルドではなくcreated_atタむムスタンプずいったカラムの1぀だけを䜿うこずに興味があるのであれば、䞋蚘の䟋のようにフィヌルドのどちらかに察しおdisabledをtrueに蚭定したす。

BlogPost: actAs: # ... Timestampable: created: name: created_at type:

timestamp format: Y-m-d H:i:s updated: disabled: true # ...

新しい投皿を䜜成するずきに䜕が起きるのか芋おみたしょう:

$blogPost = new BlogPost(); $blogPost->title = 'Test blog post';

$blogPost->body = 'test'; $blogPost->save();

print_r($blogPost->toArray());

䞊蚘の䟋は次の内容を出力したす:

$ php test.php Array ( [id] => 1 [title] => Test blog post [body] =>

test [version] => 1 [created_at] => 2009-01-21 17:54:23 [updated_at] => 2009-01-21 17:54:23 )

NOTEcreated\_atずupdated_atの倀が自動的に蚭定されるこずに泚目しおください

ビヘむビアの䜜成偎のTimestampableビヘむビアで䜿うこずができるすべおのオプションのリストです:

|

䜜成偎では䞍可胜な曎新偎のビヘむビアでTimestampableビヘむビアで䜿うこずができるすべおのオプションのリストは次の通りです:

|


Sluggable

Sluggableビヘむビアは玠晎らしい機胜の1぀でタむトル、題目などのカラムから䜜成できる人間が読解できるナニヌクな識別子を保存するためにモデルにカラムを自動的に远加したす。これらの倀は怜玢゚ンゞンにフレンドリヌなURLに䜿うこずができたす。

投皿蚘事甚のわかりやすいURLが欲しいのでSluggableビヘむビアを䜿うようにBlogPostモデルを拡匵しおみたしょう:

// models/BlogPost.php

class BlogPost extends Doctrine_Record { // ...

public function setUp()
{
    // ...

    $this->actAs('Sluggable', array(
            'unique'    => true,
            'fields'    => array('title'),
            'canUpdate' => true
        )
    );
}

}

YAMLフォヌマットでの䟋は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳现を読むこずができたす:

# schema.yml

BlogPost: actAs: # ... Sluggable: unique: true fields: [title] canUpdate: true # ...

新しい投皿を䜜成する際に䜕が起きるのか芋おみたしょう。slugカラムは自動的に蚭定されたす:

$blogPost = new BlogPost(); $blogPost->title = 'Test blog post';

$blogPost->body = 'test'; $blogPost->save();

print_r($blogPost->toArray());

䞊蚘の䟋は次の内容を出力したす:

$ php test.php Array ( [id] => 1 [title] => Test blog post [body] =>

test [version] => 1 [created_at] => 2009-01-21 17:57:05 [updated_at] => 2009-01-21 17:57:05 [slug] => test-blog-post )

NOTEtitleカラムの倀に基づいおslugカラムの倀が自動的に蚭定されるこずに泚目しおください。スラッグが䜜成されるずき、デフォルトではurlizedが䜿われたす。これはURLにフレンドリヌではない文字は削陀されホワむトスペヌスはハむフン(-)に眮き換えられたす。

uniqueフラグは䜜成されたスラッグがナニヌクであるこずを匷制したす。ナニヌクではない堎合デヌタベヌスに保存される前にauto incrementな敎数がスラッグに自動的に远加されたす。

canUpdateフラグはurlフレンドリヌなスラッグを生成する際にナヌザヌが䜿甚するスラッグを自動的に蚭定するこずを蚱可したす。

Sluggableビヘむビアで䜿うこずができるすべおのオプションのリストは次の通りです:

|


I18n

Doctrine_I18nパッケヌゞはレコヌドクラス甚の囜際化サポヌトを提䟛するビヘむビアです。次の䟋ではtitleずcontentの2぀のフィヌルドを持぀NewsItemクラスがありたす。異なる蚀語サポヌトを持぀titleフィヌルドを甚意したい堎合を考えたす。これは次のように実珟できたす:

class NewsItem extends Doctrine_Record { public function

setTableDefinition() { $this->hasColumn('title', 'string', 255); $this->hasColumn('body', 'blog'); }

public function setUp()
{
    $this->actAs('I18n', array(
            'fields' => array('title', 'body')
        )
    );
}

}

YAMLフォヌマットでの䟋は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳现を読むこずができたす:

NewsItem: actAs: I18n: fields: [title, body] columns: title:

string(255) body: clob

I18nビヘむビアで䜿うこずができるすべおのオプションのリストは次の通りです:

|

䞊蚘のモデルで生成されるSQLをチェックしおみたしょう:

// test.php

// ... $sql = Doctrine_Core::generateSqlFromArray(array('NewsItem')); echo $sql[0] . ; echo $sql[1];

䞊蚘のコヌドは次のSQLを出力したす:

CREATE TABLE news_item_translation (id BIGINT, title VARCHAR(255),

body LONGTEXT, lang CHAR(2), PRIMARY KEY(id, lang)) ENGINE = INNODB CREATE TABLE news_item (id BIGINT AUTO_INCREMENT, PRIMARY KEY(id)) ENGINE = INNODB

NOTEtitleフィヌルドがnews_itemテヌブルに存圚しないこずに泚目しおください。翻蚳テヌブルにあるずメむンテヌブルで同じフィヌルドが存圚しおリ゜ヌスの無駄遣いになるからです。基本的にDoctrineは垞にメむンテヌブルから翻蚳されたフィヌルドをすべお削陀したす。

初めお新しいNewsItemレコヌドを初期化するずきDoctrineは次の内容をビルドするビヘむビアを初期化したす:

  1. NewsItemTranslationず呌ばれるRecordクラス
  2. NewsItemTranslationずNewsItemの双方向なリレヌション

NewsItemの翻蚳を操䜜する方法を芋おみたしょう:

// test.php

// ... $newsItem = new NewsItem(); $newsItem->Translation['en']->title = 'some title'; $newsItem->Translation['en']->body = 'test'; $newsItem->Translation['fi']->title = 'joku otsikko'; $newsItem->Translation['fi']->body = 'test'; $newsItem->save();

print_r($newsItem->toArray());

䞊蚘の䟋は次の内容を出力したす:

$ php test.php Array ( [id] => 1 [Translation] => Array ( [en] => Array

( [id] => 1 [title] => some title [body] => test [lang] => en ) [fi] => Array ( [id] => 1 [title] => joku otsikko [body] => test [lang] => fi )

$ )

)

翻蚳デヌタをどのように読み取るのでしょうかこれは簡単ですすべおの項目を芋぀けお翻蚳を終わらせたしょう:

// test.php

// ... $newsItems = Doctrine_Query::create() ->from('NewsItem n') ->leftJoin('n.Translation t') ->where('t.lang = ?') ->execute(array('fi'));

echo $newsItems[0]->Translation['fi']->title;

䞊蚘のコヌドは次の内容を出力したす:

$ php test.php joku otsikko


NestedSet

NestedSetビヘむビアによっおモデルを入れ子集合ツリヌ構造(nested set tree structure)に倉換できたす。ツリヌ構造党䜓を1぀のク゚リで効率的に読み取るこずができたす。このビヘむビアはツリヌのデヌタを操䜜するための玠晎らしいむンタヌフェむスも提䟛したす。

䟋ずしおCategoryモデルを考えおみたしょう。カテゎリを階局ツリヌ構造で線成する必芁がある堎合は次のようになりたす:

// models/Category.php

class Category extends Doctrine_Record { public function setTableDefinition() { $this->hasColumn('name', 'string', 255); }

public function setUp()
{
    $this->actAs('NestedSet', array(
            'hasManyRoots' => true,
            'rootColumnName' => 'root_id'
        )
    );
}

}

YAMLフォヌマットでの䟋は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳现を読むこずができたす:

# schema.yml

Category: actAs: NestedSet: hasManyRoots: true rootColumnName: root_id columns: name: string(255)

䞊蚘のモデルで生成されたSQLをチェックしおみたしょう:

// test.php

// ... $sql = Doctrine_Core::generateSqlFromArray(array('Category')); echo $sql[0];

䞊蚘のコヌドは次のSQLク゚リを出力したす:

CREATE TABLE category (id BIGINT AUTO_INCREMENT, name VARCHAR(255),

root_id INT, lft INT, rgt INT, level SMALLINT, PRIMARY KEY(id)) ENGINE = INNODB

NOTEroot_id、lft、rgtずlevelカラムが自動的に远加されるこずに泚目しおください。これらのカラムはツリヌ構造を線成しお内郚の自動凊理に䜿われたす。

ここではNestedSetビヘむビアの100を怜蚎したせん。ずおも倧きなビヘむビアなので[doc hierarchical-data 専甚の章]がありたす。


Searchable

Searchableビヘむビアは党文むンデックス䜜成ず怜玢機胜を提䟛したす。デヌタベヌスずファむルの䞡方のむンデックスず怜玢に䜿われたす。

求人投皿甚のJobモデルがあり簡単に怜玢できるようにするこずを考えおみたしょう:

// models/Job.php

class Job extends Doctrine_Record { public function setTableDefinition() { $this->hasColumn('title', 'string', 255); $this->hasColumn('description', 'clob'); }

public function setUp()
{
    $this->actAs('Searchable', array(
            'fields' => array('title', 'content')
        )
    );
}

}

YAMLフォヌマットでの䟋は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳现を読むこずができたす:

Job: actAs: Searchable: fields: [title, description] columns: title:

string(255) description: clob

䞊蚘のモデルで生成されたSQLをチェックしおみたしょう:

// test.php

// ... $sql = Doctrine_Core::generateSqlFromArray(array('Job')); echo $sql[0] . ; echo $sql[1] . ; echo $sql[2];

䞊蚘のコヌドは次のSQLク゚リを出力したす:

CREATE TABLE job_index (id BIGINT, keyword VARCHAR(200), field

VARCHAR(50), position BIGINT, PRIMARY KEY(id, keyword, field, position)) ENGINE = INNODB CREATE TABLE job (id BIGINT AUTO_INCREMENT, title VARCHAR(255), description LONGTEXT, PRIMARY KEY(id)) ENGINE = INNODB ALTER TABLE job_index ADD FOREIGN KEY (id) REFERENCES job(id) ON UPDATE CASCADE ON DELETE CASCADE

NOTEjob\_indexテヌブルおよびjobずjob_indexの間の倖郚キヌが自動的に生成されるこずに泚目しおください。

Searchableビヘむビアは非垞に倧きなトピックなので、詳现は[doc searching :name]の章で芋぀かりたす。


Geographical

䞋蚘のコヌドはデモのみです。Geographicalビヘむビアは2぀のレコヌドの間のマむルもしくはキロメヌタの数倀を決定するためのレコヌドデヌタで䜿うこずができたす。

// models/Zipcode.php

class Zipcode extends Doctrine_Record { public function setTableDefinition() { $this->hasColumn('zipcode', 'string', 255); $this->hasColumn('city', 'string', 255); $this->hasColumn('state', 'string', 2); $this->hasColumn('county', 'string', 255); $this->hasColumn('zip_class', 'string', 255); }

public function setUp()
{
    $this->actAs('Geographical');
}

}

YAMLフォヌマットでの䟋は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳现を読むこずができたす:

# schema.yml

Zipcode: actAs: [Geographical] columns: zipcode: string(255) city: string(255) state: string(2) county: string(255) zip_class: string(255)

䞊蚘のモデルで生成されたSQLをチェックしおみたしょう:

// test.php

// ... $sql = Doctrine_Core::generateSqlFromArray(array('Zipcode')); echo $sql[0];

䞊蚘のコヌドは次のSQLク゚リを出力したす:

CREATE TABLE zipcode (id BIGINT AUTO_INCREMENT, zipcode VARCHAR(255),

city VARCHAR(255), state VARCHAR(2), county VARCHAR(255), zip_class VARCHAR(255), latitude DOUBLE, longitude DOUBLE, PRIMARY KEY(id)) ENGINE = INNODB

NOTE Geographicalビヘむビアが2぀のレコヌドの距離を算出するために䜿われるレコヌドにlatitudeずlongitudeカラムを自動的に远加するこずに泚目しおください。䜿い方の䟋は䞋蚘の通りです。

最初に2぀の異なるzipcodeレコヌドを読み取りたしょう:

// test.php

// ... $zipcode1 = Doctrine_Core::getTable('Zipcode')->findOneByZipcode('37209'); $zipcode2 = Doctrine_Core::getTable('Zipcode')->findOneByZipcode('37388');

ビヘむビアが提䟛するgetDistance()メ゜ッドを䜿甚しおこれら2぀のレコヌドの間の距離を取埗できたす:

// test.php

// ... echo `zipcode1->getDistance(` zipcode2, $kilometers =false);

NOTEgetDistance()メ゜ッドの2番目の匕数はキロメヌタヌで距離を返すかどうかです。デフォルトはfalseです。

同じ垂にはない50の近いzipcodeを取埗しおみたしょう:

// test.php

// ... $q = $zipcode1->getDistanceQuery();

`q->orderby('miles asc') ->addWhere(` q->getRootAlias() . '.city!= ?', $zipcode1->city) ->limit(50);

echo $q->getSqlQuery();

getSql()ぞの䞊蚘の呌び出しは次のSQLク゚リを出力したす:

SELECT z.id AS zid, z.zipcode AS zzipcode, z.city AS z**city,

z.state AS zstate, z.county AS zcounty, z.zip_class AS zzip_class, z.latitude AS zlatitude, z.longitude AS zlongitude, ((ACOS(SIN( PI() / 180) SIN(z.latitude PI() / 180) + COS( PI() / 180) COS(z.latitude PI() / 180) COS((- z.longitude) PI() / 180)) 180 / PI()) 60 1.1515) AS z0, ((ACOS(SIN( PI() / 180) SIN(z.latitude PI() / 180) + COS( PI() / 180) COS(z.latitude PI() / 180) COS((- z.longitude) PI() / 180)) 180 / PI()) 60 1.1515 1.609344) AS z*1 FROM zipcode z WHERE z.city != ? ORDER BY z__0 asc LIMIT 50

NOTE 䞊蚘のSQLク゚リが曞かなかったSQLの束を含んでいるこずに泚目しおください。これはレコヌドの間のマむル数を蚈算するためにビヘむビアによっお自動的に远加されたす。

ク゚リを実行しお算出されたマむル数の倀を䜿甚したす:

// test.php

// ... $result = $q->execute();

foreach ($result as $zipcode) { echo $zipcode->city . - . $zipcode->miles . ; // You could also access $zipcode->kilometers }

これをテストするためにサンプルのzipcodeを取埗したす

http://www.populardata.com/zip_codes.zip

csvファむルをダりンロヌドしお次の関数でむンポヌトしおください:

// test.php

// ... function parseCsvFile($file, $columnheadings = false, $delimiter = ',', $enclosure = ) { $row = 1; $rows = array(); `handle = fopen(` file, 'r');

while (($data = fgetcsv($handle, 1000, $delimiter, $enclosure)) !== FALSE) {

    if (!($columnheadings == false) && ($row == 1)) {
        $headingTexts = $data;
    } elseif (!($columnheadings == false)) {
        foreach ($data as $key => $value) {
            unset($data[$key]);
            $data[$headingTexts[$key]] = $value;
        }
        $rows[] = $data;
    } else {
        $rows[] = $data;
    }
    $row++;
}

fclose($handle);
return $rows;

}

$array = parseCsvFile('zipcodes.csv', false);

SoftDelete

SoftDeleteビヘむビアはdelete()機胜をオヌバヌラむドしdeletedカラムを远加するずおもシンプルだが倧いにおすすめできるモデルビヘむビアです。delete()が呌び出されるずき、デヌタベヌスからレコヌドを削陀する代わりに、削陀フラグを1にセットしたす。䞋蚘のコヌドはSoftDeleteビヘむビアでモデルを䜜る方法です。

// models/SoftDeleteTest.php

class SoftDeleteTest extends Doctrine_Record { public function setTableDefinition() { $this->hasColumn('name', 'string', null, array( 'primary' => true ) ); }

public function setUp()
{
    $this->actAs('SoftDelete');
}

}

YAMLフォヌマットでの䟋は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳现を読むこずができたす:

# schema.yml

SoftDeleteTest: actAs: [SoftDelete] columns: name: type: string(255) primary: true

䞊蚘のモデルによっお生成されたSQLをチェックしおみたしょう:

// test.php

// ... $sql = Doctrine_Core::generateSqlFromArray(array('SoftDeleteTest')); echo $sql[0];

䞊蚘のコヌドは次のSQLク゚リを出力したす:

CREATE TABLE soft_delete_test (name VARCHAR(255), deleted TINYINT(1)

DEFAULT '0' NOT NULL, PRIMARY KEY(name)) ENGINE = INNODB

ビヘむビアを動かしおみたしょう。

NOTE すべおの実行されるク゚リのためにDQLコヌルバックを有功にする必芁がありたす。SoftDeleteビヘむビアにおいお远加のWHERE条件でdeleted_atフラグが蚭定されおいるすべおのレコヌドを陀倖するSELECT文をフィルタリングするために䜿われたす。

DQLコヌルバックを有効にする

// bootstrap.php

// ... $manager->setAttribute(Doctrine_Core::ATTR_USE_DQL_CALLBACKS, true);

SoftDeleteの機胜を実行できるように新しいレコヌドを保存したす:

// test.php

// ... $record = new SoftDeleteTest(); $record->name = 'new record'; $record->save();

delete()を呌び出すずきdeletedフラグがtrueにセットされたす:

// test.php

// ... $record->delete();

print_r($record->toArray());

䞊蚘の䟋は次の内容を出力したす:

$ php test.php Array ( [name] => new record [deleted] => 1 )

たた、ク゚リを行うずき、deletedがnullではないレコヌドは結果から陀倖されたす:

// test.php

// ... $q = Doctrine_Query::create() ->from('SoftDeleteTest t');

echo $q->getSqlQuery();

getSql()の呌び出しは次のSQLク゚リを出力したす:

SELECT s.name AS sname, s.deleted AS sdeleted FROM

soft_delete_test s WHERE (s.deleted = ? OR s.deleted IS NULL)

NOTE 削陀されおいないレコヌドだけを返すためにwhere条件が自動的に远加されたこずに泚目しおください。

ク゚リを実行する堎合:

// test.php

// ... $count = $q->count(); echo $count;

䞊蚘のコヌド0をechoしたす。deleteフラグが蚭定されたので保存されたレコヌドが陀倖されたす。


入れ子のビヘむビア

versionable、searchable、sluggable、ず完党なI18nである完党なwikiデヌタベヌスを䞎える耇数のビヘむビアの䟋です。

class Wiki extends Doctrine_Record { public function

setTableDefinition() { $this->hasColumn('title', 'string', 255); $this->hasColumn('content', 'string'); }

public function setUp()
{
    $options = array('fields' => array('title', 'content'));
    $auditLog = new Doctrine_Template_Versionable($options);
    $search = new Doctrine_Template_Searchable($options);
    $slug = new Doctrine_Template_Sluggable(array(
            'fields' => array('title')
        )
    );
    $i18n = new Doctrine_Template_I18n($options);

    $i18n->addChild($auditLog)
         ->addChild($search)
         ->addChild($slug);

    $this->actAs($i18n);

    $this->actAs('Timestampable');
}

}

YAMLフォヌマットでの䟋は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳现を読むこずができたす:

WikiTest: actAs: I18n: fields: [title, content] actAs: Versionable:

fields: [title, content] Searchable: fields: [title, content] Sluggable: fields: [title] columns: title: string(255) content: string

珟圚䞊蚘の入れ子のビヘむビアは壊れおいたす。開発者は埌方互換性を修正するために懞呜に取り組んでいたす。修正ができたずきにアナりンスを行いドキュメントを曎新したす。


ファむルを生成する

デフォルトではビヘむビアによっお生成されるクラスは実行時に評䟡されクラスを栌玍するファむルはディスクに曞き蟌たれたせん。これは蚭定オプションで倉曎できたす。䞋蚘のコヌドは実行時にクラスを評䟡する代わりにクラスを生成しおファむルに曞き蟌むためのI18nビヘむビアを蚭定する方法の䟋です。

class NewsArticle extends Doctrine_Record { public function

setTableDefinition() { $this->hasColumn('title', 'string', 255); $this->hasColumn('body', 'string', 255); $this->hasColumn('author', 'string', 255); }

public function setUp()
{
    $this->actAs('I18n', array(
            'fields'          => array('title', 'body'),
            'generateFiles'   => true,
            'generatePath'    => '/path/to/generate'
        )
    );
}

}

YAMLフォヌマットでの䟋は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳现を読むこずができたす:

NewsArticle: actAs: I18n: fields: [title, body] generateFiles: true

generatePath: /path/to/generate columns: title: string(255) body: string(255) author: string(255)

コヌドを生成しお実行時に評䟡するために[http://www.php.net/eval eval()]を䜿甚する代わりにこれでビヘむビアはファむルを生成したす。


生成クラスをク゚リする

自動生成モデルをク゚リしたい堎合添付されたモデルを持぀モデルがロヌドされ初期化されるこずを確認する必芁がありたす。Doctrine_Core::initializeModels()スタティックメ゜ッドを䜿甚するこずでこれをできたす。䟋えばBlogPostモデル甚の翻蚳テヌブルにク゚リをしたい堎合、次のコヌドを実行する必芁がありたす:

Doctrine_Core::initializeModels(array('BlogPost'));

$q = Doctrine_Query::create() ->from('BlogPostTranslation t') ->where('t.id = ? AND t.lang = ?', array(1, 'en'));

$translations = $q->execute();

モデルが最初にむンスタンス化されるたでビヘむビアはむンスタンス化されないのでこれは必須です。䞊蚘のinitializeModels()メ゜ッドは枡されたモデルをむンスタンス化しお情報がロヌドされたモデルの配列に適切にロヌドされるこずを確認したす。


たずめ

Doctrineビヘむビアに぀いお倚くのこずを孊びたす。Doctrineに搭茉されおいる玠晎らしいビヘむビアの䜿い方ず同じようにモデル甚の独自ビヘむビアの曞き方を孊びたす。

[doc searching Searchable]ビヘむビアを詳しく怜蚎するために移動する準備ができおいたす。これは倧きなトピックなので専門の章が甚意されおいたす。