Yiiフレームワークでのアイデンティティマップテンプレートの実装

こんにちは、habrasociety!



問題



データベースを使用したり、アプリケーションのさまざまな部分からアクセスできるオブジェクトを使用したりする場合、同等であると思われるオブジェクトがまったく存在しないという危険があります。



たとえば、いくつかのActiveRecordモデル、Expence、およびこのコードがあるとします。

$modelOne = Expence::model()->findByPk(10); $modelTwo = Expence::model()->findByPk(10); var_dump($modelOne === $modelTwo); //  false
      
      





したがって、1つのモデルを変更しても、2番目のモデルには影響しません(異なるオブジェクトを参照するため、論理的です)。

 $modelOne->someField = "Data"; $modelOne->save(); /// ...- ... echo $modelTwo->someField; //    $modelTwo->save(); //    
      
      









解決策



この問題を解決するために、Martin Fowler Identity Mapという設計パターンを使用します。

彼のアイデアは、同じ識別子を持つアプリケーション内のオブジェクトの存在を追跡することです。 したがって、プログラムの2つの異なる場所でidが5のモデルを要求すると、同じオブジェクトへのリンクが取得されます。



実装



残念ながら、Yiiはデータベースからオブジェクトを要求したかどうかを監視しないため、独自のクラスを作成する必要があります。

 <?php /** * Singleton class to manipulate instances of models (eg CActiveRecord). * * @author Yuriy Ratanov <organium@gmail.com> */ class ObjectWatcher { /** * Current instance of ObjectWatcher * @var ObjectWatcher */ private static $_instance; /** * Array of objects to work with. * @var array */ private $objects = array(); /** * Geting instance of ObjectWatcher. * @return ObjectWatcher */ static function getInstance(){ if(!isset(self::$_instance)){ self::$_instance = new ObjectWatcher; } return self::$_instance; } /** * Getting instance of the object existing in the current application. * @param string $className * @param int $id * @return mixed null or object of the class $className with an id = $id if it exists. */ static function getRecord($className, $id) { $inst = self::getInstance(); $key = "$className.$id"; if(isset($inst->objects[$key])){ return $inst->objects[$key]; } return null; } /** * Adding object to ObjectWatcher registry. * @param $obj * @param int $id */ static function addRecord($obj, $id) { $inst = self::getInstance(); $inst->objects[$inst->getKey($obj, $id)] = $obj; } function getKey($obj, $id){ return get_class($obj).'.'.$id; } }
      
      







addRecordメソッドとgetRecordメソッドを使用して、モデルの固有のレジストリ( "class_name.id" =>オブジェクトの形式の連想配列)からモデルを追加および取得します。



ここで、まだ受け取っていない場合はYiiにオブジェクトを作成させるか、$ objects配列から既存のオブジェクトを返す必要があります。 CActiveRecordの後に余分なレイヤーを作成せずに、Yiiツールを使用してこれを実行しようとします。 もちろん、Expence :: model()-> findByPk(10)を実行した後、データベースを照会せずに以前に取得したオブジェクトを返しますが、Yiiにはこの照会をインターセプトするメカニズムはありません。 はい、CActiveRecord :: beforeFind()がありますが、特にfindByPk()の後に、リクエストに関するデータを取得することはできません。 モデルインスタンスは、CActiveRecord :: instantiate()メソッドで作成されます。 モデルで再定義します。

 <?php class Expence extends CActiveRecord { //...-  protected function instantiate($attributes) { if(($record = ObjectWatcher::getRecord(get_class($this), $attributes['id'])) != false){ //   ,       $model = $record; }else{//           $model = parent::instantiate($attributes); ObjectWatcher::addRecord($model, $attributes['id']);//     } return $model; } //...-  }
      
      





これですべてです

 $modelOne = Expence::model()->findByPk(10); $modelTwo = Expence::model()->findByPk(10); var_dump($modelOne === $modelTwo); // true
      
      







参照:



All Articles