Skip to content
This repository has been archived by the owner on Jul 1, 2022. It is now read-only.

Commit

Permalink
Fix for Yii 2.0.14 compatibility. Has many relations were not saved. (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Alban Jubert committed Feb 22, 2018
1 parent 4bb47af commit 2acfceb
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 59 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
123 changes: 70 additions & 53 deletions src/SaveRelationsBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -55,6 +60,9 @@ public function init()
}
}

/**
* @inheritdoc
*/
public function events()
{
return [
Expand Down Expand Up @@ -121,20 +129,6 @@ public function __set($name, $value)
}
}

/**
* 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
Expand All @@ -159,6 +153,7 @@ protected function setMultipleRelation($name, $value)
$newRelations[] = $this->processModelAsArray($entry, $relation);
}
}
$this->_newRelationValue[$name] = $newRelations;
$this->owner->populateRelation($name, $newRelations);
}

Expand Down Expand Up @@ -221,6 +216,21 @@ protected function processModelAsArray($data, $relation)
return $relationModel;
}

/**
* 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
Expand Down Expand Up @@ -348,6 +358,34 @@ protected function validateRelationModel($pettyRelationName, $relationName, Base
}
}

/**
* 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.
Expand All @@ -359,6 +397,10 @@ public function afterSave()
/** @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...
Expand Down Expand Up @@ -455,26 +497,6 @@ function (BaseActiveRecord $model) {
}
}

/**
* 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
Expand Down Expand Up @@ -529,27 +551,22 @@ private function _computePkDiff($initialRelations, $updatedRelations, $forceSave
}

/**
* 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__);
}
}
}
12 changes: 6 additions & 6 deletions tests/SaveRelationsBehaviorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ public function testSaveNewHasManyRelationAsArrayShouldSucceed()
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';
Expand Down Expand Up @@ -370,7 +370,7 @@ public function testCreateHasManyRelationWithOneOfTheMissingKeyOfCompositeFk()
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']
Expand All @@ -394,7 +394,7 @@ public function testSaveNewHasManyRelationWithCompositeFksAsArrayShouldSucceed()
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;
Expand Down Expand Up @@ -436,7 +436,7 @@ public function testSaveMixedRelationsShouldSucceed()
$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');
Expand Down Expand Up @@ -497,15 +497,15 @@ public function testAssignSingleObjectToHasManyRelationShouldSucceed()
$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()
{
$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()
Expand Down

0 comments on commit 2acfceb

Please sign in to comment.