Created
November 18, 2014 08:27
-
-
Save index0h/8478a104a7dc3dc5e60e to your computer and use it in GitHub Desktop.
yii1-translation-behavior
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Поведение для стандартизированной работы с переводами. | |
* | |
* Подготовка. | |
* Создаем основную модель. | |
* | |
* - **ДОЛЖНА НЕ** иметь переводимых полей. | |
* - **ДОЛЖНА** иметь поле id. | |
* - Основная модель не обязана иметь прописанную связь с моделью переводов. | |
* | |
* Создаем модель переводов со следующими требованиями. | |
* | |
* - **ДОЛЖНА** называться так же, как и основная модель, но с суффиксом **Translation**. | |
* - **ДОЛЖНА** иметь поле id. | |
* - **ДОЛЖНА** иметь поле sourceId - внешний ключ на основную модель, поле id. | |
* - **ДОЛЖНА** иметь поле language. | |
* - **НЕ ДОЛЖНА** иметь поля, названия которых соответствуют названиям языков. | |
* | |
* Подключаем поведение в основную модель. | |
* | |
* ```php | |
* <?php | |
* public function behaviors() | |
* { | |
* return array( | |
* 'translation' => array( | |
* 'class' => 'namespace\of\behavior\Translation', | |
* // Параметр languages - опциональный, по умолчанию используется \Yii::app()->params['languages']. | |
* 'languages' => array('ru', 'en', 'uk') | |
* ) | |
* ); | |
* } | |
* ``` | |
* | |
* Использование. | |
* Допустим у нас имеется основная модель [id, code] и ее переводы [id, sourceId, language, data] для языков [ru, en]. | |
* | |
* 1. Создание и сохранение. | |
* | |
* ```php | |
* <?php | |
* $object = new MainModel; | |
* $object->code = 'someCode'; | |
* $object->ru->data = 'Данные на русском языке'; | |
* $object->en->data = 'English data'; | |
* $object->save(); | |
* ``` | |
* | |
* ```php | |
* <?php | |
* $object = new MainModel; | |
* $object->code = 'someCode'; | |
* $object->ru->data = 'Данные на русском языке'; | |
* if ($object->save() === false) { | |
* echo 'нет английского перевода'; | |
* } | |
* ``` | |
* | |
* 2. Поиск. | |
* ```php | |
* <?php | |
* $object = MainModel::model()->findAll(); | |
* echo $object->ru->data; | |
* ``` | |
* | |
* 3. Удаление. | |
* ```php | |
* <?php | |
* $object = MainModel::model()->findByPk(1); | |
* $object->delete(); | |
* ``` | |
* | |
* @author Roman Levishchenko <[email protected]> | |
* @copyright Roman Levishchenko <[email protected]> | |
* @license MIT | |
*/ | |
/** | |
* Поведение для стандартизированной работы с переводами. | |
*/ | |
class Translation extends \CActiveRecordBehavior | |
{ | |
/** @var array Доступные языки для модели. */ | |
public $languages; | |
/** @var array Префикс для алиаса таблицы. */ | |
public $prefix = ''; | |
/** @var string Поле связей для переводов. */ | |
protected $relationField = 'sourceId'; | |
/** @var boolean Пропускаем валидацию. */ | |
public $skipValidation = false; | |
/** | |
* Подключает переводы по умолчанию. | |
*/ | |
public function __construct() | |
{ | |
$this->languages = \Yii::app()->params['languages']; | |
} | |
/** | |
* Добавляет связи переводов и инициализирует их для данной модели. | |
* | |
* @param \CModelEvent $event Событие после инициализации модели. | |
*/ | |
public function afterConstruct($event) | |
{ | |
$this->addRelations(); | |
$this->createRelationObjects(); | |
} | |
/** | |
* Добавляет связи переводов и расширяет критерии загрузки. | |
* | |
* @param \CModelEvent $event Событие перед поиском моделей. | |
*/ | |
public function beforeFind($event) | |
{ | |
$this->addRelations(); | |
$criteria = new \CDbCriteria; | |
$criteria->with = $this->languages; | |
$this->owner->dbCriteria->mergeWith($criteria); | |
} | |
/** | |
* В случае, если переводы не создались после поиска (не найдены), создаем их. | |
* | |
* @param \CModelEvent $event Событие перед поиском моделей. | |
*/ | |
public function afterFind($event) | |
{ | |
$this->createRelationObjects(); | |
} | |
/** | |
* Перед удалением основной модели - удаляет все ее переводы. | |
* | |
* @param \CModelEvent $event Событие перед удалением модели. | |
*/ | |
public function beforeDelete($event) | |
{ | |
foreach ($this->languages as $language) { | |
if (empty($this->owner->{$language}) === false) { | |
$this->owner->{$language}->delete(); | |
} | |
} | |
} | |
/** | |
* Ассоциирует все переводы с главной моделью. | |
* | |
* @param \CModelEvent $event Событие после сохранения модели. | |
*/ | |
public function afterSave($event) | |
{ | |
foreach ($this->languages as $language) { | |
$model = $this->owner->{$language}; | |
$model->{$this->relationField} = $this->owner->id; | |
if ($model->save() === false) { | |
$this->addErrorsToOwner($model->errors, $language); | |
} | |
} | |
} | |
/** | |
* Выполняет предварительную проверку всех переводов. | |
* | |
* @param \CModelEvent $event Используется на случай ошибок валидации. | |
*/ | |
public function beforeValidate($event) | |
{ | |
if ($this->skipValidation === true) { | |
return; | |
} | |
foreach ($this->languages as $language) { | |
$translation = $this->owner->{$language}; | |
$translation->validate(); | |
$errors = $translation->errors; | |
if (empty($errors) === true) { | |
// Ошибок нет. | |
continue; | |
} | |
if ($translation->isNewRecord === false) { | |
// Запись уже создана, но не валидна после обновления. | |
$event->isValid = false; | |
$this->addErrorsToOwner($errors, $language); | |
return; | |
} | |
if (isset($errors[$this->relationField])) { | |
// Запись новая, если ошибка во внешнем ключе - очищаем ее. | |
unset($errors[$this->relationField]); | |
} | |
if (empty($errors) === true) { | |
// Ошибок нет. | |
continue; | |
} else { | |
// Найдены другие ошибки валидации. | |
$event->isValid = false; | |
$this->addErrorsToOwner($errors, $language); | |
return; | |
} | |
} | |
} | |
/** | |
* Добавляет связи с переводами. | |
* ВНИМАНИЕ!! | |
* Модель переводов должна называться так же, как и основная, но с суффиксом 'Translation'. | |
* Так же она должна находится в том же namespace. | |
*/ | |
protected function addRelations() | |
{ | |
$translationModel = get_class($this->owner) . 'Translation'; | |
$metaData = $this->owner->getMetaData(); | |
foreach ($this->languages as $language) { | |
if ($metaData->hasRelation($language) === true) { | |
continue; | |
} | |
$metaData->addRelation( | |
$language, | |
array( | |
\CActiveRecord::HAS_ONE, | |
$translationModel, | |
$this->relationField, | |
'alias' => "{$this->prefix}{$language}", | |
'on' => "{$this->prefix}{$language}.language = '{$language}'" | |
) | |
); | |
} | |
} | |
/** | |
* Инициализирует объекты переводов. | |
*/ | |
protected function createRelationObjects() | |
{ | |
$translationModel = get_class($this->owner) . 'Translation'; | |
foreach ($this->languages as $language) { | |
if ($this->owner->{$language} === null) { | |
$this->owner->{$language} = new $translationModel; | |
$this->owner->{$language}->language = $language; | |
} | |
} | |
} | |
/** | |
* Добавляет переводимый объект ошибки из моделей переводов, что бы их можно было отследить. | |
* | |
* @param array $allErrors Массив ошибок взятых из $translation->errors. | |
* @param string $language Язык перевода, в котором найдены ошибки. | |
*/ | |
protected function addErrorsToOwner($allErrors, $language) | |
{ | |
foreach ($allErrors as $field => $errors) { | |
foreach ($errors as $error) { | |
$this->owner->addError('id', "Translation '{$language}' field '{$field}': {$error}."); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment