Browse Source

Added ability to delete related record after owner model delete

tags/1.5.0^2
Alban Jubert 7 years ago
parent
commit
08f3ff8bd1
  1. 47
      src/SaveRelationsBehavior.php
  2. 50
      tests/SaveRelationsBehaviorTest.php
  3. 20
      tests/bootstrap.php
  4. 5
      tests/models/Project.php
  5. 11
      tests/models/ProjectLink.php
  6. 2
      tests/models/User.php

47
src/SaveRelationsBehavior.php

@ -15,6 +15,7 @@ use yii\db\Exception as DbException;
use yii\db\Transaction; use yii\db\Transaction;
use yii\helpers\ArrayHelper; use yii\helpers\ArrayHelper;
use yii\helpers\Inflector; use yii\helpers\Inflector;
use yii\helpers\VarDumper;
/** /**
* This Active Record Behavior allows to validate and save the Model relations when the save() method is invoked. * This Active Record Behavior allows to validate and save the Model relations when the save() method is invoked.
@ -28,12 +29,14 @@ class SaveRelationsBehavior extends Behavior
private $_relations = []; private $_relations = [];
private $_oldRelationValue = []; // Store initial relations value private $_oldRelationValue = []; // Store initial relations value
private $_newRelationValue = []; // Store update relations value private $_newRelationValue = []; // Store update relations value
private $_relationsToDelete = [];
private $_relationsSaveStarted = false; private $_relationsSaveStarted = false;
private $_transaction; private $_transaction;
private $_relationsScenario = []; private $_relationsScenario = [];
private $_relationsExtraColumns = []; private $_relationsExtraColumns = [];
private $_relationsCascadeDelete = [];
/** /**
* @param $relationName * @param $relationName
@ -53,7 +56,7 @@ class SaveRelationsBehavior extends Behavior
public function init() public function init()
{ {
parent::init(); parent::init();
$allowedProperties = ['scenario', 'extraColumns']; $allowedProperties = ['scenario', 'extraColumns', 'cascadeDelete'];
foreach ($this->relations as $key => $value) { foreach ($this->relations as $key => $value) {
if (is_int($key)) { if (is_int($key)) {
$this->_relations[] = $value; $this->_relations[] = $value;
@ -81,6 +84,8 @@ class SaveRelationsBehavior extends Behavior
BaseActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate', BaseActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
BaseActiveRecord::EVENT_AFTER_INSERT => 'afterSave', BaseActiveRecord::EVENT_AFTER_INSERT => 'afterSave',
BaseActiveRecord::EVENT_AFTER_UPDATE => 'afterSave', BaseActiveRecord::EVENT_AFTER_UPDATE => 'afterSave',
BaseActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
BaseActiveRecord::EVENT_AFTER_DELETE => 'afterDelete'
]; ];
} }
@ -386,6 +391,46 @@ class SaveRelationsBehavior extends Behavior
} }
/** /**
* Get the list of owner model relations in order to be able to delete them after its deletion
*/
public function beforeDelete()
{
$model = $this->owner;
foreach ($this->_relationsCascadeDelete as $relationName => $params) {
if ($params === true) {
$relation = $model->getRelation($relationName);
if (!empty($model->{$relationName})) {
if ($relation->multiple === true) { // Has many relation
$this->_relationsToDelete = ArrayHelper::merge($this->_relationsToDelete, $model->{$relationName});
} else {
$this->_relationsToDelete[] = $model->{$relationName};
}
}
}
}
}
/**
* Delete related models marked as to be deleted
* @throws Exception
*/
public function afterDelete()
{
/** @var BaseActiveRecord $modelToDelete */
foreach ($this->_relationsToDelete as $modelToDelete) {
try {
if (!$modelToDelete->delete()) {
throw new DbException('Could not delete the related record: ' . $modelToDelete::className() . '(' . VarDumper::dumpAsString($modelToDelete->primaryKey) . ')');
}
} catch (Exception $e) {
Yii::warning(get_class($e) . ' was thrown while deleting related records during afterDelete event: ' . $e->getMessage(), __METHOD__);
$this->_rollback();
throw $e;
}
}
}
/**
* 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

50
tests/SaveRelationsBehaviorTest.php

@ -8,6 +8,7 @@ use tests\models\DummyModel;
use tests\models\DummyModelParent; use tests\models\DummyModelParent;
use tests\models\Link; use tests\models\Link;
use tests\models\Project; use tests\models\Project;
use tests\models\ProjectLink;
use tests\models\ProjectNoTransactions; use tests\models\ProjectNoTransactions;
use tests\models\Tag; use tests\models\Tag;
use tests\models\User; use tests\models\User;
@ -142,6 +143,13 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase
[4, 'Jonathan Ive', 1] [4, 'Jonathan Ive', 1]
])->execute(); ])->execute();
$db->createCommand()->batchInsert('user_profile', ['user_id', 'bio'], [
[1, 'Steven Paul Jobs (February 24, 1955 – October 5, 2011) was an American entrepreneur, business magnate, inventor, and industrial designer. He was the chairman, chief executive officer (CEO), and co-founder of Apple Inc.; CEO and majority shareholder of Pixar; a member of The Walt Disney Company\'s board of directors following its acquisition of Pixar; and the founder, chairman, and CEO of NeXT.'],
[2, 'William Henry Gates III (born October 28, 1955) is an American business magnate, investor, author, philanthropist, and co-founder of the Microsoft Corporation along with Paul Allen.'],
[3, 'Timothy Donald Cook (born November 1, 1960) is an American business executive, industrial engineer, and developer. Cook is the Chief Executive Officer of Apple Inc., previously serving as the company\'s Chief Operating Officer, under its founder Steve Jobs.'],
[4, 'Sir Jonathan Paul "Jony" Ive, KBE (born 27 February 1967), is an English industrial designer who is currently the chief design officer (CDO) of Apple and chancellor of the Royal College of Art in London.']
])->execute();
$db->createCommand()->batchInsert('project', ['id', 'name', 'company_id'], [ $db->createCommand()->batchInsert('project', ['id', 'name', 'company_id'], [
[1, 'Mac OS X', 1], [1, 'Mac OS X', 1],
[2, 'Windows 10', 2] [2, 'Windows 10', 2]
@ -725,4 +733,46 @@ class SaveRelationsBehaviorTest extends \PHPUnit_Framework_TestCase
$user->userProfile->bio = 'Lawrence Edward Page (born March 26, 1973) is an American computer scientist and Internet entrepreneur who co-founded Google with Sergey Brin.'; $user->userProfile->bio = 'Lawrence Edward Page (born March 26, 1973) is an American computer scientist and Internet entrepreneur who co-founded Google with Sergey Brin.';
$this->assertTrue($user->save(), 'User could not be saved'); $this->assertTrue($user->save(), 'User could not be saved');
} }
public function testDeleteRelatedHasOneShouldSucceed()
{
User::findOne(1)->delete();
$this->assertNull(UserProfile::findOne(1), 'Related user profile was not deleted');
$this->assertNotNull(UserProfile::findOne(2), 'Unrelated user profile was deleted');
}
public function testDeleteRelatedHasManyShouldSucceed()
{
Project::findOne(1)->delete();
$this->assertCount(0, ProjectLink::find()->where(['project_id' => 1])->all(), 'Related project links were not deleted');
}
public function testDeleteRelatedWithErrorShouldThrowAnException()
{
$this->setExpectedException('\yii\db\Exception');
$project = Project::findOne(1);
foreach ($project->projectLinks as $projectLink) {
$projectLink->blockDelete = true;
}
$this->assertFalse($project->delete(), 'Project could be deleted');
}
public function testSaveProjectWithCompanyWithUserShouldSucceed()
{
// Test for cascading save relations
$project = new Project();
$project->name = "Cartoon";
$company = new Company();
$company->name = 'ACME';
$user = new User();
$user->username = "Bugs Bunny";
$company->users = $user;
$project->company = $company;
$this->assertTrue($project->save(), 'Project could not be saved');
$this->assertEquals('ACME', $project->company->name, 'Project company\'s name is wrong');
$this->assertCount(1, $project->company->users, 'Count of related users is wrong');
$this->assertEquals('Bugs Bunny', $project->company->users[0]->username, 'Company user\'s name is wrong');
$this->assertFalse($project->company->isNewRecord, 'Company record should be saved');
$this->assertFalse($project->company->users[0]->isNewRecord, 'Company Users records should be saved');
}
} }

20
tests/bootstrap.php

@ -15,19 +15,19 @@ new \yii\console\Application([
'id' => 'unit', 'id' => 'unit',
'basePath' => __DIR__, 'basePath' => __DIR__,
'vendorPath' => dirname(__DIR__) . '/vendor', 'vendorPath' => dirname(__DIR__) . '/vendor',
// 'bootstrap' => ['log'], 'bootstrap' => ['log'],
'components' => [ 'components' => [
'db' => [ 'db' => [
'class' => 'yii\db\Connection', 'class' => 'yii\db\Connection',
'dsn' => 'sqlite::memory:', 'dsn' => 'sqlite::memory:',
], ],
// 'log' => [ 'log' => [
// 'targets' => [ 'targets' => [
// [ [
// 'class' => 'yii\log\FileTarget', 'class' => 'yii\log\FileTarget',
// 'categories' => ['yii\db\*', 'lhs\Yii2SaveRelationsBehavior\*'] 'categories' => ['yii\db\*', 'lhs\Yii2SaveRelationsBehavior\*']
// ], ],
// ] ]
// ], ],
] ]
]); ]);

5
tests/models/Project.php

@ -25,8 +25,9 @@ class Project extends \yii\db\ActiveRecord
'relations' => [ 'relations' => [
'company', 'company',
'users', 'users',
'links' => ['scenario' => Link::SCENARIO_FIRST], 'links' => ['scenario' => Link::SCENARIO_FIRST],
'tags' => [ 'projectLinks' => ['cascadeDelete' => true],
'tags' => [
'extraColumns' => function ($model) { 'extraColumns' => function ($model) {
/** @var $model Tag */ /** @var $model Tag */
return [ return [

11
tests/models/ProjectLink.php

@ -5,6 +5,8 @@ namespace tests\models;
class ProjectLink extends \yii\db\ActiveRecord class ProjectLink extends \yii\db\ActiveRecord
{ {
public $blockDelete = false;
/** /**
* @inheritdoc * @inheritdoc
*/ */
@ -24,4 +26,13 @@ class ProjectLink extends \yii\db\ActiveRecord
]; ];
} }
public function beforeDelete()
{
if ($this->blockDelete === true) {
return false;
} else {
return parent::beforeDelete();
}
}
} }

2
tests/models/User.php

@ -22,7 +22,7 @@ class User extends \yii\db\ActiveRecord
return [ return [
'saveRelations' => [ 'saveRelations' => [
'class' => SaveRelationsBehavior::className(), 'class' => SaveRelationsBehavior::className(),
'relations' => ['userProfile', 'company'] 'relations' => ['userProfile' => ['cascadeDelete' => true], 'company']
], ],
]; ];
} }

Loading…
Cancel
Save