Skip to content

Instantly share code, notes, and snippets.

@index0h
Created November 18, 2014 08:27
Show Gist options
  • Save index0h/8478a104a7dc3dc5e60e to your computer and use it in GitHub Desktop.
Save index0h/8478a104a7dc3dc5e60e to your computer and use it in GitHub Desktop.
yii1-translation-behavior
<?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 <index.0h@gmail.com>
* @copyright Roman Levishchenko <index.0h@gmail.com>
* @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