Doctrine、お気に入りのORMフレームワークの機能を拡張します! パート1.b(I18n、翻訳された属性へのクイックアクセスの変更)

前の記事で、翻訳可能な属性にすばやくアクセスする1つの方法を検討しました。 これが何であるかを理解するために、この記事の前に上記の記事を読むことを強くお勧めします)すでに読んだ人のために、主なtsimesは、__ call( )-関数テンプレート。 このアプローチの明らかなマイナス点は、他のテンプレートで__call()を使用できないことです。これは良くありません。 そのようなアクセスを実装するためのより美しく効果的な方法があり、それはもともとほとんど最初のバージョンからDoctrineで提供されていました-これらは属性フィルターです。 繰り返しになりますが、多くのコードとテキストがカットされています。



フィルターを介した翻訳可能な属性への迅速なアクセス



Doctrine_Recordではどのようにフィルターが使用されますか? 詳細を説明しなくても、図の例は次のようになります。



// Doctrine_Record::_get()

foreach ($ this ->_table->getFilters() as $filter) {

try {

$ value = $filter->filterGet($ this , $fieldName);

$success = true ;

} catch (Doctrine_Exception $e) {}

}

if ($success) {

return $ value ;

} else {

throw $e;

}






ご覧のように、任意の属性(存在しないものを含む)に対して、レコードに割り当てられた各フィルターインスタンスに対してfilterGet()呼び出しが行われます!



これは何を意味するのでしょうか? そして、外部フィルタークラスを介して属性にアクセスするネイティブメソッドを使用できるという事実。 デフォルトでは、Doctrine_Recordには1つのフィルターのみが設定されています-Doctrine_Record_Filter_Compoundは与えられた属性の存在を追跡し、存在しない場合は例外をスローします。 この例外はDoctrine_Recordでキャッチされ、次のフィルターに進むか、フィルターがなくなったら再びスローします。 すべてが非常に単純であり、タスクは独自のフィルターを実装することです。これは、初期化で指定した属性の呼び出しをインターセプトし、レコードのTranslationアソシエーションの対応するフィールドの値を返し、他の例外をスローします。 コードの挿入(上記参照)からわかるように、レコードはフィルターを順番に呼び出し、結果を削除して次のフィルムに進むためにフィルターをキャッチします。 そのようなフィルターを実装してみましょう。



フィルター



最初に、フィルターを作成するには、Doctrine_Record_Filterから継承したクラスを作成し、2つの抽象メソッドDoctrine_Record_Filter :: filterGet()およびDoctrine_Record_Filter :: filterSet()を実装する必要があります。 ゲッターとセッターのおおよそのスキーム(上記を参照)からわかるように、レコードはフィルターを順番に引き起こし、エラーが発生した場合は例外をスローする必要があります。



今回は、クラスの作成方法の推論のチェーンには触れませんが、すぐに使用できる既成のフィルターコードを提供し、その後、何と理由を分析します。

/**

* EasyAccess package filter. Implements access to record's properties as for translated in I18n.

*

* Can be used as a part of Of_ExtDoctrine_I18n_Helper system, or as stand-alone filter both.

*

* @author OmeZ

* @version 1.0

* @license www.opensource.org/licenses/lgpl-license.php LGPL

* @package Of_ExtDoctrine_I18n_EasyAccess

*/

