Skip to content

Commit

Permalink
Implemented feature discussed in #7462
Browse files Browse the repository at this point in the history
  • Loading branch information
mylux committed Sep 29, 2015
1 parent ef79994 commit a237c89
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 1 deletion.
68 changes: 67 additions & 1 deletion src/ORM/Association/HasMany.php
Expand Up @@ -32,7 +32,9 @@ class HasMany extends Association
{

use DependentDeleteTrait;
use ExternalAssociationTrait;
use ExternalAssociationTrait {
_options as _externalOptions;
}

/**
* The type of join to be used when adding the association to a query
Expand All @@ -55,6 +57,27 @@ class HasMany extends Association
*/
protected $_validStrategies = [self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY];

/**
* Saving strategy that will only append to the links set
*
* @var string
*/
const SAVE_APPEND = 'append';

/**
* Saving strategy that will replace the links with the provided set
*
* @var string
*/
const SAVE_REPLACE = 'replace';

/**
* Saving strategy to be used by this association
*
* @var string
*/
protected $_saveStrategy = self::SAVE_APPEND;

/**
* Returns whether or not the passed table is the owning side for this
* association. This means that rows in the 'target' table would miss important
Expand All @@ -68,6 +91,26 @@ public function isOwningSide(Table $side)
return $side === $this->source();
}

/**
* Sets the strategy that should be used for saving. If called with no
* arguments, it will return the currently configured strategy
*
* @param string|null $strategy the strategy name to be used
* @throws \InvalidArgumentException if an invalid strategy name is passed
* @return string the strategy to be used for saving
*/
public function saveStrategy($strategy = null)
{
if ($strategy === null) {
return $this->_saveStrategy;
}
if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE])) {
$msg = sprintf('Invalid save strategy "%s"', $strategy);
throw new InvalidArgumentException($msg);
}
return $this->_saveStrategy = $strategy;
}

/**
* Takes an entity from the source table and looks if there is a field
* matching the property name for this association. The found entity will be
Expand Down Expand Up @@ -104,6 +147,11 @@ public function saveAssociated(EntityInterface $entity, array $options = [])
$original = $targetEntities;
$options['_sourceTable'] = $this->source();

if ($this->_saveStrategy === self::SAVE_REPLACE) {
$updateFields = array_fill_keys(array_keys($properties), null);
$target->updateAll($updateFields, $properties);
}

foreach ($targetEntities as $k => $targetEntity) {
if (!($targetEntity instanceof EntityInterface)) {
break;
Expand All @@ -117,6 +165,10 @@ public function saveAssociated(EntityInterface $entity, array $options = [])
$targetEntity->set($properties, ['guard' => false]);
}

if ($this->_saveStrategy === self::SAVE_REPLACE) {
$targetEntity->isNew(true);
}

if ($target->save($targetEntity, $options)) {
$targetEntities[$k] = $targetEntity;
continue;
Expand Down Expand Up @@ -166,4 +218,18 @@ public function type()
{
return self::ONE_TO_MANY;
}

/**
* Parse extra options passed in the constructor.
*
* @param array $opts original list of options passed in constructor
* @return void
*/
protected function _options(array $opts)
{
$this->_externalOptions($opts);
if (!empty($opts['saveStrategy'])) {
$this->saveStrategy($opts['saveStrategy']);
}
}
}
103 changes: 103 additions & 0 deletions tests/TestCase/ORM/TableTest.php
Expand Up @@ -1737,6 +1737,109 @@ public function testSavePrimaryKeyEntityExists()
$this->assertSame($entity, $table->save($entity));
}


/**
* Test that save works with replace saveStrategy
*
* @return void
*/
public function testSaveReplaceSaveStrategy()
{
$authors = $this->getMock(
'Cake\ORM\Table',
['exists'],
[
[
'connection' => $this->connection,
'alias' => 'Authors',
'table' => 'authors',
]
]
);
$authors->hasMany('Articles', ['saveStrategy' => 'replace']);

$entity = $authors->newEntity([
'name' => 'mylux',
'articles' => [
['title' => 'One Random Post', 'body' => 'The cake is not a lie'],
['title' => 'Another Random Post', 'body' => 'The cake is nice'],
]
], ['associated' => ['Articles']]);

$entity = $authors->save($entity, ['associated' => ['Articles']]);

$this->assertEquals(2, $authors->Articles->find('all')->where(['author_id' => $entity['id']])->count());

unset($entity->articles[0]);
$entity->dirty('articles', true);

$authors->save($entity, ['associated' => ['Articles']]);

$this->assertEquals(1, $authors->Articles->find('all')->where(['author_id' => $entity['id']])->count());
}


/**
* Test that save works with append saveStrategy
*
* @return void
*/
public function testSaveAppendSaveStrategy()
{
$authors = $this->getMock(
'Cake\ORM\Table',
['exists'],
[
[
'connection' => $this->connection,
'alias' => 'Authors',
'table' => 'authors',
]
]
);
$authors->hasMany('Articles', ['saveStrategy' => 'append']);

$entity = $authors->newEntity([
'name' => 'mylux',
'articles' => [
['title' => 'One Random Post', 'body' => 'The cake is not a lie'],
['title' => 'Another Random Post', 'body' => 'The cake is nice'],
]
], ['associated' => ['Articles']]);

$entity = $authors->save($entity, ['associated' => ['Articles']]);

$this->assertEquals(2, $authors->Articles->find('all')->where(['author_id' => $entity['id']])->count());

unset($entity->articles[0]);
$entity->dirty('articles', true);

$authors->save($entity, ['associated' => ['Articles']]);

$this->assertEquals(2, $authors->Articles->find('all')->where(['author_id' => $entity['id']])->count());
}
/**
* Test that save has append as the default save strategy
*
* @return void
*/
public function testSaveDefaultSaveStrategy()
{
$authors = $this->getMock(
'Cake\ORM\Table',
['exists'],
[
[
'connection' => $this->connection,
'alias' => 'Authors',
'table' => 'authors',
]
]
);
$authors->hasMany('Articles', ['saveStrategy' => 'append']);
$this->assertEquals('append', $authors->association('articles')->saveStrategy());
}

/**
* Test that saving a new entity with a Primary Key set does not call exists when checkExisting is false.
*
Expand Down

0 comments on commit a237c89

Please sign in to comment.