フィルターを介した翻訳可能な属性への迅速なアクセス
Doctrine_Recordではどのようにフィルターが使用されますか? 詳細を説明しなくても、図の例は次のようになります。
- $ record->属性-スクリプトは特定の属性を要求します
- Doctrine :: __ get( 'attribute')メソッドが呼び出され、これはDoctrine_Record :: get( 'attribute')を呼び出し、それは順番にDoctrine :: _ get( 'attribute')メソッドです。
- このキューのプロセスでは、アクセサーとミューテーターの存在(セッターに対応する同じキュー)、関連付けの存在などのチェックが行われ、最後に奇妙な構造が呼び出されます...
// 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' ]);
}
ここでフィルターを初期化し、フィールド、言語、およびフィルターの神秘的な所有者に関する情報を含むオプションのリストを渡します。 オプション(前の記事のインターフェイスに似たインターフェイス)を詳しく見てみましょう。
- 「フィールド」 -追跡する属性のリスト。 今後、存在しない属性を指定して有効にすると、フィルターは例外をスローします
- 「言語」は、属性にアクセスするために使用するデフォルトの言語です
- 「所有者」は、フィルターの神秘的な所有者です。 このオプションは、以前に設定されていない場合にフィルターが言語を取得するためにアクセスする(Of_ExtDoctrine_I18n_EasyAccess_OwnerInterfaceインターフェースの)オブジェクトを指定するために使用されます。 しかし、それについては後で
インストールおよび取得方法
これは、メソッドのセット(取得/設定)フィールド()、(取得/設定)言語()、(取得/設定)所有者()です。 原則として、非伝統的なものは何もないので、すべてを詳細に検討することはしません。 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。 熱心な読者は、フィルターがテンプレートに記述されているフィールドと言語のバインディングを繰り返すことに気付いているかもしれません。 これは、テンプレートを使用せずにスタンドアロンコンポーネントとしてフィルターを使用するために使用されました。テンプレートはこのようなシステムに適したフォームであると考えています。
この記事では、 ソースコードハイライターを使用してコードを強調表示しました。