class Of_ExtDoctrine_I18n_EasyAccess_Filter extends Doctrine_Record_Filter {



/**

* Fields

*

* @var array

*/

protected $_fields = array();



/**

* Language

*

* @var string

*/

protected $_language;



/**

* @var Of_ExtDoctrine_I18n_EasyAccess_OwnerInterface

*/

protected $_owner;



/**

* Constructs new filter with options

*

* @param array $options

* @return void

*/

public function __construct(array $options) {

if (isset($options[ 'fields' ])) $ this ->setFields($options[ 'fields' ]);

if (isset($options[ 'language' ])) $ this ->setLanguage($options[ 'language' ]);

if (isset($options[ 'owner' ])) $ this ->setOwner($options[ 'owner' ]);

}



/**

* Returns owner of filter

*

* @return Of_ExtDoctrine_I18n_EasyAccess_OwnerInterface

*/

public function getOwner() {

return $ this ->_owner;

}



/**

* Sets owner for filter

*

* @param Of_ExtDoctrine_I18n_EasyAccess_OwnerInterface $owner

* @return Of_ExtDoctrine_I18n_EasyAccess_Filter

*/

public function setOwner(Of_ExtDoctrine_I18n_EasyAccess_OwnerInterface $owner) {

$ this ->_owner = $owner;

return $ this ;

}



/**

* Returns fields aliases for filter

*

* @return array

*/

public function getFields() {

return $ this ->_fields;

}



/**

* Sets fields aliases for filter

*

* @param $fields

* @return Of_ExtDoctrine_I18n_EasyAccess_Filter

*/

public function setFields($fields) {

$ this ->_fields = (array)$fields;

return $ this ;

}



/**

* Returns default language for filter

*

* @return string

*/

public function getLanguage() {

if ($ this ->_language !== null ) {

return $ this ->_language;

} elseif ($ this ->_owner) {

return $ this ->_owner->getLanguage();

} else {

require_once 'Of/ExtDoctrine/I18n/EasyAccess/Exception.php' ;

throw new Of_ExtDoctrine_I18n_EasyAccess_Exception( 'Impossible to detect language' );

}

}



/**

* Sets language to filter

*

* @return Of_ExtDoctrine_I18n_EasyAccess_Filter

*/

public function setLanguage($language) {

$ this ->_language = $language;

return $ this ;

}



/**

* Returns value of translatable attribute

*

* @param Doctrine_Record $record

* @param string $name

* @return mixed

*/

public function filterGet(Doctrine_Record $record, $name) {

return $ this ->getTranslation($record, $name, $ this ->getLanguage());

}



/**

* Sets value to translatable attribute

*

* @param Doctrine_Record $record

* @param string $name

* @param mixed $value

* @return void

*/

public function filterSet(Doctrine_Record $record, $name, $ value ) {

return $ this ->setTranslation($record, $name, $ value , $ this ->getLanguage());

}



/**

* Language dependent getter to translatable attribute

*

* @param Doctrine_Record $record

* @param string $name

* @param string $language

* @param boolean $return_first_found

* @return mixed

*/

public function getTranslation(Doctrine_Record $record, $name, $language = null , $return_first_found = true ) {

if (in_array($name, $ this ->_fields)) {

$language = empty($language)?( string )$ this ->getLanguage():( string )$language;

if ($record->hasRelation( 'Translation' )) {

if ($record->Translation->contains($language)) {

return $record->Translation[$language][$name];

} elseif ($return_first_found && $record->Translation->count()) {

foreach ($record->Translation as $translation)

if (!empty($translation[$name]))

return $translation[$name];

return null ;

} else return null ;

} else return null ;

} else {

require_once 'Of/ExtDoctrine/I18n/EasyAccess/NotTranslatableException.php' ;

throw new Of_ExtDoctrine_I18n_EasyAccess_NotTranslatableException( "Field {$name} is not marked as easy getter to tranlsatable attribute in " .get_class($record));

}

}



/**

* Language dependent setter to translatable attribute

*

* @param Doctrine_Record $record

* @param string $name

* @param mixed $value

* @param string $language

* @return void

*/

public function setTranslation(Doctrine_Record $record, $name, $ value , $language = null ) {

if (in_array($name, $ this ->_fields)) {

$language = empty($language)?( string )$ this ->getLanguage():( string )$language;

if ($record->hasRelation( 'Translation' )) {

$record->Translation[$language][$name] = $ value ;

}

} else {

require_once 'Of/ExtDoctrine/I18n/EasyAccess/NotTranslatableException.php' ;

throw new Of_ExtDoctrine_I18n_EasyAccess_NotTranslatableException( "Field {$name} is not marked as easy setter to tranlsatable attribute in " .get_class($record));

}

}



}









