'{item}_Create', 'update' => '{item}_Update', 'updateLocal' => '{item}_UpdateLocal', 'delete' => '{item}_Delete', ); protected $_notNullFields = array(); protected $_insertPlacement = 'PREPEND'; public $itemName = 'Item'; protected $_requestData = array(); public function __get($name) { if ($language = self::_getLanguageByName($name)) { $name = substr($name, 0, strlen($name) - 4); /** @var self $class */ $class = get_called_class(); $value = (isset($class::$contentFields[$language][$name]) ? $class::$contentFields[$language][$name] : null); return $value; } return parent::__get($name); } public static function createQuery($config = Array()) { if (static::$castQueryResult) { return static::createCastQuery(); } return parent::createQuery($config); } protected static function _getLanguageByName($name) { if (strlen($name) > 4 && $name[strlen($name) - 4] == '_') { $language = substr($name, strlen($name) - 3); /** @var self $class */ $class = get_called_class(); if (in_array($language, array_keys(Yii::$app->params['languages']))) { return $language; } else { return false; } } else { return false; } } public static function search($params = null) { $query = self::find(); if ($params) { $query->andWhere($params); } $dataProvider = new ActiveDataProvider(array( 'query' => $query, )); return $dataProvider; } public static function tableNameLocale() { return null; } public static function checkTranslate() { /** @var self $class */ $class = get_called_class(); return $class::ENABLE_TRANSLATE; } public static function find($q = null) { $query = static::createQuery(); if (self::checkTranslate()) { self::_prepareLanguageContent($query); } /** @var self $class */ $class = get_called_class(); if (is_array($q)) { return $query->where($q)->one(); } elseif ($q !== null) { // query by primary key $primaryKey = static::primaryKey(); if (isset($primaryKey[0])) { if ($class::checkTranslate()) { $class::_setContentAttributes($q); $model = $query->where(array($class::tableName() . '.' . $primaryKey[0] => $q))->one(); } else { $model = $query->where([$primaryKey[0] => $q])->one(); } return $model; } else { throw new InvalidConfigException(get_called_class() . ' must have a primary key.'); } } return $query; } public static function primaryKey() { /** @var self $class */ $class = get_called_class(); if ($class::USE_VIEW) { return array('id'); } else { return static::getTableSchema()->primaryKey; } } protected static function _setContentAttributes($q) { $data = self::_getCurrentContentValues($q); $fields = self::_getContentFields(); $result = array(); /** @var self $class */ $class = get_called_class(); foreach ($data as $languageData) { foreach ($fields as $field) { $result[$languageData['languageId']][$field] = $languageData[$field]; } } $class::$contentFields = $result; } protected static function _getCurrentContentValues($id) { /** @var self $class */ $class = get_called_class(); $query = new Query; $query->from($class::tableNameLocale()) ->where(array($class::CONTENT_TABLE_KEY => $id)); return $query->createCommand()->queryAll(); } protected static function _getContentFields() { /** @var self $class */ $class = get_called_class(); $fields = static::getDb()->schema->getTableSchema($class::tableNameLocale())->columns; unset($fields['languageId']); unset($fields[$class::CONTENT_TABLE_KEY]); return array_keys($fields); } static protected function _prepareLanguageContent(ActiveQuery &$query) { /** @var self $class */ $class = get_called_class(); $query->leftJoin( $class::tableNameLocale(), '`' . $class::tableNameLocale() . '`.`' . $class::CONTENT_TABLE_KEY . '`=`' . $class::tableName() . '`.`id` AND `' . $class::tableNameLocale() . '`.`languageId`="' . Yii::$app->language . '"' ); $columns = ''; foreach (self::_getContentFields() as $field ) { $columns .= '`' . $class::tableNameLocale() . '`.`' . $field . '` as `' . $field . '`, '; } $query->select($columns . $class::tableName() . '.*'); } public function attributeLabels() { if (self::checkTranslate()) { $labels = array(); foreach (array_keys(Yii::$app->params['languages']) as $code) { foreach (self::_getContentFields() as $field) { $labels[$field . '_' . $code] = Yii::t('api', ucfirst($field)) . ' (' . strtoupper($code) . ')'; } } return $labels; } else { return parent::attributeLabels(); } } public static function getGridOptionLinks($model) { /** @var self $class */ $class = get_called_class(); $content = Html::a(Html::encode(Yii::t('api', 'View')), array('view', 'id' => $model->id)); if ($class::checkEditable($model)) { $content .= '  |  ' . Html::a(Html::encode(Yii::t('api', 'Edit')), array('update', 'id' => $model->id)) . '  |  '; $content .= Html::a( Html::encode(Yii::t('api', 'Delete')), array('delete', 'id' => $model->id), array('class' => 'row-delete-confirm') ); } ListAsset::register(yii::$app->view); $options = array('item' => $model->itemName); yii::$app->view->registerJs('Module_List.init(' . json_encode($options) . ');'); return $content; } public static function checkEditable($model) { return true; } public function transactions() { return [ 'update' => self::OP_UPDATE, 'insert' => self::OP_INSERT, 'delete' => self::OP_DELETE, ]; } public function afterSave($insert) { if (self::checkTranslate() && $this->getPrimaryKey()) { $this->_saveContentData(); } } protected function _saveContentData() { $contentData = array(); /** @var self $class */ $class = get_called_class(); $formName = $this->formName(); foreach (array_keys(Yii::$app->params['languages']) as $code) { $contentData[$code] = array( self::CONTENT_TABLE_KEY => $this->getPrimaryKey(), 'languageId' => $code ); foreach ($class::_getContentFields() as $field ) { $contentData[$code][$field] = isset($this->_requestData[$formName][$field . '_' . $code]) ? $this->_requestData[$formName][$field . '_' . $code] : null; } } $db = static::getDb(); foreach ($contentData as $contentRow) { if ($this->_saveViaDbFunction) { $command = static::getDb()->createCommand(); $command->setSql($this->_createLocaleCommand($contentRow)); } else { if ($contentRowId = $this->_getCurrentContentId($contentRow['languageId'])) { $command = $db->createCommand()->update($this->tableNameLocale(), $contentRow, $contentRowId); } else { $command = $db->createCommand()->insert($this->tableNameLocale(), $contentRow); } } $command->execute(); } } protected function _createLocaleCommand($rowData) { $columns = array_merge(array('id', 'languageId'), $this->_getContentFields()); $data = array(); foreach ($columns as $column) { $data[$column] = $rowData[$column]; } return 'SELECT ' . $this->_getDbCrudFunctionName('updateLocal') . '(' . implode(', ', $this->_getPreparedFunctionData($rowData)) . ');'; } protected function _getCurrentContentId($language) { $query = new Query; $query->select(array('id', 'languageId')) ->from($this->tableNameLocale()) ->where(array('languageId' => $language, self::CONTENT_TABLE_KEY => $this->getPrimaryKey())); return $query->createCommand()->queryOne(); } public function createValidators() { $validators = new ArrayObject; foreach ($this->getPreparedRules() as $rule) { if ($rule instanceof Validator) { $validators->append($rule); } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type $validator = Validator::createValidator($rule[1], $this, $rule[0], array_slice($rule, 2)); $validators->append($validator); } else { throw new InvalidConfigException( 'Invalid validation rule: a rule must specify both attribute names and validator type.'); } } return $validators; } protected function getPreparedRules() { $rules = $this->rules(); $resultRules = array(); foreach ($rules as $rule) { if ($rule[1] == 'image') { if (isset($rule[2]['params'])) { $this->_imageParams[$rule[0]] = $rule[2]['params']; } $resultRules[] = array($rule[0], 'file', 'types'=>'jpg, gif, png'); } elseif (self::checkTranslate() && in_array($rule[0], self::_getContentFields())) { foreach (array_keys(Yii::$app->params['languages']) as $code) { $newRule = $rule; $newRule[0] .= '_' . $code; $resultRules[] = $newRule; } } else { $resultRules[] = $rule; } } return $resultRules; } public function activeAttributes() { $attributes = parent::activeAttributes(); if (self::checkTranslate()) { $contentFields = self::_getContentFields(); foreach ($attributes as $attribute) { if (in_array($attribute, $contentFields)) { foreach (yii::$app->params['languages'] as $code => $title) { $attributes[] = $attribute . '_' . $code; } } } } return $attributes; } public function __set($name, $value) { if (self::_getLanguageByName($name)) { $this->$name = $value; } else { parent::__set($name, $value); } } public function setAttributes($values, $safeOnly = true) { if (is_array($values)) { foreach ($values as $name => $value) { try { $this->$name = $value; } catch (\Exception $e) { Yii::trace("Unknown Property " . $name); } if ($safeOnly) { $this->onUnsafeAttribute($name, $value); } } } } public function update($runValidation = true, $attributes = null) { if ($this->_saveViaDbFunction) { return $this->updateViaDbFunction($runValidation, $attributes); } else { return parent::update($runValidation, $attributes); } } public function updateViaDbFunction($runValidation = true, $attributes = null) { if ($runValidation && !$this->validate($attributes)) { return false; } $db = static::getDb(); if ($this->isTransactional(self::OP_UPDATE) && $db->getTransaction() === null) { $transaction = $db->beginTransaction(); try { $result = $this->updateInternalViaDbFunction($attributes); if ($result === false) { $transaction->rollback(); } else { $transaction->commit(); } } catch (\Exception $e) { $transaction->rollback(); throw $e; } } else { $result = $this->updateInternalViaDbFunction($attributes); } return $result; } public function updateInternalViaDbFunction($attributes = null) { if (!$this->beforeSave(false)) { return false; } $data = $this->_getFunctionData($attributes, false); if (!empty($data)) { $sql = 'SELECT ' . $this->_getDbCrudFunctionName('update') . '(' . implode(', ', $this->_getPreparedFunctionData($data)) . ');'; $command = static::getDb()->createCommand(); $command->setSql($sql); $result = $command->execute(); } else { $result = 0; } $this->afterSave(false); return $result; } protected function _getPreparedFunctionData($data) { $result = array(); $db = yii::$app->getDb(); foreach ($data as $field => $value) { if (($value === null) && in_array($field, $this->_notNullFields)) { $result[$field] = '""'; } elseif ($value === null) { $result[$field] = 'NULL'; } else { $result[$field] = $db->quoteValue($value); } } return $result; } protected function _getFunctionData($attributes, $createFlag = true, $allowEmptyField = false) { $values = $this->getDirtyAttributes($attributes); if (empty($values) && !$allowEmptyField) { $this->afterSave(false); return array(); } $data = array(); $columns = $createFlag ? $this->_insertViaDbFunctionColumn : $this->_updateViaDbFunctionColumn; foreach ($columns as $column) { if (isset($values[$column])) { $data[$column] = $values[$column]; } else if (self::checkTranslate() && in_array($column, self::_getContentFields())){ foreach (yii::$app->params['languages'] as $code => $value) { $property = $column . '_' . $code; $data[$column] = $this->$property; if (isset($data[$column])) { break; } } } else if ($column == 'sorter') { $data[$column] = $this->_getSorter(); } else if (isset($this->$column)) { $data[$column] = $this->$column; } else if ($allowEmptyField && $this->$column === null) { $data[$column] = ''; } else { $data[$column] = $this->getAttribute($column); } $resetColumns = $createFlag ? $this->_insertViaDbFunctionResetColumn : $this->_updateViaDbFunctionResetColumn; if (in_array($column, $resetColumns)) { $data[$column . '_reset'] = (isset($data[$column]) && '' !== $data[$column]) ? 'n' : 'y'; } } if (!$createFlag) { $this->setOldAttributes($data); } return $data; } public function insert($runValidation = true, $attributes = null) { if ($this->_saveViaDbFunction) { return $this->insertViaDbFunction($runValidation, $attributes); } else { return parent::insert($runValidation, $attributes); } } public function delete() { if ($this->_saveViaDbFunction) { return $this->deleteViaDbFunction(); } else { return parent::delete(); } } public function deleteViaDbFunction() { $db = static::getDb(); $transaction = $this->isTransactional(self::OP_DELETE) && $db->getTransaction() === null ? $db->beginTransaction() : null; try { $result = false; if ($this->beforeDelete()) { // we do not check the return value of deleteAll() because it's possible // the record is already deleted in the database and thus the method will return 0 $condition = $this->getOldPrimaryKey(true); $lock = $this->optimisticLock(); if ($lock !== null) { $condition[$lock] = $this->$lock; } $result = $this->deleteAllViaDbFunction($condition); if ($lock !== null && !$result) { throw new StaleObjectException('The object being deleted is outdated.'); } $this->setOldAttributes(null); $this->afterDelete(); } if ($transaction !== null) { if ($result === false) { $transaction->rollback(); } else { $transaction->commit(); } } } catch (\Exception $e) { if ($transaction !== null) { $transaction->rollback(); } throw $e; } return $result; } public function deleteAllViaDbFunction($condition = '', $params = []) { if (!is_array($condition) || !isset($condition['id'])) { throw new Exception('Wrong item condition'); } $sql = 'SELECT ' . $this->_getDbCrudFunctionName('delete') . '(' . current($condition) . ')'; return static::getDb()->createCommand()->setSql($sql)->execute(); } public function insertViaDbFunction($runValidation = true, $attributes = null) { if ($runValidation && !$this->validate($attributes)) { return false; } $db = static::getDb(); if ($this->isTransactional(self::OP_INSERT) && $db->getTransaction() === null) { $transaction = $db->beginTransaction(); try { $result = $this->insertInternalViaDbFunction($attributes); if ($result === false) { $transaction->rollback(); } else { $transaction->commit(); } } catch (\Exception $e) { $transaction->rollback(); throw $e; } } else { $result = $this->insertInternalViaDbFunction($attributes); } return $result; } public function insertInternalViaDbFunction($attributes = null) { if (!$this->beforeSave(false)) { return false; } $data = $this->_getFunctionData($attributes); if (!empty($data)) { $sql = 'SELECT ' . $this->_getDbCrudFunctionName('insert') . '(' . implode(', ', $this->_getPreparedFunctionData($data)) . ');'; $command = static::getDb()->createCommand(); $command->setSql($sql); $result = $command->execute(); $statement = $command->pdoStatement->fetchAll(); $this->setAttribute('id', $statement[0][0]); } else { $result = 0; } $this->afterSave(false); return $result; } protected function _getDbCrudFunctionName($action) { return str_replace('{item}', $this->_saveViaDbFunctionItem, $this->_saveViaDbFunctionItemTemplate[$action]); } protected function _getSorter($insertPlacement = null, $tableName = null) { if (null === $insertPlacement) { $insertPlacement = $this->_insertPlacement; } $tableName = (null == $tableName) ? $this->tableName() : $tableName; if ('PREPEND' == $insertPlacement) { $function = 'MIN'; $diff = -1; } elseif ('APPEND' == $insertPlacement) { $function = 'MAX'; $diff = 1; } else { throw new Exception('Unknown insert placement: ' . $insertPlacement); } $query = new Query(); $query->from($tableName)->select(array($function . '(`sorter`)'))->limit(1); return (null === ($sorter = (int) current($query->one()))) ? 0 : $sorter + $diff; } public function load($data, $formName = NULL) { $this->_requestData = $data; return parent::load($data, $formName); } }