diff --git a/src/import/basic/Importer.php b/src/import/basic/Importer.php index cd7ecbc..5c91aae 100644 --- a/src/import/basic/Importer.php +++ b/src/import/basic/Importer.php @@ -47,13 +47,14 @@ protected function safeRun() foreach ($this->_models as $model) { $model->load(); $model->validate(); + $model->save(false); } - Yii::$app->db->transaction(function () { + /*Yii::$app->db->transaction(function () { foreach ($this->_models as $model) { $model->save(false); } - }); + });*/ $this->trigger(self::EVENT_RUN); } diff --git a/src/import/basic/Model.php b/src/import/basic/Model.php index 6b8d32c..f7afe58 100644 --- a/src/import/basic/Model.php +++ b/src/import/basic/Model.php @@ -2,7 +2,6 @@ namespace arogachev\excel\import\basic; -use arogachev\excel\components\Model as BaseModel; use arogachev\excel\import\DI; use arogachev\excel\import\exceptions\RowException; use PHPExcel_Worksheet_Row; @@ -10,8 +9,9 @@ /** * @property StandardModel $standardModel + * @property \yii\db\ActiveRecord $instance */ -class Model extends BaseModel +class Model extends Component { const EVENT_INIT = 'init'; @@ -21,9 +21,19 @@ class Model extends BaseModel public $row; /** - * @inheritdoc + * @var StandardModel + */ + protected $_standardModel; + + /** + * @var Attribute[] */ - protected static $attributeClassName = 'arogachev\excel\import\basic\Attribute'; + protected $_attributes = []; + + /** + * @var \yii\db\ActiveRecord + */ + protected $_instance; /** @@ -31,8 +41,7 @@ class Model extends BaseModel */ public function init() { - parent::init(); - + $this->initAttributes(); $this->trigger(self::EVENT_INIT); } @@ -50,6 +59,14 @@ protected function initAttributes() } } + /** + * @param array $config + */ + protected function initAttribute($config) + { + $this->_attributes[] = new Attribute($config); + } + public function load() { $this->loadExisting(); @@ -58,58 +75,42 @@ public function load() protected function loadExisting() { - if ($this->isPkEmpty()) { + $pk = []; + foreach ($this->_attributes as $attribute) { + //if (in_array($attribute->standardAttribute->name, $this->_instance->primaryKey())) { + if (in_array($attribute->standardAttribute->name, $attribute->standardAttribute->primaryKey())) { + $pk[$attribute->standardAttribute->name] = $attribute->value; + } + } + + if (!$pk) { return; } - /* @var $modelClass \yii\db\ActiveRecord */ - $modelClass = $this->_standardModel->className; - $model = $modelClass::findOne($this->getPkValues()); - if ($model) { - $this->_instance = $model; + if (count($pk) == 1 && !reset($pk)) { + return; } - } - /** - * @return Attribute[] - */ - protected function getPk() - { - $attributes = []; - foreach ($this->_attributes as $attribute) { - if (in_array($attribute->standardAttribute->name, $this->_instance->primaryKey())) { - $attributes[] = $attribute; + foreach ($pk as $value) { + if (!$value) { + throw new RowException($this->row, 'For updated model all primary key attributes must be specified.'); } } - return $attributes; - } + /* @var $modelClass \yii\db\ActiveRecord */ + $modelClass = $this->_standardModel->className; + $model = $modelClass::find()->where($pk)->andWhere($this->_standardModel->where)->one(); - /** - * @return array - */ - protected function getPkValues() - { - $values = []; - foreach ($this->getPk() as $attribute) { - $values[$attribute->standardAttribute->name] = $attribute->value; + if (!$model) { + //throw new RowException($this->row, 'Model for update not found.'); + $model = new $modelClass(); } - return $values; - } - - /** - * @return boolean - */ - protected function isPkEmpty() - { - foreach ($this->getPkValues() as $value) { - if ($value) { - return false; - } + foreach ($this->_standardModel->extendAttributesConfig as $name => $value) { + $model->{$name} = $value; } - return true; + $this->_instance = $model; } protected function assignMassively() @@ -139,4 +140,36 @@ public function save($runValidation = true) $this->_instance->save(false); } + + /** + * @return StandardModel + */ + public function getStandardModel() + { + return $this->_standardModel; + } + + /** + * @param StandardModel $value + */ + public function setStandardModel($value) + { + $this->_standardModel = $value; + } + + /** + * @return \yii\db\ActiveRecord + */ + public function getInstance() + { + return $this->_instance; + } + + /** + * @param \yii\db\ActiveRecord $value + */ + public function setInstance($value) + { + $this->_instance = $value; + } } diff --git a/src/import/basic/StandardAttribute.php b/src/import/basic/StandardAttribute.php index 9381108..422ab4f 100644 --- a/src/import/basic/StandardAttribute.php +++ b/src/import/basic/StandardAttribute.php @@ -2,26 +2,69 @@ namespace arogachev\excel\import\basic; -use arogachev\excel\components\StandardAttribute as BaseStandardAttribute; -use arogachev\excel\exceptions\StandardAttributeException; +use arogachev\excel\import\exceptions\StandardAttributeException; use yii\base\Object; use yii\helpers\ArrayHelper; /** * @property StandardModel $standardModel + * @property string $column */ -class StandardAttribute extends BaseStandardAttribute +class StandardAttribute extends Object { + /** + * @var string + */ + public $name; + + /** + * @var string + */ + public $label; + + /** + * @var array|callable + */ + public $valueReplacement; + + /** + * @var StandardModel + */ + protected $_standardModel; + + /** + * @var string + */ + protected $_column; + + /** * @inheritdoc */ - protected function validateName() + public function init() { - parent::validateName(); + $this->validateName(); + if ($this->_standardModel->extendStandardAttributes && !$this->label) { + $this->label = $this->_standardModel->instance->getAttributeLabel($this->name); + } + + $this->validateLabel(); + $this->validateValueReplacement(); + } + + /** + * @throws StandardAttributeException + */ + protected function validateName() + { $standardModel = $this->_standardModel; $model = $standardModel->instance; + if (!$this->name) { + throw new StandardAttributeException($this, 'Name is required.'); + } + if (!in_array($this->name, $model->attributes())) { throw new StandardAttributeException($this, 'Attribute not exist.'); } @@ -38,4 +81,65 @@ protected function validateName() throw new StandardAttributeException($this, 'Attribute is not allowed for import.'); } } + + /** + * @throws StandardAttributeException + */ + protected function validateLabel() + { + if ($this->standardModel->useAttributeLabels && !$this->label) { + throw new StandardAttributeException($this, 'Label not specified.'); + } + } + + /** + * @throws StandardAttributeException + */ + protected function validateValueReplacement() + { + if (!$this->valueReplacement || !is_array($this->valueReplacement)) { + return; + } + + if ($this->valueReplacement != array_unique($this->valueReplacement)) { + throw new StandardAttributeException($this, 'Value replacement list contains duplicate labels / values.'); + } + } + + /** + * @return StandardModel + */ + public function getStandardModel() + { + return $this->_standardModel; + } + + /** + * @param StandardModel $value + */ + public function setStandardModel($value) + { + $this->_standardModel = $value; + } + + /** + * @return string + */ + public function getColumn() + { + return $this->_column; + } + + /** + * @param string $value + */ + public function setColumn($value) + { + $this->_column = $value; + } + + public function primaryKey() + { + return $this->_standardModel->primaryKey(); + } } diff --git a/src/import/basic/StandardModel.php b/src/import/basic/StandardModel.php index dedad6b..3d4666a 100644 --- a/src/import/basic/StandardModel.php +++ b/src/import/basic/StandardModel.php @@ -2,7 +2,6 @@ namespace arogachev\excel\import\basic; -use arogachev\excel\components\StandardModel as BaseStandardModel; use arogachev\excel\import\exceptions\CellException; use arogachev\excel\import\exceptions\RowException; use PHPExcel_Worksheet_Row; @@ -13,21 +12,43 @@ use yii\helpers\ArrayHelper; /** + * @property ActiveRecord $instance * @property StandardAttribute[] $standardAttributes */ -class StandardModel extends BaseStandardModel +class StandardModel extends Object { const SCENARIO_IMPORT = 'import'; + /** + * @var string + */ + public $className; + + /** + * @var boolean + */ + public $useAttributeLabels = true; + + /** + * @var boolean + */ + public $extendStandardAttributes = true; + /** * @var boolean */ public $setScenario = false; /** - * @inheritdoc + * @var array */ - protected static $standardAttributeClassName = 'arogachev\excel\import\basic\StandardAttribute'; + public $standardAttributesConfig = []; + + public $extendAttributesConfig = []; + + public $where = []; + + public $primaryKey = false; /** * @var array @@ -37,14 +58,24 @@ class StandardModel extends BaseStandardModel ActiveRecord::EVENT_AFTER_FIND, ]; + /** + * @var ActiveRecord + */ + protected $_instance; + + /** + * @var StandardAttribute[] + */ + protected $_standardAttributes = []; + /** * @inheritdoc */ public function init() { - parent::init(); - + $this->initInstance(); + $this->initStandardAttributes(); $this->configureEventHandlers(); } @@ -53,13 +84,51 @@ public function init() */ protected function initInstance() { - parent::initInstance(); + if (!$this->className) { + throw new InvalidParamException('Class name is required for standard model.'); + } + + $this->_instance = new $this->className; if ($this->setScenario) { $this->_instance->scenario = self::SCENARIO_IMPORT; } } + /** + * @throws InvalidParamException + */ + protected function initStandardAttributes() + { + foreach ($this->standardAttributesConfig as $config) { + $this->initStandardAttribute($config); + } + + if ($this->extendStandardAttributes) { + $existingAttributes = ArrayHelper::getColumn($this->_standardAttributes, 'name'); + $missingAttributes = array_diff($this->getAllowedAttributes(), $existingAttributes); + + foreach ($missingAttributes as $attributeName) { + $this->initStandardAttribute(['name' => $attributeName]); + } + } + + $attributeLabels = ArrayHelper::getColumn($this->_standardAttributes, 'name', 'label'); + if ($attributeLabels != array_unique($attributeLabels)) { + throw new InvalidParamException("For standard model \"$this->className\" attribute labels are not unique."); + } + } + + /** + * @param array $config + */ + protected function initStandardAttribute($config) + { + $standardAttribute = new StandardAttribute(array_merge($config, ['standardModel' => $this])); + $propertyName = $this->useAttributeLabels ? 'label' : 'name'; + $this->_standardAttributes[$standardAttribute->{$propertyName}] = $standardAttribute; + } + protected function configureEventHandlers() { if ($this->setScenario) { @@ -128,4 +197,33 @@ public function parseAttributeNames($row) return !empty($attributeNames); } + + /** + * @return ActiveRecord + */ + public function getInstance() + { + return $this->_instance; + } + + /** + * @return StandardAttribute[] + */ + public function getStandardAttributes() + { + return $this->_standardAttributes; + } + + public function primaryKey() + { + if ($this->primaryKey) { + $keys = []; + if (is_string($this->primaryKey)) { + $keys = explode(',', $this->primaryKey); + //array_walk($keys, 'trim'); + } + return $keys; + } + return $this->_instance->primaryKey(); + } } diff --git a/src/import/exceptions/StandardAttributeException.php b/src/import/exceptions/StandardAttributeException.php new file mode 100644 index 0000000..711c034 --- /dev/null +++ b/src/import/exceptions/StandardAttributeException.php @@ -0,0 +1,33 @@ +name) { + $attributeName .= " $standardAttribute->name"; + } + + $modelClass = "{$standardAttribute->standardModel->className}"; + $message = "Invalid configuration for $attributeName in model $modelClass. $message"; + + parent::__construct($message, $code, $previous); + } + + /** + * @inheritdoc + */ + public function getName() + { + return 'Standard Attribute Exception'; + } +}