分析時間!



コンストラクター


/**

* Constructs new filter with options

*

* @param array $options

* @return void

*/

public function __construct(array $options) {

if (isset($options[ 'fields' ])) $ this ->setFields($options[ 'fields' ]);

if (isset($options[ 'language' ])) $ this ->setLanguage($options[ 'language' ]);

if (isset($options[ 'owner' ])) $ this ->setOwner($options[ 'owner' ]);

}






ここでフィルターを初期化し、フィールド、言語、およびフィルターの神秘的な所有者に関する情報を含むオプションのリストを渡します。 オプション(前の記事のインターフェイスに似たインターフェイス)を詳しく見てみましょう。





インストールおよび取得方法


これは、メソッドのセット(取得/設定)フィールド()、(取得/設定)言語()、(取得/設定)所有者()です。 原則として、非伝統的なものは何もないので、すべてを詳細に検討することはしません。 getLanguage()、tkのみを停止します。 謎の所有者が再び登場します。

/**

* Returns default language for filter

*

* @return string

*/

public function getLanguage() {

if ($ this ->_language !== null ) {

return $ this ->_language;

} elseif ($ this ->_owner) {

return $ this ->_owner->getLanguage();

} else {

require_once 'Of/ExtDoctrine/I18n/EasyAccess/Exception.php' ;

throw new Of_ExtDoctrine_I18n_EasyAccess_Exception( 'Impossible to detect language' );

}

}






ご覧のとおり、所有者は、言語の値が指定されていない場合にのみ言語を要求する必要があります。 これは、このフィルターを別の構造に統合できるようにするために行われます。 私の場合、これは修正されたOf_ExtDoctrine_I18n_EasyAccess_Helperテンプレートであり、以前の記事で検討した類似です。 所有者は、 Of_ExtDoctrine_I18n_EasyAccess_OwnerInterfaceインターフェースに従う必要があります。 このインターフェースには、1つのパブリックgetLanguage()メソッドのみが含まれます。

interface Of_ExtDoctrine_I18n_EasyAccess_OwnerInterface {



/**

* Returns language for inherited components

*

* @return string

*/

public function getLanguage();



}








フィルター方法


最終的に、必要なフィルター機能を実装するメソッド自体に近づきました。

/**

* Returns value of translatable attribute

*

* @param Doctrine_Record $record

* @param string $name

* @return mixed

*/

public function filterGet(Doctrine_Record $record, $name) {

return $ this ->getTranslation($record, $name, $ this ->getLanguage());

}



/**

* Sets value to translatable attribute

*

* @param Doctrine_Record $record

* @param string $name

* @param mixed $value

* @return void

*/

public function filterSet(Doctrine_Record $record, $name, $ value ) {

return $ this ->setTranslation($record, $name, $ value , $ this ->getLanguage());

}



/**

* Language dependent getter to translatable attribute

*

* @param Doctrine_Record $record

* @param string $name

* @param string $language

* @param boolean $return_first_found

* @return mixed

*/

public function getTranslation(Doctrine_Record $record, $name, $language = null , $return_first_found = true ) {

if (in_array($name, $ this ->_fields)) {

$language = empty($language)?( string )$ this ->getLanguage():( string )$language;

if ($record->hasRelation( 'Translation' )) {

if ($record->Translation->contains($language)) {

return $record->Translation[$language][$name];

} elseif ($return_first_found && $record->Translation->count()) {

foreach ($record->Translation as $translation)

if (!empty($translation[$name]))

return $translation[$name];

return null ;

} else return null ;

} else return null ;

} else {

require_once 'Of/ExtDoctrine/I18n/EasyAccess/NotTranslatableException.php' ;

throw new Of_ExtDoctrine_I18n_EasyAccess_NotTranslatableException( "Field {$name} is not marked as easy getter to tranlsatable attribute in " .get_class($record));

}

}



