From 3aa286f7c9f8399b3d4ba93c502878925f1be7d5 Mon Sep 17 00:00:00 2001 From: Alban Jubert Date: Fri, 19 Feb 2021 18:22:39 +0100 Subject: [PATCH] Prepare v2.0.0 release --- CHANGELOG.md | 12 ++++++++-- README.md | 41 ++++++++++++++++++++++------------ src/SaveRelationsBehavior.php | 10 +++------ tests/SaveRelationsBehaviorTest.php | 15 ++++++++++++- tests/models/Link.php | 2 +- tests/models/Project.php | 2 +- tests/models/ProjectNoTransactions.php | 3 ++- tests/models/UserProfile.php | 1 + 8 files changed, 59 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e0d09e..924199b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,18 @@ # Yii2 Active Record Save Relations Behavior Change Log -## [1.8.0] Unreleased +## [2.0.0] Unreleased + +> This release contains breaking changes. +> Update your models rules to include relations attributes as `safe`. + +### Changed + +- Relations now honor the `safe` validation rule (thx @artemryzhov) ### Fixed -- Fix #57: Better nested models support (thx @malinink) +- Fix #57: Fix nested relations save, on dirty attributes not present (thx @malinink) +- Fix #55: Prevent model double validation when not necessary (thx @leandrogehlen) ## [1.7.2] diff --git a/README.md b/README.md index 0f35a38..ddd412c 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ Yii2 Active Record Save Relations Behavior ========================================== Automatically validate and save related Active Record models. -[![Latest Stable Version](https://poser.pugx.org/la-haute-societe/yii2-save-relations-behavior/v/stable)](https://packagist.org/packages/la-haute-societe/yii2-save-relations-behavior) -[![Total Downloads](https://poser.pugx.org/la-haute-societe/yii2-save-relations-behavior/downloads)](https://packagist.org/packages/la-haute-societe/yii2-save-relations-behavior) +[![Latest Stable Version](https://poser.pugx.org/la-haute-societe/yii2-save-relations-behavior/v/stable)](https://packagist.org/packages/la-haute-societe/yii2-save-relations-behavior) +[![Total Downloads](https://poser.pugx.org/la-haute-societe/yii2-save-relations-behavior/downloads)](https://packagist.org/packages/la-haute-societe/yii2-save-relations-behavior) [![Code Coverage](https://scrutinizer-ci.com/g/la-haute-societe/yii2-save-relations-behavior/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/la-haute-societe/yii2-save-relations-behavior/?branch=master) [![Build Status](https://scrutinizer-ci.com/g/la-haute-societe/yii2-save-relations-behavior/badges/build.png?b=master)](https://scrutinizer-ci.com/g/la-haute-societe/yii2-save-relations-behavior/build-status/master) -[![Latest Unstable Version](https://poser.pugx.org/la-haute-societe/yii2-save-relations-behavior/v/unstable)](https://packagist.org/packages/la-haute-societe/yii2-save-relations-behavior) +[![Latest Unstable Version](https://poser.pugx.org/la-haute-societe/yii2-save-relations-behavior/v/unstable)](https://packagist.org/packages/la-haute-societe/yii2-save-relations-behavior) [![License](https://poser.pugx.org/la-haute-societe/yii2-save-relations-behavior/license)](https://packagist.org/packages/la-haute-societe/yii2-save-relations-behavior) @@ -14,9 +14,10 @@ Features -------- - Both `hasMany()` and `hasOne()` relations are supported - Works with existing as well as new related models -- Composite primary keys are supported +- Compound primary keys are supported - Only pure Active Record API is used so it should work with any DB driver - As of 1.5.0 release, related records can now be deleted along with the main model +- ⚠️ As of 2.0.0 release, relations attributes now honor the `safe` validation rule Installation @@ -76,6 +77,15 @@ class Project extends \yii\db\ActiveRecord ]; } + public function rules() + { + return [ + [['name', 'company_id'], 'required'], + [['name'], 'unique', 'targetAttribute' => ['company_id', 'name']], + [['company', 'users', 'projectLinks', 'tags'], 'safe'] + ]; + } + public function transactions() { return [ @@ -109,7 +119,7 @@ class Project extends \yii\db\ActiveRecord { return $this->hasMany(User::className(), ['id' => 'user_id'])->via('ProjectUsers'); } - + /** * @return ActiveQuery */ @@ -129,6 +139,8 @@ class Project extends \yii\db\ActiveRecord ``` > Though not mandatory, it is highly recommended to activate the transactions for the owner model. +> ⚠️ Relations attributes has to be defined as `safe` in owner model validation rules in order to be saved. + Usage @@ -167,7 +179,6 @@ Attributes of the related model will be massively assigned using the `load() met > **Note:** > Only newly created or changed related models will be saved. - > See the PHPUnit tests for more examples. @@ -177,8 +188,7 @@ In a many-to-many relation involving a junction table additional column values c See the configuration section for examples. > **Note:** -> If junction table properties are configured for a relation the rows associated with the related models in the junction table will be deleted and inserted again on each saving -> to ensure that changes to the junction table properties are saved too. +> If junction table properties are configured for a relation, the rows associated with the related models in the junction table will be deleted and inserted again on each saving to ensure that changes to the junction table properties are saved too. Validation @@ -200,7 +210,7 @@ For instance, in the following configuration, the `links ` related records will 'relations' => ['company', 'users', 'links' => ['scenario' => Link::SOME_SCENARIO]] ], ]; - } + } ... ``` @@ -238,7 +248,7 @@ For example, related `projectLinks` records will automatically be deleted when t ... ``` -> **Note:** +> **Note:**. > Every records related to the main model as they are defined in their `ActiveQuery` statement will be deleted. @@ -274,12 +284,12 @@ $project = Project::findOne(1); $project->loadRelations(Yii::$app->request->post()); ``` -You can even further simplify the process by adding the `SaveRelationsTrait` to your model. -In that case, a call to the `load()` method will also automatically trigger a call to the `loadRelations()` method by using the same data, so you basically won't have to change your controllers. +You can even further simplify the process by adding the `SaveRelationsTrait` to your model. In that case, a call to the `load()` method will also automatically trigger a call to the `loadRelations()` method by using the same data, so you basically won't have to change your controllers. -The `relationKeyName` property can be used to decide how the relations data will be retrieved from the data parameter. +The `relationKeyName` property can be used to decide how the relations data will be retrieved from the data parameter. Possible constants values are: + * `SaveRelationsBehavior::RELATION_KEY_FORM_NAME` (default): the key name will be computed using the model [`formName()`](https://www.yiiframework.com/doc/api/2.0/yii-base-model#formName()-detail) method * `SaveRelationsBehavior::RELATION_KEY_RELATION_NAME`: the relation name as defined in the behavior declarations will be used @@ -288,10 +298,12 @@ Get old relations values ------------------------ To retrieve relations value prior to there most recent modification until the model is saved, the following methods can be used: + * `getOldRelation($name)`: Get a named relation old value. * `getOldRelations()`: Get an array of relations index by there name with there old values. -> **Notes** +> **Notes:** +> > * If a relation has not been modified yet, its initial value will be returned > * Only relations defined in the behavior parameters will be returned @@ -299,6 +311,7 @@ To retrieve relations value prior to there most recent modification until the mo Get dirty relations ------------------- To deal with dirty (modified) relations since the model was loaded, the following methods can be used: + * `getDirtyRelations()`: Get the relations that have been modified since they are loaded (name-value pairs) * `markRelationDirty($name)`: Mark a relation as dirty even if it's not been modified. diff --git a/src/SaveRelationsBehavior.php b/src/SaveRelationsBehavior.php index 88adca3..525a53c 100644 --- a/src/SaveRelationsBehavior.php +++ b/src/SaveRelationsBehavior.php @@ -127,7 +127,7 @@ class SaveRelationsBehavior extends Behavior { /** @var BaseActiveRecord $owner */ $owner = $this->owner; - if (in_array($name, $this->_relations)) { + if (in_array($name, $this->_relations) && in_array($name, $owner->safeAttributes())) { Yii::debug("Setting {$name} relation value", __METHOD__); /** @var ActiveQuery $relation */ $relation = $owner->getRelation($name); @@ -549,9 +549,7 @@ class SaveRelationsBehavior extends Behavior foreach ($owner->{$relationName} as $i => $relationModel) { if ($relationModel->isNewRecord) { if (!empty($relation->via)) { - if ($relationModel->validate()) { - $relationModel->save(); - } else { + if (!$relationModel->save()) { $this->_addError($relationModel, $owner, $relationName, self::prettyRelationName($relationName, $i)); throw new DbException('Related record ' . self::prettyRelationName($relationName, $i) . ' could not be saved.'); } @@ -562,9 +560,7 @@ class SaveRelationsBehavior extends Behavior $existingRecords[] = $relationModel; } if (count($relationModel->dirtyAttributes) || count($this->_newRelationValue)) { - if ($relationModel->validate()) { - $relationModel->save(); - } else { + if (!$relationModel->save()) { $this->_addError($relationModel, $owner, $relationName, self::prettyRelationName($relationName)); throw new DbException('Related record ' . self::prettyRelationName($relationName) . ' could not be saved.'); } diff --git a/tests/SaveRelationsBehaviorTest.php b/tests/SaveRelationsBehaviorTest.php index bd9b8d6..3f16d29 100644 --- a/tests/SaveRelationsBehaviorTest.php +++ b/tests/SaveRelationsBehaviorTest.php @@ -270,7 +270,7 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase public function testHasOneRelationsShouldNotBeSavedFail() { - $project = New Project(); + $project = new Project(); $company = new Company(); $company->name = "Oracle"; $project->company = $company; @@ -1028,4 +1028,17 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase $this->assertCount(1, $project->users); $this->assertEquals($project->users[0]->username, "Another user"); } + + public function testSaveExistingHasOneRelation() + { + $project = Project::findOne(1); + $project->company = [ + 'id' => 1, + 'name' => 'new company' + ]; + $this->assertTrue($project->save(), 'Project could not be saved ' . VarDumper::dumpAsString($project->getErrors())); + $project = Project::findOne(1); + $this->assertEquals('new company', $project->company->name); + $this->assertEquals(1, $project->company->id); + } } diff --git a/tests/models/Link.php b/tests/models/Link.php index da3a64a..42725f2 100644 --- a/tests/models/Link.php +++ b/tests/models/Link.php @@ -38,7 +38,7 @@ class Link extends \yii\db\ActiveRecord [['language', 'name', 'link'], 'required'], [['name'], 'unique', 'targetAttribute' => ['language', 'name']], [['link'], 'url', 'on' => [self::SCENARIO_FIRST]], - [['link_type_id'], 'safe'] + [['link_type_id', 'linkType'], 'safe'] ]; } diff --git a/tests/models/Project.php b/tests/models/Project.php index a381876..6f33a7d 100644 --- a/tests/models/Project.php +++ b/tests/models/Project.php @@ -53,7 +53,7 @@ class Project extends \yii\db\ActiveRecord return [ [['name', 'company_id'], 'required'], [['name'], 'unique', 'targetAttribute' => ['company_id', 'name']], - [['company', 'links', 'users'], 'safe'] + [['company', 'links', 'users', 'contacts', 'images', 'projectLinks', 'tags'], 'safe'] ]; } diff --git a/tests/models/ProjectNoTransactions.php b/tests/models/ProjectNoTransactions.php index bd94c90..afc182a 100644 --- a/tests/models/ProjectNoTransactions.php +++ b/tests/models/ProjectNoTransactions.php @@ -35,6 +35,7 @@ class ProjectNoTransactions extends \yii\db\ActiveRecord return [ [['name', 'company_id'], 'required'], [['name'], 'unique', 'targetAttribute' => ['company_id', 'name']], + [['company', 'users', 'links'], 'safe'] ]; } @@ -78,4 +79,4 @@ class ProjectNoTransactions extends \yii\db\ActiveRecord return $this->hasMany(Link::className(), ['language' => 'language', 'name' => 'name'])->via('projectLinks'); } -} \ No newline at end of file +} diff --git a/tests/models/UserProfile.php b/tests/models/UserProfile.php index d817149..d595291 100644 --- a/tests/models/UserProfile.php +++ b/tests/models/UserProfile.php @@ -41,6 +41,7 @@ class UserProfile extends \yii\db\ActiveRecord [['user_id'], 'unique'], [['user_id'], 'exist', 'skipOnError' => true, 'targetClass' => User::className(), 'targetAttribute' => ['user_id' => 'id']], [['agree'], 'required', 'on' => 'insert'], + ['user', 'safe'] ]; }