diff --git a/CHANGELOG.md b/CHANGELOG.md index 35a4117..093e434 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Yii2 Active Record Save Relations Behavior Change Log ## [1.4.0] +### Fixed +- Bug #25: Fix for Yii 2.0.14 compatibility. Has many relations were not saved. (thx @SanChes-tanker) + ### Added - Enh #15: Allow to save extra columns to junction table (thx @sspat) diff --git a/src/SaveRelationsBehavior.php b/src/SaveRelationsBehavior.php index f7c7dea..53a5c8c 100644 --- a/src/SaveRelationsBehavior.php +++ b/src/SaveRelationsBehavior.php @@ -24,15 +24,20 @@ class SaveRelationsBehavior extends Behavior public $relations = []; private $_relations = []; - private $_oldRelationValue = []; + private $_oldRelationValue = []; // Store initial relations value + private $_newRelationValue = []; // Store update relations value private $_relationsSaveStarted = false; private $_transaction; + private $_relationsScenario = []; private $_relationsExtraColumns = []; //private $_relationsCascadeDelete = []; //TODO + /** + * @inheritdoc + */ public function init() { parent::init(); @@ -55,6 +60,9 @@ class SaveRelationsBehavior extends Behavior } } + /** + * @inheritdoc + */ public function events() { return [ @@ -122,20 +130,6 @@ class SaveRelationsBehavior extends Behavior } /** - * Set the named single relation with the given value - * @param $name - * @param $value - */ - protected function setSingleRelation($name, $value) - { - $relation = $this->owner->getRelation($name); - if (!($value instanceof $relation->modelClass)) { - $value = $this->processModelAsArray($value, $relation); - } - $this->owner->populateRelation($name, $value); - } - - /** * Set the named multiple relation with the given value * @param $name * @param $value @@ -159,6 +153,7 @@ class SaveRelationsBehavior extends Behavior $newRelations[] = $this->processModelAsArray($entry, $relation); } } + $this->_newRelationValue[$name] = $newRelations; $this->owner->populateRelation($name, $newRelations); } @@ -222,6 +217,21 @@ class SaveRelationsBehavior extends Behavior } /** + * Set the named single relation with the given value + * @param $name + * @param $value + */ + protected function setSingleRelation($name, $value) + { + $relation = $this->owner->getRelation($name); + if (!($value instanceof $relation->modelClass)) { + $value = $this->processModelAsArray($value, $relation); + } + $this->_newRelationValue[$name] = $value; + $this->owner->populateRelation($name, $value); + } + + /** * Before the owner model validation, save related models. * For `hasOne()` relations, set the according foreign keys of the owner model to be able to validate it * @param ModelEvent $event @@ -349,6 +359,34 @@ class SaveRelationsBehavior extends Behavior } /** + * Attach errors to owner relational attributes + * @param $relationModel + * @param $owner + * @param $relationName + * @param $pettyRelationName + */ + private function _addError($relationModel, $owner, $relationName, $pettyRelationName) + { + foreach ($relationModel->errors as $attributeErrors) { + foreach ($attributeErrors as $error) { + $owner->addError($relationName, "{$pettyRelationName}: {$error}"); + } + } + } + + /** + * Rollback transaction if any + * @throws DbException + */ + private function _rollback() + { + if (($this->_transaction instanceof Transaction) && $this->_transaction->isActive) { + $this->_transaction->rollBack(); // If anything goes wrong, transaction will be rolled back + Yii::info("Rolling back", __METHOD__); + } + } + + /** * Link the related models. * If the models have not been changed, nothing will be done. * Related records will be linked to the owner model using the BaseActiveRecord `link()` method. @@ -359,6 +397,10 @@ class SaveRelationsBehavior extends Behavior /** @var BaseActiveRecord $model */ $model = $this->owner; $this->_relationsSaveStarted = true; + // Populate relations with updated values + foreach ($this->_newRelationValue as $name => $value) { + $this->owner->populateRelation($name, $value); + } try { foreach ($this->_relations as $relationName) { if (array_key_exists($relationName, $this->_oldRelationValue)) { // Relation was not set, do nothing... @@ -456,26 +498,6 @@ class SaveRelationsBehavior extends Behavior } /** - * Populates relations with input data - * @param array $data - */ - public function loadRelations($data) - { - /** @var BaseActiveRecord $model */ - $model = $this->owner; - foreach ($this->_relations as $relationName) { - $relation = $model->getRelation($relationName); - $modelClass = $relation->modelClass; - /** @var BaseActiveRecord $relationalModel */ - $relationalModel = new $modelClass; - $formName = $relationalModel->formName(); - if (array_key_exists($formName, $data)) { - $model->{$relationName} = $data[$formName]; - } - } - } - - /** * Return array of columns to save to the junction table for a related model having a many-to-many relation. * @param string $relationName * @param BaseActiveRecord $model @@ -529,27 +551,22 @@ class SaveRelationsBehavior extends Behavior } /** - * Attach errors to owner relational attributes - * @param $relationModel - * @param $owner - * @param $relationName - * @param $pettyRelationName - * @return array + * Populates relations with input data + * @param array $data */ - private function _addError($relationModel, $owner, $relationName, $pettyRelationName) + public function loadRelations($data) { - foreach ($relationModel->errors as $attributeErrors) { - foreach ($attributeErrors as $error) { - $owner->addError($relationName, "{$pettyRelationName}: {$error}"); + /** @var BaseActiveRecord $model */ + $model = $this->owner; + foreach ($this->_relations as $relationName) { + $relation = $model->getRelation($relationName); + $modelClass = $relation->modelClass; + /** @var BaseActiveRecord $relationalModel */ + $relationalModel = new $modelClass; + $formName = $relationalModel->formName(); + if (array_key_exists($formName, $data)) { + $model->{$relationName} = $data[$formName]; } } } - - private function _rollback() - { - if (($this->_transaction instanceof Transaction) && $this->_transaction->isActive) { - $this->_transaction->rollBack(); // If anything goes wrong, transaction will be rolled back - Yii::info("Rolling back", __METHOD__); - } - } } diff --git a/tests/SaveRelationsBehaviorTest.php b/tests/SaveRelationsBehaviorTest.php index bb759ea..9641440 100644 --- a/tests/SaveRelationsBehaviorTest.php +++ b/tests/SaveRelationsBehaviorTest.php @@ -336,7 +336,7 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase public function testSaveNewHasManyRelationWithCompositeFksShouldSucceed() { $project = Project::findOne(1); - $this->assertEquals(2, count($project->links), 'Project should have 2 links before save'); + $this->assertCount(2, $project->links, 'Project should have 2 links before save'); $link = new Link(); $link->language = 'fr'; $link->name = 'windows10'; @@ -370,7 +370,7 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase public function testSaveNewHasManyRelationWithCompositeFksAsArrayShouldSucceed() { $project = Project::findOne(1); - $this->assertEquals(2, count($project->links), 'Project should have 2 links before save'); + $this->assertCount(2, $project->links, 'Project should have 2 links before save'); $links = [ ['language' => 'fr', 'name' => 'windows10', 'link' => 'https://www.microsoft.com/fr-fr/windows/features'], ['language' => 'en', 'name' => 'windows10', 'link' => 'https://www.microsoft.com/en-us/windows/features'] @@ -394,7 +394,7 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase public function testSaveUpdatedHasManyRelationWithCompositeFksAsArrayShouldSucceed() { $project = Project::findOne(1); - $this->assertEquals(2, count($project->links), 'Project should have 2 links before save'); + $this->assertCount(2, $project->links, 'Project should have 2 links before save'); $links = $project->links; $links[1]->link = "http://www.otherlink.com/"; $project->links = $links; @@ -436,7 +436,7 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase $users = User::findAll([1, 3]); $this->assertCount(0, $project->users, 'Project should have 0 users before save'); $project->users = $users; // Add users - $this->assertEquals(2, count($project->users), 'Project should have 2 users after assignment'); + $this->assertCount(2, $project->users, 'Project should have 2 users after assignment'); $this->assertTrue($project->save(), 'Project could not be saved'); $this->assertCount(2, $project->users, 'Project should have 2 users after save'); $this->assertEquals(2, $project->company_id, 'Company ID is not the one expected'); @@ -497,7 +497,7 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase $project = new Project(); $user = User::findOne(1); $project->users = $user; - $this->assertEquals(1, count($project->users), 'Project should have 1 users after assignment'); + $this->assertCount(1, $project->users, 'Project should have 1 users after assignment'); } public function testAssignSingleEmptyObjectToHasManyRelationShouldSucceed() @@ -505,7 +505,7 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase $project = new Project(); $user = User::findOne(1); $project->users = null; - $this->assertEquals(0, count($project->users), 'Project should have 0 users after assignment'); + $this->assertCount(0, $project->users, 'Project should have 0 users after assignment'); } public function testChangeHasOneRelationWithAnotherObject()