/**

* Language dependent setter to translatable attribute

*

* @param Doctrine_Record $record

* @param string $name

* @param mixed $value

* @param string $language

* @return void

*/

public function setTranslation(Doctrine_Record $record, $name, $ value , $language = null ) {

if (in_array($name, $ this ->_fields)) {

$language = empty($language)?( string )$ this ->getLanguage():( string )$language;

if ($record->hasRelation( 'Translation' )) {

$record->Translation[$language][$name] = $ value ;

}

} else {

require_once 'Of/ExtDoctrine/I18n/EasyAccess/NotTranslatableException.php' ;

throw new Of_ExtDoctrine_I18n_EasyAccess_NotTranslatableException( "Field {$name} is not marked as easy setter to tranlsatable attribute in " .get_class($record));

}

}






前の部分のアクセサーとミューテーターと実際に違いはなく、2つのフィルタリングメソッドと2つの人工的なゲッターとセッターで構成されていることがわかります。 時々任意の言語の翻訳値を取得する機会を残したいので、それらを作成しました。



また、ゲッターでは、現在の言語の翻訳が空の場合に最初の翻訳された値を検索する機能を無効または有効にすることが可能になりました。 デフォルトではオンになっていますが、何らかの形で追加オプションにする必要があります。



フィルター接続



最初の部分のレコードと同様のレコードを作成し、フィルターを接続します。

class Product extends Doctrine_Record {



public function setTableDefinition() {

//....

$ this ->hasColumn( 'name' , 'string' , 255);

$ this ->hasColumn( 'description' , 'string' );

//....

}



public function setUp() {



$ this ->actAs( 'I18n' , array(

'fields' =>array( 'name' , 'description' )

));



$i18nFilter = new Of_ExtDoctrine_I18n_EasyAccess_Filter(array(

'language' => 'en' ,

'fields' =>array( 'name' , 'description' ) //

));

$ this ->getTable()->unshiftFilter($i18nFilter);

}



}






$record = new Product();

$record->description = 'my description' ; // $record->Translation['en']->description



echo $record->description; // echo $record->Translation[ 'en' ]->description






「description」属性にアクセスすると、フィルターが機能し、英語の値が返されますが、未指定の属性を有効にすると、フィルターは例外をスローし、次のフィルターDoctrine_Record_Filter_Compoundはそのような属性が存在しないことを知らせます

try {

echo $record->lol; //

} catch (Of_ExtDoctrine_I18n_EasyAccess_NotTranslatableException $e) {

//...

}






属性への書き込みでも同じになりますが、考慮しません。



テンプレート統合



Of_ExtDoctrine_I18n_Templateテンプレートを介した属性へのアクセスを検討していました。 hasAccessorMutatorの代わりにフィルターを使用するように変更してみましょう。 途中で、名前を変更してシステムをOf_ExtDoctrine_I18n_EasyAccessパッケージに配置します。 クラスのソースコードをすぐに提供します。

/**

* Temlate implements extrabehavior for standard Doctrine I18n template.

*

* @author OmeZ

* @version 1.7

* @license www.opensource.org/licenses/lgpl-license.php LGPL

* @package Of_ExtDoctrine_I18n_EasyAccess

*/

