Skip to content

Commit

Permalink
Extract cascading deletes into the association classes.
Browse files Browse the repository at this point in the history
Doing this allows each association to handle cascading deletes in their
own way. I think this makes the code cleaner, and allows userland
associations to participate in cascading deletes. In addition a new
trait has been extract to avoid inheritance/code duplication in
HasOne/HasMany.
  • Loading branch information
markstory committed Nov 3, 2013
1 parent fa8298d commit 6e82645
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 50 deletions.
13 changes: 13 additions & 0 deletions Cake/ORM/Association.php
Expand Up @@ -16,6 +16,7 @@
*/
namespace Cake\ORM;

use Cake\ORM\Entity;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;

Expand Down Expand Up @@ -415,4 +416,16 @@ public function transformRow($row) {
* @return string|array|boolean
*/
protected abstract function _joinCondition(array $options);

/**
* Handles cascading a delete from an associated model.
*
* Each implementing class should handle the cascaded delete as
* required.
*
* @param Cake\ORM\Entity $entity The entity that started the cascaded delete.
* @return boolean Success
*/
public abstract function cascadeDelete(Entity $entity);

}
13 changes: 13 additions & 0 deletions Cake/ORM/Association/BelongsTo.php
Expand Up @@ -17,6 +17,7 @@
namespace Cake\ORM\Association;

use Cake\ORM\Association;
use Cake\ORM\Entity;
use Cake\ORM\Query;
use Cake\Utility\Inflector;

Expand Down Expand Up @@ -52,6 +53,18 @@ public function foreignKey($key = null) {
return parent::foreignKey($key);
}

/**
* Handle cascading deletes.
*
* BelongsTo associations are never cleared in a cascading delete scenario.
*
* @param Cake\ORM\Entity $entity The entity that started the cascaded delete.
* @return boolean Success.
*/
public function cascadeDelete(Entity $entity) {
return true;
}

/**
* Returns a single or multiple conditions to be appended to the generated join
* clause for getting the results on the target table.
Expand Down
20 changes: 20 additions & 0 deletions Cake/ORM/Association/BelongsToMany.php
Expand Up @@ -17,6 +17,7 @@
namespace Cake\ORM\Association;

use Cake\ORM\Association;
use Cake\ORM\Entity;
use Cake\ORM\Query;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
Expand Down Expand Up @@ -217,6 +218,25 @@ public function eagerLoader(array $options) {
return $this->_resultInjector($fetchQuery, $resultMap);
}

/**
* Clear out the data in the join/pivot table for a given entity.
*
* @param Cake\ORM\Entity $entity The entity that started the cascading delete.
* @return boolean Success.
*/
public function cascadeDelete(Entity $entity) {
$foreignKey = $this->foreignKey();
$primaryKey = $this->source()->primaryKey();
$conditions = [
$foreignKey => $entity->get($primaryKey)
];
// TODO fix multi-column primary keys.
$conditions = array_merge($conditions, $this->conditions());

$table = $this->pivot();
$table->deleteAll($conditions);
}

/**
* Appends any conditions required to load the relevant set of records in the
* target table query given a filter key and some filtering values.
Expand Down
56 changes: 56 additions & 0 deletions Cake/ORM/Association/DependentDeleteTrait.php
@@ -0,0 +1,56 @@
<?php
/**
* PHP Version 5.4
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since CakePHP(tm) v 3.0.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace Cake\ORM\Association;

use Cake\ORM\Entity;

/**
* Implements cascading deletes for dependent associations.
*
* Included by HasOne and HasMany association classes.
*/
trait DependentDeleteTrait {

/**
* Cascade a delete to remove dependent records.
*
* This method does nothing if the association is not dependent.
*
* @param Cake\ORM\Entity $entity The entity that started the cascaded delete.
* @return boolean Success.
*/
public function cascadeDelete(Entity $entity) {
if (!$this->dependent()) {
return true;
}
$table = $this->target();
$foreignKey = $this->foreignKey();
$primaryKey = $this->source()->primaryKey();

$conditions = [
$foreignKey => $entity->get($primaryKey)
];
// TODO fix multi-column primary keys.
$conditions = array_merge($conditions, $this->conditions());

$query = $table->find('all')->where($conditions);
foreach ($query as $related) {
$table->delete($related, ['atomic' => false]);
}
return true;
}
}
3 changes: 3 additions & 0 deletions Cake/ORM/Association/HasMany.php
Expand Up @@ -17,6 +17,8 @@
namespace Cake\ORM\Association;

