Skip to content
Permalink
Browse files

Make the return values consistent in Associations methods.

Having mixed boolean/entity return values makes the interface a bit
confusing. I'd like to try and have consistent return types if possible.

Do some tweaks to the tests for this change, update failing tests in
BelongsToMany. Making Associations::add() return the added object allows
Table to use fewer lines of code and keep its existing behavior.
  • Loading branch information...
markstory committed Dec 19, 2013
1 parent d56e1ff commit 7a56155e01d99cbb839ef4842de8a18dbb1af437
@@ -41,7 +41,7 @@ class Associations {
* @return void
*/
public function add($alias, Association $association) {
$this->_items[strtolower($alias)] = $association;
return $this->_items[strtolower($alias)] = $association;
}
/**
@@ -68,6 +68,15 @@ public function has($alias) {
return isset($this->_items[strtolower($alias)]);
}
/**
* Get the names of all the associations in the collection.
*
* @return array
*/
public function keys() {
return array_keys($this->_items);
}
/**
* Drop/remove an association.
*
@@ -91,11 +100,11 @@ public function drop($alias) {
* @param array $associations The list of associations to save parents from.
* associations not in this list will not be saved.
* @param array $options The options for the save operation.
* @return Entity Modified entity
* @return boolean Success
*/
public function saveParents(Table $table, Entity $entity, $associations, $options = []) {
if (empty($associations)) {
return $entity;
return true;
}
return $this->_saveAssociations($table, $entity, $associations, $options, false);
}
@@ -112,11 +121,11 @@ public function saveParents(Table $table, Entity $entity, $associations, $option
* @param array $associations The list of associations to save children from.
* associations not in this list will not be saved.
* @param array $options The options for the save operation.
* @return Entity Modified entity
* @return boolean Success
*/
public function saveChildren(Table $table, Entity $entity, $associations, $options) {
if (empty($associations)) {
return $entity;
return true;
}
return $this->_saveAssociations($table, $entity, $associations, $options, true);
}
@@ -130,7 +139,7 @@ public function saveChildren(Table $table, Entity $entity, $associations, $optio
* @param array $options Original options
* @param boolean $owningSide Compared with association classes'
* isOwningSide method.
* @return Entity modified entity.
* @return boolean Success
* @throws new \InvalidArgumentException When an unknown alias is used.
*/
protected function _saveAssociations($table, $entity, $associations, $options, $owningSide) {
@@ -152,9 +161,11 @@ protected function _saveAssociations($table, $entity, $associations, $options, $
if ($relation->isOwningSide($table) !== $owningSide) {
continue;
}
$this->_save($relation, $entity, $nested, $options);
if (!$this->_save($relation, $entity, $nested, $options)) {
return false;
}
}
return $entity;
return true;
}
/**
@@ -164,7 +175,7 @@ protected function _saveAssociations($table, $entity, $associations, $options, $
* @param Entity $entity The entity to save
* @param array $nested Options for deeper associations
* @param array $options Original options
* @return void
* @return boolean Success
*/
protected function _save($association, $entity, $nested, $options) {
if (!$entity->dirty($association->property())) {
@@ -173,7 +184,7 @@ protected function _save($association, $entity, $nested, $options) {
if (!empty($nested)) {
$options = (array)$nested + $options;
}
$association->save($entity, $options);
return (bool)$association->save($entity, $options);
}
/**
@@ -20,6 +20,7 @@
use Cake\Event\Event;
use Cake\Event\EventListener;
use Cake\Event\EventManager;
use Cake\ORM\Associations;
use Cake\ORM\Association\BelongsTo;
use Cake\ORM\Association\BelongsToMany;
use Cake\ORM\Association\HasMany;
@@ -132,12 +133,11 @@ class Table implements EventListener {
protected $_displayField;
/**
* The list of associations for this table. Indexed by association name,
* values are Association object instances.
* The associations container for this Table.
*
* @var array
* @var Cake\ORM\Associations
*/
protected $_associations = [];
protected $_associated;
/**
* EventManager for this table.
@@ -210,8 +210,14 @@ public function __construct(array $config = []) {
if (!empty($config['behaviors'])) {
$behaviors = $config['behaviors'];
}
$associations = null;
if (!empty($config['associations'])) {
$associations = $config['associations'];
}
$this->_eventManager = $eventManager ?: new EventManager();
$this->_associated = $associations ?: new Associations();
$this->_behaviors = $behaviors ?: new BehaviorRegistry($this);
$this->initialize($config);
$this->_eventManager->attach($this);
}
@@ -497,11 +503,7 @@ public function hasBehavior($name) {
* @return Cake\ORM\Association
*/
public function association($name) {
$name = strtolower($name);
if (isset($this->_associations[$name])) {
return $this->_associations[$name];
}
return null;
return $this->_associated->get($name);
}
/**
@@ -533,7 +535,7 @@ public function association($name) {
public function belongsTo($associated, array $options = []) {
$options += ['sourceTable' => $this];
$association = new BelongsTo($associated, $options);
return $this->_associations[strtolower($association->name())] = $association;
return $this->_associated->add($association->name(), $association);
}
/**
@@ -570,7 +572,7 @@ public function belongsTo($associated, array $options = []) {
public function hasOne($associated, array $options = []) {
$options += ['sourceTable' => $this];
$association = new HasOne($associated, $options);
return $this->_associations[strtolower($association->name())] = $association;
return $this->_associated->add($association->name(), $association);
}
/**
@@ -611,7 +613,7 @@ public function hasOne($associated, array $options = []) {
public function hasMany($associated, array $options = []) {
$options += ['sourceTable' => $this];
$association = new HasMany($associated, $options);
return $this->_associations[strtolower($association->name())] = $association;
return $this->_associated->add($association->name(), $association);
}
/**
@@ -651,7 +653,7 @@ public function hasMany($associated, array $options = []) {
public function belongsToMany($associated, array $options = []) {
$options += ['sourceTable' => $this];
$association = new BelongsToMany($associated, $options);
return $this->_associations[strtolower($association->name())] = $association;
return $this->_associated->add($association->name(), $association);
}
/**
@@ -981,12 +983,18 @@ public function exists(array $conditions) {
* be saved and to pass additional option for saving them.
*
* {{{
* $articles->save($entity, ['associated' => ['Comment']); // Only save comment assoc
* // Only save the comments association
* $articles->save($entity, ['associated' => ['Comments']);
*
* // Save the company, the employees and related addresses for each of them.
* // For employees use the 'special' validation group
* $companies->save($entity, [
* 'associated' => ['Employee' => ['associated' => ['Address'], 'validate' => 'special']
* 'associated' => [
* 'Employees' => [
* 'associated' => ['Addresses'],
* 'validate' => 'special'
* ]
* ]
* ]);
*
* // Save no associations
@@ -1038,7 +1046,7 @@ protected function _processSave($entity, $options) {
}
if ($options['associated'] === true) {
$options['associated'] = array_keys($this->_associations);
$options['associated'] = $this->_associated->keys();
}
$options['associated'] = array_filter((array)$options['associated']);
@@ -1053,9 +1061,12 @@ protected function _processSave($entity, $options) {
return $event->result;
}
$originalOptions = $options->getArrayCopy();
list($parents, $children) = $this->_sortAssociationTypes($options['associated']);
$saved = $this->_saveAssociations($parents, $entity, $originalOptions);
$saved = $this->_associated->saveParents(
$this,
$entity,
$options['associated'],
$options->getArrayCopy()
);
if (!$saved && $options['atomic']) {
return false;
@@ -1072,7 +1083,12 @@ protected function _processSave($entity, $options) {
}
if ($success) {
$success = $this->_saveAssociations($children, $entity, $originalOptions);
$success = $this->_associated->saveChildren(
$this,
$entity,
$options['associated'],
$options->getArrayCopy()
);
if ($success || !$options['atomic']) {
$entity->clean();
$event = new Event('Model.afterSave', $this, compact('entity', 'options'));
@@ -1090,82 +1106,6 @@ protected function _processSave($entity, $options) {
return $success;
}
/**
* Auxiliary method used to sort out which associations are defined in this table
* as the owning side and which not based on a list of provided aliases.
*
* @param array $assocs list of association aliases
* @throws \InvalidArgumentException if one of the provided aliases is not an
* actual association defined for this table
* @return array with two values, first one contain associations which target
* is the owning side (or parent associations) and second value is an array of
* associations aliases for which this table is the owning side.
*/
protected function _sortAssociationTypes($assocs) {
$parents = $children = [];
foreach ($assocs as $key => $assoc) {
if (!is_string($assoc)) {
$assoc = $key;
}
$association = $this->association($assoc);
if (!$association) {
$msg = __d('cake_dev', '%s is not associated to %s', $this->alias(), $assoc);
throw new \InvalidArgumentException($msg);
}
if ($association->isOwningSide($this)) {
$children[] = $assoc;
} else {
$parents[] = $assoc;
}
}
return [$parents, $children];
}
/**
* Runs through a list of aliases and for each of them calls the `save()` method
* on the corresponding association objects passing $entity and $options as values.
* If the alias for one of the associations contains an array as values in $assocs,
* $options will be merged to this value before passed to the save operation.
*
*
* @param array $assocs list of association aliases.
* @param \Cake\ORM\Entity $entity the entity belonging to this table and maybe
* containing associated entities.
* @param array $options options to be passed to the save operation
* @return boolean|\Cake\ORM\Entity The saved source entity on success, false
* otherwise
*/
protected function _saveAssociations($assocs, $entity, array $options) {
if (empty($assocs)) {
return $entity;
}
$associated = $options['associated'];
unset($options['associated']);
foreach ($assocs as $alias) {
$association = $this->association($alias);
$property = $association->property();
$passOptions = $options;
if (!$entity->dirty($property)) {
continue;
}
if (isset($associated[$alias])) {
$passOptions = (array)$associated[$alias] + $options;
}
if (!$association->save($entity, $passOptions)) {
return false;
}
}
return $entity;
}
/**
* Auxiliary function to handle the insert of an entity's data in the table
*
@@ -1384,9 +1324,7 @@ protected function _processDelete($entity, $options) {
return $success;
}
foreach ($this->_associations as $assoc) {
$assoc->cascadeDelete($entity, $options->getArrayCopy());
}
$this->_associated->cascadeDelete($entity, $options->getArrayCopy());
$event = new Event('Model.afterDelete', $this, [
'entity' => $entity,
@@ -1556,7 +1494,7 @@ public function marshaller($safe = false) {
*/
public function newEntity(array $data, $associations = null) {
if ($associations === null) {
$associations = array_keys($this->_associations);
$associations = $this->_associated->keys();
}
$marshaller = $this->marshaller();
return $marshaller->one($data, $associations);
@@ -1590,7 +1528,7 @@ public function newEntity(array $data, $associations = null) {
*/
public function newEntities(array $data, $associations = null) {
if ($associations === null) {
$associations = array_keys($this->_associations);
$associations = $this->_associated->keys();
}
$marshaller = $this->marshaller();
return $marshaller->many($data, $associations);
@@ -567,7 +567,7 @@ public function testEagerLoaderSubquery() {
* @return void
*/
public function testCascadeDelete() {
$articleTag = $this->getMock('Cake\ORM\Table', ['deleteAll'], [], '', false);
$articleTag = $this->getMock('Cake\ORM\Table', ['deleteAll'], []);
$config = [
'sourceTable' => $this->article,
'targetTable' => $this->tag,
@@ -594,7 +594,7 @@ public function testCascadeDelete() {
* @return void
*/
public function testCascadeDeleteWithCallbacks() {
$articleTag = $this->getMock('Cake\ORM\Table', ['find', 'delete'], [], '', false);
$articleTag = $this->getMock('Cake\ORM\Table', ['find', 'delete'], []);
$config = [
'sourceTable' => $this->article,
'targetTable' => $this->tag,
Oops, something went wrong.

0 comments on commit 7a56155

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