トップ  > symfony  > モデル ( doctrine )  > 記事

No.1859 Doctrineのビヘイビアを作る

Doctrine のビヘイビアを作る

この節では Doctrine 1.2 を用いてビヘイビアを作成する例をご紹介します。 これから作るものは、リレーションシップの件数を簡単に保持しておくもので、毎回クエリを発行しなくても件数を取得できるようにするものです。

この機能はとてもシンプルです。リレーションシップの件数を保持したいと思うものすべてに対し、件数を保持するためのカラムを追加します。

スキーマ

これから使うスキーマです。後ほどビヘイビアのために actAs という定義を追加していきます。

# config/doctrine/schema.yml
Thread:
  columns:
    title:
      type: string(255)
      notnull: true
 
Post:
  columns:
    thread_id:
      type: integer
      notnull: true
    body:
      type: clob
      notnull: true
  relations:
    Thread:
      onDelete: CASCADE
      foreignAlias: Posts

スキーマをビルドするには以下のコマンドを実行します:

$ php symfony doctrine:build --all

テンプレート

まずは Doctrine_Template の子クラスを作り、件数を保持するカラムをモデルに追加する記述をします。

lib/ ディレクトリ以下に配置すると symfony のオートロードの対象になりますので、そこに置きましょう:

// lib/count_cache/CountCache.class.php
class CountCache extends Doctrine_Template
{
  public function setTableDefinition()
  {
  }
 
  public function setUp()
  {
  }
}

では Post モデルに actAs を定義して CountCache ビヘイビアを追加します:

# config/doctrine/schema.yml
Post:
  actAs:
    CountCache: ~
  # ...

これで Post モデルで CountCache ビヘイビアが使えるようになりましたので、ビヘイビアについて少し説明します。

モデルがインスタンス化されたとき、付属するビヘイビアの setTableDefinition()setUp() メソッドが呼ばれ、マッピング情報が定義されます。これは lib/model/doctrine/base/BasePost.class.php にある BasePost クラスなどと同じ挙動です。 これによりプラグアンドプレイ形式でカラム、リレーションシップ、イベントリスナーなどの追加が可能です。

なんとなく仕組みがわかったかと思うので、CountCache ビヘイビアに処理を記述していきます:

class CountCache extends Doctrine_Template
{
  protected $_options = array(
    'relations' => array()
  );
 
  public function setTableDefinition()
  {
    foreach ($this->_options['relations'] as $relation => $options)
    {
      // Build column name if one is not given
      if (!isset($options['columnName']))
      {
        $this->_options['relations'][$relation]['columnName'] = 'num_'.Doctrine_Inflector::tableize($relation);
      }
 
      // Add the column to the related model
      $columnName = $this->_options['relations'][$relation]['columnName'];
      $relatedTable = $this->_table->getRelation($relation)->getTable();
      $this->_options['relations'][$relation]['className'] = $relatedTable->getOption('name');
      $relatedTable->setColumn($columnName, 'integer', null, array('default' => 0));
    }
  }
}

このコードはリレーションシップの件数を保持するカラムをモデルに追加するものです。 今回は Post モデルの Thread へのリレーションシップに対してビヘイビアを追加します。各 Threadnum_posts カラムにポストされた件数を保持させましょう。YAML スキーマにビヘイビアのためのオプションを定義します:

# ...
 
Post:
  actAs:
    CountCache:
      relations:
        Thread:
          columnName: num_posts
          foreignAlias: Posts
  # ...

これで Thread モデルに現在のポスト件数を保持するための num_posts カラムができました。

イベントリスナー

次のステップは、レコードが新規作成されたときや削除されたときに常に最新の件数が正しく保持されるよう、イベントリスナーの記述を行います。

class CountCache extends Doctrine_Template
{
  // ...
 
  public function setTableDefinition()
  {
    // ...
 
    $this->addListener(new CountCacheListener($this->_options));
  }
}

何はともあれ、Doctrine_Record_Listener を継承した CountCacheListener クラスを定義しましょう。このクラスは Template クラスから配列形式のオプションを受け取ります。

// lib/model/count_cache/CountCacheListener.class.php
 