use Cake\ORM\Association;
use Cake\ORM\Association\DependentDeleteTrait;
use Cake\ORM\Association\ExternalAssociationTrait;
use Cake\ORM\Query;

/**
Expand All @@ -28,6 +30,7 @@
class HasMany extends Association {

use ExternalAssociationTrait;
use DependentDeleteTrait;

/**
* The type of join to be used when adding the association to a query
Expand Down
3 changes: 3 additions & 0 deletions Cake/ORM/Association/HasOne.php
Expand Up @@ -17,6 +17,7 @@
namespace Cake\ORM\Association;

use Cake\ORM\Association;
use Cake\ORM\Association\DependentDeleteTrait;
use Cake\ORM\Query;
use Cake\Utility\Inflector;

Expand All @@ -28,6 +29,8 @@
*/
class HasOne extends Association {

use DependentDeleteTrait;

/**
* Whether this association can be expressed directly in a query join
*
Expand Down
50 changes: 1 addition & 49 deletions Cake/ORM/Table.php
Expand Up @@ -989,59 +989,11 @@ protected function _processDelete($entity, $options) {
}

foreach ($this->_associations as $assoc) {
// TODO Perhaps refactor the deletion into the association class?
if ($assoc->dependent()) {
$this->_deleteDependent($assoc, $entity);
} elseif ($assoc instanceof BelongsToMany) {
$this->_deletePivotRecords($assoc, $entity);
}
$assoc->cascadeDelete($entity);
}
return $success;
}

/**
* Delete data in any associations that are marked 'dependent'
*
* @param Cake\ORM\Association $assoc The association to clear.
* @param Cake\ORM\Entity $entity The entity to delete dependent data for.
* @return void
*/
protected function _deleteDependent(Association $assoc, Entity $entity) {
$table = $assoc->target();
$foreignKey = $assoc->foreignKey();
$primaryKey = $this->primaryKey();

$conditions = [
$foreignKey => $entity->get($primaryKey)
];
// TODO fix multi-column primary keys.
$conditions = array_merge($conditions, $assoc->conditions());

$query = $table->find('all')->where($conditions);
foreach ($query as $related) {
$table->delete($related, ['atomic' => false]);
}
}

/**
* Clear the data out of a BelongsToMany join table.
*
* @param Cake\ORM\Association\BelongsToMany $assoc The association to clear.
* @param Cake\ORM\Entity $entity The entity to remove pivot records for.
* @return void
*/
protected function _deletePivotRecords(BelongsToMany $assoc, Entity $entity) {
$foreignKey = $assoc->foreignKey();
$primaryKey = $this->primaryKey();
$conditions = [
$foreignKey => $entity->get($primaryKey)
];
// TODO fix multi-column primary keys.
$conditions = array_merge($conditions, $assoc->conditions());
$table = $assoc->pivot();
$table->deleteAll($conditions);
}

/**
* Calls a finder method directly and applies it to the passed query,
* if no query is passed a new one will be created and returned
Expand Down
2 changes: 1 addition & 1 deletion Cake/Test/TestCase/ORM/AssociationTest.php
Expand Up @@ -52,7 +52,7 @@ public function setUp() {
];
$this->association = $this->getMock(
'\Cake\ORM\Association',
['_options', 'attachTo', '_joinCondition'],
['_options', 'attachTo', '_joinCondition', 'cascadeDelete'],
['Foo', $config]
);
}
Expand Down

0 comments on commit 6e82645

Please sign in to comment.