/** * Attach two-way sync needed behaviors for REST action * @param ActionEvent $event * @return bool * @throws NotSupportedException */ public function beforeRestAction(ActionEvent $event) { /** @var Action $action */ $action = $event->action; Event::on(BaseActiveRecord::className(), BaseActiveRecord::EVENT_INIT, [$this, 'makeModelSyncable'], null, false); switch (get_class($action)) { case IndexAction::className(): $action->attachBehavior('indexLatest', IndexLatestBehavior::className()); break; case CreateAction::className(): // nothing to attach on create for now // TODO: check is needed to behave something here break; case UpdateAction::className(): $action->attachBehavior('updateConflict', UpdateConflictBehavior::className()); break; default: // TODO: implement custom new so called 'TwoWaySync action' stub for future custom actions // TODO: and add method attachSyncBehaviors() // $action->attachSyncBehaviors(); // throw new NotSupportedException("Not implemented yet"); break; } return $event->isValid; }
/** * @inheritdoc */ public function rules() { return array_merge(parent::rules(), [[['modelClass', 'controllerClass'], 'filter', 'filter' => 'trim'], [['modelClass', 'controllerClass'], 'required'], [['modelClass', 'controllerClass'], 'match', 'pattern' => '/^[\\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], [['modelClass'], 'validateClass', 'params' => ['extends' => BaseActiveRecord::className()]], [['controllerClass'], 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'], [['controllerClass'], 'match', 'pattern' => '/(^|\\\\)[A-Z][^\\\\]+Controller$/', 'message' => 'Controller class name must start with an uppercase letter.'], ['controllerClass', function () { $dir = Yii::getAlias('@app') . '/' . $this->moduleId . '/controllers'; if (!file_exists($dir)) { mkdir($dir, 0777, true); } }], [['controllerClass'], 'validateNewClass']]); }
/** * @throws InvalidConfigException */ public function init() { parent::init(); if (!$this->redirectUrl) { $this->redirectUrl = Url::previous(self::URL_NAME_INDEX_ACTION); } if (!is_subclass_of($this->modelClass, BaseActiveRecord::className())) { throw new InvalidConfigException("Property 'modelClass': given class extend '" . BaseActiveRecord::className() . "'"); } }
/** * @inheritdoc */ public function rules() { return array_merge(parent::rules(), [[['db', 'ns', 'tableName', 'modelClass', 'baseClass'], 'filter', 'filter' => 'trim'], [['ns', 'modelLangClass'], 'filter', 'filter' => function ($value) { return trim($value, '\\'); }], [['modelLangClass'], 'filter', 'filter' => function ($value) { if ($value !== '' && strpos($value, '\\') === false) { return $this->ns . '\\' . $value; } return $value; }], [['db', 'ns', 'tableName', 'baseClass'], 'required'], [['db', 'modelClass'], 'match', 'pattern' => '/^\\w+$/', 'message' => 'Only word characters are allowed.'], [['ns', 'baseClass'], 'match', 'pattern' => '/^[\\w\\\\]+$/', 'message' => 'Only word characters and backslashes are allowed.'], [['tableName'], 'match', 'pattern' => '/^(\\w+\\.)?([\\w\\*]+)$/', 'message' => 'Only word characters, and optionally an asterisk and/or a dot are allowed.'], [['db'], 'validateDb'], [['ns'], 'validateNamespace'], [['tableName'], 'validateTableName'], [['modelClass'], 'validateModelClass', 'skipOnEmpty' => false], [['modelLangClass'], 'validateClass', 'params' => ['extends' => BaseActiveRecord::className()]], [['baseClass'], 'validateClass', 'params' => ['extends' => ActiveRecord::className()]], [['generateRelations', 'generateLabelsFromComments'], 'boolean'], [['modelClassQuery'], 'boolean'], [['enableI18N'], 'boolean'], [['useTablePrefix'], 'boolean'], [['isLang'], 'boolean'], [['messageCategory'], 'validateMessageCategory', 'skipOnEmpty' => false]]); }
/** * @inheritdoc */ public function rules() { return array_merge(parent::rules(), [ [['controllerClass', 'modelClass', 'searchModelClass', 'baseControllerClass'], 'filter', 'filter' => 'trim'], [['modelClass', 'controllerClass', 'baseControllerClass', 'indexWidgetType'], 'required'], [['searchModelClass'], 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'], [['modelClass', 'controllerClass', 'baseControllerClass', 'searchModelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], [['modelClass'], 'validateClass', 'params' => ['extends' => BaseActiveRecord::className()]], [['baseControllerClass'], 'validateClass', 'params' => ['extends' => Controller::className()]], [['controllerClass'], 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'], [['controllerClass'], 'match', 'pattern' => '/(^|\\\\)[A-Z][^\\\\]+Controller$/', 'message' => 'Controller class name must start with an uppercase letter.'], [['controllerClass', 'searchModelClass'], 'validateNewClass'], [['indexWidgetType'], 'in', 'range' => ['grid', 'list']], [['modelClass'], 'validateModelClass'], [['enableI18N'], 'boolean'], [['messageCategory'], 'validateMessageCategory', 'skipOnEmpty' => false], ['viewPath', 'safe'], ]); }
/** * @inheritdoc * @throws \yii\base\UnknownPropertyException */ public static function populateRecord($record, $row) { $attributes = array_flip($record->attributes()); foreach ($attributes as $attributeName => $attributeValue) { if (!array_key_exists($attributeName, $row)) { throw new UnknownPropertyException("Attribute `{$attributeName}` not found in API response. Available fields: " . implode(', ', array_keys($row)) . '.'); } } parent::populateRecord($record, $row); }
/** * Populates an active record object using a row of data from the database/storage. * * This is an internal method meant to be called to create active record objects after * fetching data from the database. It is mainly used by [[ActiveQuery]] to populate * the query results into active records. * * When calling this method manually you should call [[afterFind()]] on the created * record to trigger the [[EVENT_AFTER_FIND|afterFind Event]]. * * @param BaseActiveRecord $record the record to be populated. In most cases this will be an instance * created by [[instantiate()]] beforehand. * @param array $row attribute values (name => value) */ public static function populateRecord($record, $row) { $columns = array_flip($record->attributes()); foreach ($row as $name => $value) { if (isset($columns[$name])) { $record->_attributes[$name] = $value; } elseif ($record->canSetProperty($name)) { $record->{$name} = $value; } } $record->_oldAttributes = $record->_attributes; }
/** * @inheritdoc */ public static function populateRecord($record, $row) { $attributes = []; if (isset($row['_source'])) { $attributes = $row['_source']; } if (isset($row['fields'])) { // reset fields in case it is scalar value TODO use field metadata for this foreach ($row['fields'] as $key => $value) { if (count($value) == 1) { $row['fields'][$key] = reset($value); } } $attributes = array_merge($attributes, $row['fields']); } parent::populateRecord($record, $attributes); $pk = static::primaryKey()[0]; //TODO should always set ID in case of fields are not returned if ($pk === '_id') { $record->_id = $row['_id']; } $record->_highlight = isset($row['highlight']) ? $row['highlight'] : null; $record->_score = isset($row['_score']) ? $row['_score'] : null; $record->_version = isset($row['_version']) ? $row['_version'] : null; // TODO version should always be available... }
public static function log(BaseActiveRecord $model, $message = null, $category = 'application') { if ($model->hasErrors()) { Yii::error(VarDumper::dumpAsString(['class' => get_class($model), 'message' => $message, 'attributes' => $model->getAttributes(), 'errors' => $model->getErrors()]), $category); } else { Yii::info(VarDumper::dumpAsString(['class' => get_class($model), 'message' => $message, 'attributes' => $model->getAttributes()]), $category); } }
/** * @param $class * @param array $attributes * * @return object * @throws Exception * @throws \yii\base\InvalidConfigException * */ public function createModel($class, $attributes = []) { /** @var ActiveRecord $model */ $model = Yii::createObject($class); if (!$model || !is_a($model, BaseActiveRecord::className())) { throw new Exception('This class is not supported for creating from ArrayObject. Only subclasses of yii\\base\\Model are supported'); } if ($attributes && !empty($attributes)) { foreach ($attributes as $attribute) { $model->setAttribute($attribute, $this->{$attribute}); } } else { $model->setAttributes($this->getValues(), true); } return $model; }
/** * @inheritdoc * @todo */ public static function deleteAll($condition = '', $params = []) { parent::deleteAll($condition, $params); }
/** * Regular populate overridden to determine the dynamic attributes first and add those. * After that the populate can continue normally. Please note that this will pickup ANY * attribute not returned as a regular one, even if it wasn't added as magic property * * @param \yii\db\BaseActiveRecord $record * @param array $row */ public static function populateRecord($record, $row) { if ($row) { // Just figure out the ones we don't already know about and register them so they also get picked up $dynamic = array_diff(array_keys($row), $record->attributes()); $record->addAttributes($dynamic); } // Now let the regular code do its job parent::populateRecord($record, $row); }
/** * @inheritdoc */ public function rules() { return array_merge(parent::rules(), [[['modelClass'], 'required'], [['modelClass', 'baseControllerClass', 'extraActions'], 'filter', 'filter' => 'trim'], [['modelClass', 'controllerClass', 'baseControllerClass'], 'match', 'pattern' => '/^[\\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], [['modelClass'], 'validateClass', 'params' => ['extends' => BaseActiveRecord::className()]], [['baseControllerClass'], 'validateClass', 'params' => ['extends' => Controller::className()]], [['controllerClass'], 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'], [['controllerClass'], 'match', 'pattern' => '/(^|\\\\)[A-Z][^\\\\]+Controller$/', 'message' => 'Controller class name must start with an uppercase letter.'], [['controllerClass'], 'validateNewClass'], [['modelClass'], 'validateModelClass'], [['controllerActionIndexEnabled', 'controllerActionCreateEnabled', 'controllerActionUpdateEnabled', 'controllerActionDeleteEnabled', 'controllerActionApproveEnabled', 'controllerActionDisapproveEnabled', 'controllerActionDeleteSelectedEnabled', 'controllerActionApproveSelectedEnabled', 'controllerActionDisapproveSelectedEnabled', 'controllerActionDetailViewEnabled'], 'boolean', 'on' => 'controller'], ['headingTitle', 'string', 'on' => 'index'], ['columns', 'safe', 'on' => 'index'], [['toolbarCreateButtonEnabled', 'toolbarApproveButtonEnabled', 'toolbarDisapproveButtonEnabled', 'toolbarDeleteButtonEnabled', 'toolbarRefreshButtonEnabled', 'checkboxColumnEnabled', 'actionColumnEnabled', 'actionApproveEnabled', 'actionDisapproveEnabled', 'idColumnEnabled', 'createdColumnEnabled', 'updatedColumnEnabled', 'detailColumnEnabled', 'pjax', 'condensed', 'hover', 'showPageSummary', 'resizableColumns', 'floatHeader', 'perfectScrollbar', 'toggleData', 'showHeader', 'bootstrap', 'bordered', 'striped'], 'boolean', 'on' => 'index'], ['columns', 'safe', 'on' => 'index'], ['columns', 'safe', 'on' => 'index']]); }
/** * @inheritdoc */ public function rules() { return [[['template'], 'required', 'message' => 'A code template must be selected.'], [['template'], 'validateTemplate'], [['controllerClass', 'modelClass', 'baseControllerClass', 'baseModelClass'], 'filter', 'filter' => 'trim'], [['modelClass', 'controllerClass', 'baseControllerClass', 'baseModelClass'], 'required'], [['modelClass', 'controllerClass', 'baseControllerClass', 'baseModelClass'], 'match', 'pattern' => '/^[\\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], [['baseModelClass'], 'validateClass', 'params' => ['extends' => BaseActiveRecord::className()]], [['baseControllerClass'], 'validateClass', 'params' => ['extends' => Controller::className()]], [['controllerClass'], 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'], [['controllerClass'], 'match', 'pattern' => '/(^|\\\\)[A-Z][^\\\\]+Controller$/', 'message' => 'Controller class name must start with an uppercase letter.'], [['controllerClass', 'modelClass'], 'validateNewClass'], [['baseModelClass'], 'validateBaseModelClass'], [['enableI18N', 'isImage'], 'boolean'], [['messageCategory'], 'validateMessageCategory', 'skipOnEmpty' => false], ['viewPath', 'safe']]; }
/** * @inheritdoc */ public static function populateRecord($record, $row) { $columns = static::getIndexSchema()->columns; foreach ($row as $name => $value) { if (isset($columns[$name])) { if ($columns[$name]->isMva) { $mvaValue = explode(',', $value); $row[$name] = array_map([$columns[$name], 'phpTypecast'], $mvaValue); } else { $row[$name] = $columns[$name]->phpTypecast($value); } } } parent::populateRecord($record, $row); }
/** * Tests that a `SimpleWorkflowBehavior` behavior is attached to the object passed as argument. * * This method returns FALSE if $model is not an instance of BaseActiveRecord (has SimpleWorkflowBehavior can only be attached * to instances of this class) or if none of its attached behaviors is a or inherit from SimpleWorkflowBehavior. * * @param BaseActiveRecord $model the model to test. * @return boolean TRUE if at least one SimpleWorkflowBehavior behavior is attached to $model, FALSE otherwise */ public static function isAttachedTo($model) { if ($model instanceof yii\base\Component) { foreach ($model->getBehaviors() as $behavior) { if ($behavior instanceof SimpleWorkflowBehavior) { return true; } } } else { throw new WorkflowException('Invalid argument type : $model must be a BaseActiveRecord'); } return false; }
/** * {@inheritdoc} * @return ActiveQuery the relational query object. */ public function hasMany($class, $link) { return parent::hasMany($class, $link); }
/** * @inheritdoc */ public static function populateRecord($record, $row) { $columns = static::getIndexSchema()->columns; foreach ($row as $name => $value) { if (isset($columns[$name]) && $columns[$name]->isMva) { $row[$name] = explode(',', $value); } } parent::populateRecord($record, $row); }
/** * {@inheritdoc} * @return ActiveQueryInterface|ActiveQuery the relational query object. If the relation does not exist * and `$throwException` is false, null will be returned. */ public function getRelation($name, $throwException = true) { return parent::getRelation($name, $throwException); }
/** * Displays the status for the model passed as argument. * * This method assumes that the status includes a metadata value called 'labelTemplate' that contains * the HTML template of the rendering status. In this template the string '{label}' will be replaced by the * status label. * * Example : * 'status' => [ * 'draft' => [ * 'label' => 'Draft', * 'transition' => ['ready' ], * 'metadata' => [ * 'labelTemplate' => '<span class="label label-default">{label}</span>' * ] * ], * * @param BaseActiveRecord $model * @return string|NULL the HTML rendered status or null if not labelTemplate is found */ public static function renderLabel($model) { if ($model->hasWorkflowStatus()) { $labelTemplate = $model->getWorkflowStatus()->getMetadata('labelTemplate'); if (!empty($labelTemplate)) { return strtr($labelTemplate, ['{label}' => $model->getWorkflowStatus()->getLabel()]); } } return null; }
/** * Populates an active record object using a row of data from the database/storage. * * This is an internal method meant to be called to create active record objects after * fetching data from the database. It is mainly used by [[ActiveQuery]] to populate * the query results into active records. * * When calling this method manually you should call [[afterFind()]] on the created * record to trigger the [[EVENT_AFTER_FIND|afterFind Event]]. * * @param BaseActiveRecord $record the record to be populated. In most cases this will be an instance * created by [[instantiate()]] beforehand. * @param array $row attribute values (name => value) */ public static function populateRecord($record, $row) { foreach ($record->attributes() as $name) { $record->setAttribute($name, isset($row[$name]) ? $row[$name] : null); if (isset($row[$name]) && $record->canSetProperty($name)) { $record->{$name} = $row[$name]; } } $record->setOldAttributes($record->attributes); }
/** * Populates an active record object using a row of data from the database/storage. * * This is an internal method meant to be called to create active record objects after * fetching data from the database. It is mainly used by [[ActiveQuery]] to populate * the query results into active records. * * When calling this method manually you should call [[afterFind()]] on the created * record to trigger the [[EVENT_AFTER_FIND|afterFind Event]]. * * @param static $record The record to be populated. In most cases this will be an instance * created by [[instantiate()]] beforehand. * @param array $row Attribute values (name => value). * @return void */ public static function populateRecord($record, $row) { $responseData = ArrayHelper::getValue($row, Query::RESPONSE_KEY_PARAM); unset($row[Query::RESPONSE_KEY_PARAM]); parent::populateRecord($record, $row); if (!empty($responseData)) { $record->_responseData = $responseData; } }
public function testAfterFind() { /** @var \yii\db\ActiveRecordInterface $customerClass */ $customerClass = $this->getCustomerClass(); /** @var BaseActiveRecord $orderClass */ $orderClass = $this->getOrderClass(); /** @var TestCase|ActiveRecordTestTrait $this */ $afterFindCalls = []; Event::on(BaseActiveRecord::className(), BaseActiveRecord::EVENT_AFTER_FIND, function ($event) use(&$afterFindCalls) { /** @var BaseActiveRecord $ar */ $ar = $event->sender; $afterFindCalls[] = [get_class($ar), $ar->getIsNewRecord(), $ar->getPrimaryKey(), $ar->isRelationPopulated('orders')]; }); $customer = $customerClass::findOne(1); $this->assertNotNull($customer); $this->assertEquals([[$customerClass, false, 1, false]], $afterFindCalls); $afterFindCalls = []; $customer = $customerClass::find()->where(['id' => 1])->one(); $this->assertNotNull($customer); $this->assertEquals([[$customerClass, false, 1, false]], $afterFindCalls); $afterFindCalls = []; $customer = $customerClass::find()->where(['id' => 1])->all(); $this->assertNotNull($customer); $this->assertEquals([[$customerClass, false, 1, false]], $afterFindCalls); $afterFindCalls = []; $customer = $customerClass::find()->where(['id' => 1])->with('orders')->all(); $this->assertNotNull($customer); $this->assertEquals([[$this->getOrderClass(), false, 1, false], [$customerClass, false, 1, true]], $afterFindCalls); $afterFindCalls = []; if ($this instanceof \yiiunit\extensions\redis\ActiveRecordTest) { // TODO redis does not support orderBy() yet $customer = $customerClass::find()->where(['id' => [1, 2]])->with('orders')->all(); } else { // orderBy is needed to avoid random test failure $customer = $customerClass::find()->where(['id' => [1, 2]])->with('orders')->orderBy('name')->all(); } $this->assertNotNull($customer); $this->assertEquals([[$orderClass, false, 1, false], [$orderClass, false, 2, false], [$orderClass, false, 3, false], [$customerClass, false, 1, true], [$customerClass, false, 2, true]], $afterFindCalls); $afterFindCalls = []; Event::off(BaseActiveRecord::className(), BaseActiveRecord::EVENT_AFTER_FIND); }
/** * Magic calls for populated AR object. * * @param string $name the method name * @param array $params method parameters * @throws UnknownMethodException when calling unknown method * @return mixed the method return value */ public function __call($name, $params) { if ($this->_internalDoc instanceof \XSDocument) { try { return call_user_func_array(array($this->_internalDoc, $name), $params); } catch (\Exception $e) { } } return parent::__call($name, $params); }
/** * Fills up default attributes for the variation model. * @param BaseActiveRecord $variationModel model instance. * @throws InvalidConfigException on invalid configuration. */ private function fillUpVariationModelDefaults($variationModel) { if ($this->variationModelDefaultAttributes === null) { $variationsRelation = $this->owner->getRelation($this->variationsRelation); if (isset($variationsRelation->where)) { foreach ((array) $variationsRelation->where as $attribute => $value) { if ($variationModel->hasAttribute($attribute)) { $variationModel->{$attribute} = $value; } } } return; } if (is_callable($this->variationModelDefaultAttributes, true)) { call_user_func($this->variationModelDefaultAttributes, $variationModel); return; } if (!is_array($this->variationModelDefaultAttributes)) { throw new InvalidConfigException('"' . get_class($this) . '::variationModelDefaultAttributes" must be a valid callable or an array.'); } foreach ($this->variationModelDefaultAttributes as $attribute => $value) { $variationModel->{$attribute} = $value; } }
public function afterDelete($event) { $this->_relation->delete(); }
/** * @inheritdoc */ public function rules() { return array_merge(\yii\gii\Generator::rules(), [[['moduleID', 'controllerID', 'modelClass', 'searchModelClass', 'baseControllerClass'], 'filter', 'filter' => 'trim'], [['modelClass', 'controllerID', 'baseControllerClass', 'indexWidgetType'], 'required'], [['searchModelClass'], 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'], [['modelClass', 'baseControllerClass', 'searchModelClass'], 'match', 'pattern' => '/^[\\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], [['modelClass'], 'validateClass', 'params' => ['extends' => BaseActiveRecord::className()]], [['baseControllerClass'], 'validateClass', 'params' => ['extends' => Controller::className()]], [['controllerID'], 'match', 'pattern' => '/^[a-z][a-z0-9\\-\\/]*$/', 'message' => 'Only a-z, 0-9, dashes (-) and slashes (/) are allowed.'], [['searchModelClass'], 'validateNewClass'], [['controllerClass'], 'filter', 'filter' => function () { return $this->getControllerClass(); }, 'skipOnEmpty' => false], [['indexWidgetType'], 'in', 'range' => ['grid', 'list']], [['modelClass'], 'validateModelClass'], [['moduleID'], 'validateModuleID'], [['enableI18N'], 'boolean'], [['messageCategory'], 'validateMessageCategory', 'skipOnEmpty' => false]]); }
/** * @inheritdoc */ public function toArray(array $fields = [], array $expand = [], $recursive = true) { $data = parent::toArray($fields, $expand, false); if (!$recursive) { return $data; } return $this->toArrayInternal($data); }
/** * @inheritdoc */ public static function populateRecord($record, $row) { $columns = static::getTableSchema()->columns; foreach ($row as $name => $value) { if (isset($columns[$name])) { $row[$name] = $columns[$name]->phpTypecast($value); } } parent::populateRecord($record, $row); }
public function testAfterFindGet() { /* @var $customerClass BaseActiveRecord */ $customerClass = $this->getCustomerClass(); $afterFindCalls = []; Event::on(BaseActiveRecord::className(), BaseActiveRecord::EVENT_AFTER_FIND, function ($event) use(&$afterFindCalls) { /* @var $ar BaseActiveRecord */ $ar = $event->sender; $afterFindCalls[] = [get_class($ar), $ar->getIsNewRecord(), $ar->getPrimaryKey(), $ar->isRelationPopulated('orders')]; }); $customer = Customer::get(1); $this->assertNotNull($customer); $this->assertEquals([[$customerClass, false, 1, false]], $afterFindCalls); $afterFindCalls = []; $customer = Customer::mget([1, 2]); $this->assertNotNull($customer); $this->assertEquals([[$customerClass, false, 1, false], [$customerClass, false, 2, false]], $afterFindCalls); $afterFindCalls = []; Event::off(BaseActiveRecord::className(), BaseActiveRecord::EVENT_AFTER_FIND); }