Doctrine_RecordのpostHydrateフックを使う

Doctrineには、例えば「特定のモデルでレコード保存前」だとかにカスタム処理を実行するための仕組みとして、イベントリスナーの機能と、それと似たレコードフックの機能があります。

イベントリスナーは、どちらかというとモデルに関係なくデータベース処理全体に対して、特定のタイミングの処理をカスタマイズする場合に使います。
対してレコードフックは、特定のモデルでの特定のタイミングの処理をカスタマイズする場合に使います。

このレコードフックは、Doctrine_Recordクラスに中身が空のメソッドとして実装されており、Doctrine_Recordを継承している各モデルクラスでオーバーライドします。


ドキュメントには、以下の10個のレコードフックが説明されています。

  • preSave()
  • postSave()
  • preUpdate()
  • postUpdate()
  • preInsert()
  • postInsert()
  • preDelete()
  • postDelete()
  • preValidate()
  • postValidate()

しかし、Doctrine_Recordクラスのソースを見ると、以下のものもレコードフック?として実装されています。

  • preHydrate()
  • postHydrate()
  • preSerialize()
  • postSerialize()
  • preUnserialize()
  • postUnserialize()

今回は、このうちの「postHydrate()」を使ってみました。

postHydrate()はハイドレートされるオブジェクト自身で呼び出されるわけではない

この点に多少ハマッたので、今回のエントリを書いています。
postHydrate()というのは文字通り、DQLでデータベースからレコードを取得し、そのレコードから当該モデルオブジェクトのインスタンスが作られる際に呼び出されるのですが、最初私は、「ハイドレーションされた後に、生成されたモデルオブジェクト自身で呼び出される」と勘違いしていました。

例えば、ハイドレーション後に「姓」と「名」を結合した「姓名」フィールドを作っておきたいとします。

失敗例:

<?php
public function postHydrate($event)
{
    $this->sei_mei = $this->sei . ' ' . $this->mei;
}

ハイドレーション後のオブジェクト自身のメソッドとして呼び出されていれば、上記でsei_meiフィールドが設定されるはずですが、これではエラーになってしまいます。

実際、postHydrate()は/Doctrine/Hydrator/Graph.phpのhydrateResultSet()内で以下のように呼び出されています。

<?php
153       $element = $this->getElement($rowData[$rootAlias], $componentName);
154       $event->set('data', $element);
155       $listeners[$componentName]->postHydrate($event);
156       $instances[$componentName]->postHydrate($event);

これを見ると、$eventオブジェクトのdataプロパティに生成されたオブジェクトが格納されてpostHydrateが呼び出されていることが分かります。

というわけで、上のコードは以下のようにする必要があります。

修正例:

<?php
public function postHydrate($event)
{
    $obj = $event->data;
    $obj->sei_mei = $obj->sei . ' ' . $obj->mei;
    $event->set('data', $obj);
}

ドキュメントにないのは忘れられている?

このpostHydraete()フックですが、最初に紹介したドキュメントページのレコードフック一覧に記述がありません。

ただ、最近追加された機能というわけではなく、2008年10月のDoctrine 1.1の頃に追加されたようです。

実装だけして、ドキュメントの更新が忘れられているということなんでしょうか。