Browse Source

Fix for Yii 2.0.14 compatibility. Has many relations were not saved. (#25)

tags/1.4.0
Alban Jubert 7 years ago
parent
commit
2acfceb8fa
  1. 3
      CHANGELOG.md
  2. 123
      src/SaveRelationsBehavior.php
  3. 12
      tests/SaveRelationsBehaviorTest.php

3
CHANGELOG.md

@ -1,6 +1,9 @@
# Yii2 Active Record Save Relations Behavior Change Log # Yii2 Active Record Save Relations Behavior Change Log
## [1.4.0] ## [1.4.0]
### Fixed
- Bug #25: Fix for Yii 2.0.14 compatibility. Has many relations were not saved. (thx @SanChes-tanker)
### Added ### Added
- Enh #15: Allow to save extra columns to junction table (thx @sspat) - Enh #15: Allow to save extra columns to junction table (thx @sspat)

123
src/SaveRelationsBehavior.php

@ -24,15 +24,20 @@ class SaveRelationsBehavior extends Behavior
public $relations = []; public $relations = [];
private $_relations = []; private $_relations = [];
private $_oldRelationValue = []; private $_oldRelationValue = []; // Store initial relations value
private $_newRelationValue = []; // Store update relations value
private $_relationsSaveStarted = false; private $_relationsSaveStarted = false;
private $_transaction; private $_transaction;
private $_relationsScenario = []; private $_relationsScenario = [];
private $_relationsExtraColumns = []; private $_relationsExtraColumns = [];
//private $_relationsCascadeDelete = []; //TODO //private $_relationsCascadeDelete = []; //TODO
/**
* @inheritdoc
*/
public function init() public function init()
{ {
parent::init(); parent::init();
@ -55,6 +60,9 @@ class SaveRelationsBehavior extends Behavior
} }
} }
/**
* @inheritdoc
*/
public function events() public function events()
{ {
return [ 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 * Set the named multiple relation with the given value
* @param $name * @param $name
* @param $value * @param $value
@ -159,6 +153,7 @@ class SaveRelationsBehavior extends Behavior
$newRelations[] = $this->processModelAsArray($entry, $relation); $newRelations[] = $this->processModelAsArray($entry, $relation);
} }
} }
$this->_newRelationValue[$name] = $newRelations;
$this->owner->populateRelation($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. * 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 * For `hasOne()` relations, set the according foreign keys of the owner model to be able to validate it
* @param ModelEvent $event * @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. * Link the related models.
* If the models have not been changed, nothing will be done. * 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. * 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 */ /** @var BaseActiveRecord $model */
$model = $this->owner; $model = $this->owner;
$this->_relationsSaveStarted = true; $this->_relationsSaveStarted = true;
// Populate relations with updated values
foreach ($this->_newRelationValue as $name => $value) {
$this->owner->populateRelation($name, $value);
}
try { try {
foreach ($this->_relations as $relationName) { foreach ($this->_relations as $relationName) {
if (array_key_exists($relationName, $this->_oldRelationValue)) { // Relation was not set, do nothing... 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. * Return array of columns to save to the junction table for a related model having a many-to-many relation.
* @param string $relationName * @param string $relationName
* @param BaseActiveRecord $model * @param BaseActiveRecord $model
@ -529,27 +551,22 @@ class SaveRelationsBehavior extends Behavior
} }
/** /**
* Attach errors to owner relational attributes * Populates relations with input data
* @param $relationModel * @param array $data
* @param $owner
* @param $relationName
* @param $pettyRelationName
* @return array
*/ */
private function _addError($relationModel, $owner, $relationName, $pettyRelationName) public function loadRelations($data)
{ {
foreach ($relationModel->errors as $attributeErrors) { /** @var BaseActiveRecord $model */
foreach ($attributeErrors as $error) { $model = $this->owner;
$owner->addError($relationName, "{$pettyRelationName}: {$error}"); 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__);
} }
} }
} }

12
tests/SaveRelationsBehaviorTest.php

@ -336,7 +336,7 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase
public function testSaveNewHasManyRelationWithCompositeFksShouldSucceed() public function testSaveNewHasManyRelationWithCompositeFksShouldSucceed()
{ {
$project = Project::findOne(1); $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 = new Link();
$link->language = 'fr'; $link->language = 'fr';
$link->name = 'windows10'; $link->name = 'windows10';
@ -370,7 +370,7 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase
public function testSaveNewHasManyRelationWithCompositeFksAsArrayShouldSucceed() public function testSaveNewHasManyRelationWithCompositeFksAsArrayShouldSucceed()
{ {
$project = Project::findOne(1); $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 = [ $links = [
['language' => 'fr', 'name' => 'windows10', 'link' => 'https://www.microsoft.com/fr-fr/windows/features'], ['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'] ['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() public function testSaveUpdatedHasManyRelationWithCompositeFksAsArrayShouldSucceed()
{ {
$project = Project::findOne(1); $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 = $project->links;
$links[1]->link = "http://www.otherlink.com/"; $links[1]->link = "http://www.otherlink.com/";
$project->links = $links; $project->links = $links;
@ -436,7 +436,7 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase
$users = User::findAll([1, 3]); $users = User::findAll([1, 3]);
$this->assertCount(0, $project->users, 'Project should have 0 users before save'); $this->assertCount(0, $project->users, 'Project should have 0 users before save');
$project->users = $users; // Add users $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->assertTrue($project->save(), 'Project could not be saved');
$this->assertCount(2, $project->users, 'Project should have 2 users after save'); $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'); $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(); $project = new Project();
$user = User::findOne(1); $user = User::findOne(1);
$project->users = $user; $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() public function testAssignSingleEmptyObjectToHasManyRelationShouldSucceed()
@ -505,7 +505,7 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase
$project = new Project(); $project = new Project();
$user = User::findOne(1); $user = User::findOne(1);
$project->users = null; $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() public function testChangeHasOneRelationWithAnotherObject()

Loading…
Cancel
Save