class CountCacheListener extends Doctrine_Record_Listener
{
  protected $_options;
 
  public function __construct(array $options)
  {
    $this->_options = $options;
  }
}

最新の件数を保持するためには以下のイベントを使う必要があります。

  • postInsert(): レコードが新規作成されたときに件数を増やします。

  • postDelete(): レコードが削除されたときに件数を減らします。

  • preDqlDelete(): DQL 経由で削除が実行されたときに件数を減らします。

まずは postInsert() メソッドの定義を行います。

class CountCacheListener extends Doctrine_Record_Listener
{
  // ...
 
  public function postInsert(Doctrine_Event $event)
  {
    $invoker = $event->getInvoker();
    foreach ($this->_options['relations'] as $relation => $options)
    {
      $table = Doctrine::getTable($options['className']);
      $relation = $table->getRelation($options['foreignAlias']);
 
      $table
        ->createQuery()
        ->update()
        ->set($options['columnName'], $options['columnName'].' + 1')
        ->where($relation['local'].' = ?', $invoker->$relation['foreign'])
        ->execute();
    }
  }
}

上記のコードは、設定したリレーションシップに該当するレコードが下記のようにして新規作成されたときに、DQL の UPDATE 文を用いて件数を増やすものです:

$post = new Post();
$post->thread_id = 1;
$post->body = 'body of the post';
$post->save();

この場合は id1Threadnum_posts カラムが 1 増加します。

これでレコードが作成されたときに件数が増えるようになりました。 次にレコードが削除されたときに件数を減らすために postDelete() メソッドを実装します:

class CountCacheListener extends Doctrine_Record_Listener
{
  // ...
 
  public function postDelete(Doctrine_Event $event)
  {
    $invoker = $event->getInvoker();
    foreach ($this->_options['relations'] as $relation => $options)
    {
      $table = Doctrine::getTable($options['className']);
      $relation = $table->getRelation($options['foreignAlias']);
 
      $table
        ->createQuery()
        ->update()
        ->set($options['columnName'], $options['columnName'].' - 1')
        ->where($relation['local'].' = ?', $invoker->$relation['foreign'])
        ->execute();
    }
  }
}

この postDelete() メソッドは num_posts の値を 1 減らす以外、先ほどの postInsert() メソッドとほぼ同じものです。 下記は先ほど作成した $post レコードを削除するコードで、このような場合に上記のコードが実行されます:

$post->delete();

パズルの最後のピースとなるのは DQL を用いてレコードを削除した場合の処理です。 この場合は preDqlDelete() メソッドで対処できます:

class CountCacheListener extends Doctrine_Record_Listener
{
  // ...
 
  public function preDqlDelete(Doctrine_Event $event)
  {
    foreach ($this->_options['relations'] as $relation => $options)
    {
      $table = Doctrine::getTable($options['className']);
      $relation = $table->getRelation($options['foreignAlias']);
 
      $q = clone $event->getQuery();
      $q->select($relation['foreign']);
      $ids = $q->execute(array(), Doctrine::HYDRATE_NONE);
 
      foreach ($ids as $id)
      {
        $id = $id[0];
 
        $table
          ->createQuery()
          ->update()
          ->set($options['columnName'], $options['columnName'].' - 1')
          ->where($relation['local'].' = ?', $id)
          ->execute();
      }
    }
  }
}

このコードは発行された DQL の DELETE 文を複製して SELECT文 に変換し、削除しようとしているレコードの ID を取得してきて、対応するレコードのリレーションシップ件数を減らしています。

下記のような場合にリレーションシップ件数が自動的に減るようになります:

Doctrine::getTable('Post')
  ->createQuery()
  ->delete()
  ->where('id = ?', 1)
  ->execute();

また、下記のように複数のレコードを削除することもできますが、その場合でもリレーションシップ件数は減ります:

Doctrine::getTable('Post')
  ->createQuery()
  ->delete()
  ->where('body LIKE ?', '%cool%')
  ->execute();

preDqlDelete() などの DQL 発行時に実行されるメソッドは、パフォーマンスを考慮してデフォルトで無効に設定されています。これらのメソッドを有効にするためには以下のように設定の変更を行う必要があります。

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

