Skip to content
Permalink
Browse files

Extract cascading deletes into the association classes.

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 6e82645077148a3fb37677f3bd463bf5ac8799be
@@ -16,6 +16,7 @@
*/
namespace Cake\ORM;
use Cake\ORM\Entity;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
@@ -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);
}
@@ -17,6 +17,7 @@
namespace Cake\ORM\Association;
use Cake\ORM\Association;
use Cake\ORM\Entity;
use Cake\ORM\Query;
use Cake\Utility\Inflector;
@@ -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.
@@ -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;
@@ -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.
@@ -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;
}
}
@@ -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;
/**
@@ -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
@@ -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;
@@ -28,6 +29,8 @@
*/
class HasOne extends Association {
use DependentDeleteTrait;
/**
* Whether this association can be expressed directly in a query join
*
@@ -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
@@ -52,7 +52,7 @@ public function setUp() {
];
$this->association = $this->getMock(
'\Cake\ORM\Association',
['_options', 'attachTo', '_joinCondition'],
['_options', 'attachTo', '_joinCondition', 'cascadeDelete'],
['Foo', $config]
);
}

0 comments on commit 6e82645

Please sign in to comment.
You can’t perform that action at this time.