class Of_ExtDoctrine_I18n_EasyAccess_Helper extends Doctrine_Template implements Of_ExtDoctrine_I18n_EasyAccess_OwnerInterface {



protected $_options = array(

'language' => null ,

'fields' => null ,

'disableFilter' => false ,

);



/**

* Holds default language for all behaviors

*

* @var string

*/

static protected $_defaultLanguage;



/**

* Holds language for current model behavior

*

* @var string

*/

protected $_language;



/**

* EasyAccess filter

*

* @var Of_ExtDoctrine_I18n_EasyAccess_Filter

*/

protected $_easyaccess_filter;



public function setUp() {



$language = $ this ->getOption( 'language' );

if ($language) $ this ->setLanguage($language);



// Adds filter for access to properties, this can be used as stand-alone plugin

if (!$ this ->getOption( 'disableFilter' )) {

require_once 'Of/ExtDoctrine/I18n/EasyAccess/Filter.php' ;

$ this ->_easyaccess_filter = new Of_ExtDoctrine_I18n_EasyAccess_Filter(array(

'owner' =>$ this ,

'language' => null , // this value will make filter access to template getLanguage() method

'fields' =>$ this ->getOption( 'fields' , array())

));

$ this ->_table->unshiftFilter($ this ->_easyaccess_filter);

}



// adds listener to manage Doctrine_Query hydrations. Will add translatable props as keys in

// array when HYDRATE_ARRAY, or mapped values in records. This can be used as stand-alone plugin

}



/**

* Returns default language for all behaviors

*

* @return string

*/

static public function getDefaultLanguage() {

return ( string )self::$_defaultLanguage;

}



/**

* Sets default language for all behaviors

*

* @param string $language

* @return void

*/

static public function setDefaultLanguage($language) {

self::$_defaultLanguage = $language;

}



/**

* Returns current language behavior

*

* @return void

*/

public function getLanguage($without_static = false ) {

if ($ this ->_language === null && !$without_static) return self::getDefaultLanguage();

else return ( string )$ this ->_language;

}



/**

* Sets current behavior language

*

* @param $language

* @return string

*/

public function setLanguage($language) {

$ this ->_language = $language;

}

}






テンプレートに変更はありません。Translationの読み取り/書き込みメソッドを削除しただけです。 同じ理由で不要になったため、フィルタと__call()メソッドに転送されます。



フィルターは以前のようにsetUp()メソッドのテーブルにインストールされます。何らかの理由でフィルターを無効にする必要がある場合は、disableFilterオプションが追加されます。 フィルターの所有者として、テンプレートをインストールし、言語の空の終端を渡します。これにより、テンプレートでフィルター言語の現在の値を制御できます。Of_ExtDoctrine_I18n_EasyAccess_OwnerInterface:: getLanguage()メソッドは既に実装されています。 残るのは、デモレコードのテンプレートを接続し、テストを実施することだけです

class Product extends Doctrine_Record {



public function setTableDefinition() {

//....

$ this ->hasColumn( 'name' , 'string' , 255);

$ this ->hasColumn( 'description' , 'string' );

//....

}



public function setUp() {



$ this ->actAs( 'I18n' , array(

'fields' =>array( 'name' , 'description' )

));



$ this ->actAs( new Of_ExtDoctrine_I18n_EasyAccess_Helper(array(

'fields' =>array( 'name' , 'description' ) //

)));



}



}






$record = new Product();



$record->description = 'my description' ; // $record->Translation['en']->description

echo $record->description; // echo $record->Translation['en']->description



$record->setLanguage( 'ru' ); //



echo $record->description; // 'my description', ..









おわりに



このようなアクセスの実装ははるかに便利であり、判明したように、前述の方法よりもはるかに高速です。 次のステップは、Doctrine :: HYDRATE_ARRAYなど、さまざまなハイドレーションメソッドで属性にアクセスする機能です。 そこにはオブジェクトは作成されず、Translationアソシエーション(この場合はネストされた配列)の使用を強制されます。



1つのアーカイブ内のすべてのクラスはここにあります。



PS。 熱心な読者は、フィルターがテンプレートに記述されているフィールドと言語のバインディングを繰り返すことに気付いているかもしれません。 これは、テンプレートを使用せずにスタンドアロンコンポーネントとしてフィルターを使用するために使用されました。テンプレートはこのようなシステムに適したフォームであると考えています。



この記事では、 ソースコードハイライターを使用してコードを強調表示しました。



All Articles