これでビヘイビアの実装が完了しましたので、最後に少しテストをしましょう。

テスト

先ほど実装したコードのテストのためにフィクスチャーを作成しましょう:

# data/fixtures/data.yml
 
Thread:
  thread1:
    title: Test Thread
    Posts:
      post1:
        body: This is the body of my test thread
      post2:
        body: This is really cool
      post3:
        body: Ya it is pretty cool

ではモデルとデータを再構築して、フィクスチャーを読み込ませましょう:

$ php symfony doctrine:build --all --and-load

これで再構築とフィクスチャーの読み込みが完了しました。まずはリレーションシップの件数が正しく保持されているか見てみましょう:

$ php symfony doctrine:dql "FROM Thread t, t.Posts p"
doctrine - executing: "FROM Thread t, t.Posts p" ()
doctrine - id: '1'
doctrine - title: 'Test Thread'
doctrine - num_posts: '3'
doctrine - Posts:
doctrine - -
doctrine - id: '1'
doctrine - thread_id: '1'
doctrine - body: 'This is the body of my test thread'
doctrine - -
doctrine - id: '2'
doctrine - thread_id: '1'
doctrine - body: 'This is really cool'
doctrine - -
doctrine - id: '3'
doctrine - thread_id: '1'
doctrine - body: 'Ya it is pretty cool'

Thread モデルの num_posts3 になっているのが確認できると思います。 次にポストを 1 件削除して件数が減っているかテストしましょう:

$post = Doctrine_Core::getTable('Post')->find(1);
$post->delete();

レコードが削除され、件数が更新されているのが確認できると思います:

$ php symfony doctrine:dql "FROM Thread t, t.Posts p"
doctrine - executing: "FROM Thread t, t.Posts p" ()
doctrine - id: '1'
doctrine - title: 'Test Thread'
doctrine - num_posts: '2'
doctrine - Posts:
doctrine - -
doctrine - id: '2'
doctrine - thread_id: '1'
doctrine - body: 'This is really cool'
doctrine - -
doctrine - id: '3'
doctrine - thread_id: '1'
doctrine - body: 'Ya it is pretty cool'

DQL の DELETE 文で残りの 2 レコードをまとめて削除しても動作するかテストしましょう:

Doctrine_Core::getTable('Post')
  ->createQuery()
  ->delete()
  ->where('body LIKE ?', '%cool%')
  ->execute();

関連するすべてのポストを削除したので、num_posts は 0 になっていなければなりません:

$ php symfony doctrine:dql "FROM Thread t, t.Posts p"
doctrine - executing: "FROM Thread t, t.Posts p" ()
doctrine - id: '1'
doctrine - title: 'Test Thread'
doctrine - num_posts: '0'
doctrine - Posts: { }

バッチリです!ここで学んだビヘイビアについてのことや、ビヘイビアそのものがお役に立てることを願っています!

続く...

引用元

更新:2009/12/11 17:00 カテゴリ: symfony  > モデル ( doctrine ) ▲トップ

FuelPHP

Mac

フロントエンド開発

web開発

プロマネ

マネタイズ

プレゼン

webサービス運用

webサービス

Linux

サーバ管理

MySQL

ソース・開発

svn・git

PHP

HTML・CSS

JavaScript

ツール, ライブラリ

ビジネス

テンプレート

負荷・チューニング

Windows

メール

メール・手紙文例

CodeIgniter

オブジェクト指向

UI・フロントエンド

cloud

マークアップ・テキスト

Flash

デザイン

DBその他

Ruby

PostgreSQL

ユーティリティ・ソフト

Firefox

ハードウェア

Google

symfony

OpenPNE全般

OpenPNE2

Hack(賢コツ)

OpenPNE3

リンク

個人開発

その他

未確認

KVS

ubuntu

Android

負荷試験

オープンソース

社会

便利ツール

マネー

Twig

食品宅配

WEB設計

オーディオ

一般常識

アプリ開発

Python

サイトマップ

うずら技術ブログ

たませんSNS

rss2.0