mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
NEW Add/remove callbacks on RelationList
This provides a mechanism for adjusting the behaviour of these relations when building more complex data models. For example the following example has a status field incorporates a Status field into the relationship: ```php function MyRelation() { $rel = $this->getManyManyComponents(‘MyRelation’); $rel = $rel->filter(‘Status’, ‘Active’); $rel->addCallbacks()->add(function ($relation, $item, $extra) { $item->Status = ‘Active’; $item->write(); }); } ``` Introduces a new library dependency: http://github.com/sminnee/callbacklist
This commit is contained in:
parent
ff18dec2e5
commit
0d7c5a9ece
@ -35,6 +35,7 @@
|
||||
"silverstripe/config": "^1@dev",
|
||||
"silverstripe/assets": "^1@dev",
|
||||
"silverstripe/vendor-plugin": "^1.4",
|
||||
"sminnee/callbacklist": "^0.1",
|
||||
"swiftmailer/swiftmailer": "~5.4",
|
||||
"symfony/cache": "^3.3@dev",
|
||||
"symfony/config": "^3.2",
|
||||
|
@ -93,6 +93,10 @@ class HasManyList extends RelationList
|
||||
$item->$foreignKey = $foreignID;
|
||||
|
||||
$item->write();
|
||||
|
||||
if ($this->addCallbacks) {
|
||||
$this->addCallbacks->call($this, $item, []);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -136,5 +140,9 @@ class HasManyList extends RelationList
|
||||
$item->$foreignKey = null;
|
||||
$item->write();
|
||||
}
|
||||
|
||||
if ($this->removeCallbacks) {
|
||||
$this->removeCallbacks->call($this, [$item->ID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -322,6 +322,10 @@ class ManyManyList extends RelationList
|
||||
|
||||
DB::manipulate($manipulation);
|
||||
}
|
||||
|
||||
if ($this->addCallbacks) {
|
||||
$this->addCallbacks->call($this, $item, $extraFields);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -338,7 +342,9 @@ class ManyManyList extends RelationList
|
||||
throw new InvalidArgumentException("ManyManyList::remove() expecting a $this->dataClass object");
|
||||
}
|
||||
|
||||
return $this->removeByID($item->ID);
|
||||
$result = $this->removeByID($item->ID);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -366,7 +372,13 @@ class ManyManyList extends RelationList
|
||||
$query->addWhere([
|
||||
"\"{$this->joinTable}\".\"{$this->localKey}\"" => $itemID
|
||||
]);
|
||||
|
||||
// Perform the deletion
|
||||
$query->execute();
|
||||
|
||||
if ($this->removeCallbacks) {
|
||||
$this->removeCallbacks->call($this, [$itemID]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -403,7 +415,22 @@ class ManyManyList extends RelationList
|
||||
$delete->addWhere([
|
||||
"\"{$this->joinTable}\".\"{$this->localKey}\" IN ($subSelect)" => $parameters
|
||||
]);
|
||||
|
||||
$affectedIds = [];
|
||||
if ($this->removeCallbacks) {
|
||||
$affectedIds = $delete
|
||||
->toSelect()
|
||||
->setSelect("\"{$this->joinTable}\".\"{$this->localKey}\"")
|
||||
->execute()
|
||||
->column();
|
||||
}
|
||||
|
||||
// Perform the deletion
|
||||
$delete->execute();
|
||||
|
||||
if ($this->removeCallbacks && $affectedIds) {
|
||||
$this->removeCallbacks->call($this, $affectedIds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,20 +130,31 @@ class ManyManyThroughList extends RelationList
|
||||
// Find has_many row with a local key matching the given id
|
||||
$hasManyList = $this->manipulator->getParentRelationship($this->dataQuery());
|
||||
$records = $hasManyList->filter($this->manipulator->getLocalKey(), $itemID);
|
||||
$affectedIds = [];
|
||||
|
||||
// Rather than simple un-associating the record (as in has_many list)
|
||||
// Delete the actual mapping row as many_many deletions behave.
|
||||
/** @var DataObject $record */
|
||||
foreach ($records as $record) {
|
||||
$affectedIds[] = $record->ID;
|
||||
$record->delete();
|
||||
}
|
||||
|
||||
if ($this->removeCallbacks && $affectedIds) {
|
||||
$this->removeCallbacks->call($this, $affectedIds);
|
||||
}
|
||||
}
|
||||
|
||||
public function removeAll()
|
||||
{
|
||||
// Empty has_many table matching the current foreign key
|
||||
$hasManyList = $this->manipulator->getParentRelationship($this->dataQuery());
|
||||
$affectedIds = $hasManyList->column('ID');
|
||||
$hasManyList->removeAll();
|
||||
|
||||
if ($this->removeCallbacks && $affectedIds) {
|
||||
$this->removeCallbacks->call($this, $affectedIds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -222,6 +233,10 @@ class ManyManyThroughList extends RelationList
|
||||
if ($item instanceof DataObject) {
|
||||
$item->setJoin($record, $this->manipulator->getJoinAlias());
|
||||
}
|
||||
|
||||
if ($this->addCallbacks) {
|
||||
$this->addCallbacks->call($this, $item, $extraFields);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace SilverStripe\ORM;
|
||||
|
||||
use Exception;
|
||||
use Sminnee\CallbackList\CallbackList;
|
||||
|
||||
/**
|
||||
* A DataList that represents a relation.
|
||||
@ -11,6 +12,79 @@ use Exception;
|
||||
*/
|
||||
abstract class RelationList extends DataList implements Relation
|
||||
{
|
||||
/**
|
||||
* @var CallbackList|null
|
||||
*/
|
||||
protected $addCallbacks;
|
||||
|
||||
/**
|
||||
* @var CallbackList|null
|
||||
*/
|
||||
protected $removeCallbacks;
|
||||
|
||||
/**
|
||||
* Manage callbacks which are called after the add() action is completed.
|
||||
* Each callback will be passed (RelationList $this, DataObject|int $item, array $extraFields).
|
||||
* If a relation methods is manually defined, this can be called to adjust the behaviour
|
||||
* when adding records to this list.
|
||||
*
|
||||
* Needs to be defined through an overloaded relationship getter
|
||||
* to ensure it is set consistently. These getters return a new object
|
||||
* every time they're called.
|
||||
*
|
||||
* Note that subclasses of RelationList must implement the callback for it to function
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public function addCallbacks(): CallbackList
|
||||
{
|
||||
if (!$this->addCallbacks) {
|
||||
$this->addCallbacks = new CallbackList();
|
||||
}
|
||||
|
||||
return $this->addCallbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage callbacks which are called after the remove() action is completed.
|
||||
* Each Callback will be passed (RelationList $this, [int] $removedIds).
|
||||
*
|
||||
* Needs to be defined through an overloaded relationship getter
|
||||
* to ensure it is set consistently. These getters return a new object
|
||||
* every time they're called. Example:
|
||||
*
|
||||
* ```php
|
||||
* class MyObject extends DataObject()
|
||||
* {
|
||||
* private static $many_many = [
|
||||
* 'MyRelationship' => '...',
|
||||
* ];
|
||||
* public function MyRelationship()
|
||||
* {
|
||||
* $list = $this->getManyManyComponents('MyRelationship');
|
||||
* $list->removeCallbacks()->add(function ($removedIds) {
|
||||
* // ...
|
||||
* });
|
||||
* return $list;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* If a relation methods is manually defined, this can be called to adjust the behaviour
|
||||
* when adding records to this list.
|
||||
*
|
||||
* Subclasses of RelationList must implement the callback for it to function
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public function removeCallbacks(): CallbackList
|
||||
{
|
||||
if (!$this->removeCallbacks) {
|
||||
$this->removeCallbacks = new CallbackList();
|
||||
}
|
||||
|
||||
return $this->removeCallbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any number of foreign keys to apply to this list
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace SilverStripe\ORM\Tests;
|
||||
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\Player;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\Team;
|
||||
use SilverStripe\ORM\Tests\HasManyListTest\Company;
|
||||
use SilverStripe\ORM\Tests\HasManyListTest\CompanyCar;
|
||||
@ -113,4 +114,79 @@ class HasManyListTest extends SapphireTest
|
||||
['Model' => 'F40'],
|
||||
], $company->CompanyCars()->sort('"Model" ASC'));
|
||||
}
|
||||
|
||||
public function testCallbackOnSetById()
|
||||
{
|
||||
$addedIds = [];
|
||||
$removedIds = [];
|
||||
|
||||
$base = $this->objFromFixture(Company::class, 'silverstripe');
|
||||
$relation = $base->Employees();
|
||||
$remove = $relation->First();
|
||||
$add = new Employee();
|
||||
$add->write();
|
||||
|
||||
$relation->addCallbacks()->add(function ($list, $item) use (&$addedIds) {
|
||||
$addedIds[] = $item;
|
||||
});
|
||||
|
||||
$relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
|
||||
$removedIds = $ids;
|
||||
});
|
||||
|
||||
$relation->setByIDList(array_merge(
|
||||
$base->Employees()->exclude('ID', $remove->ID)->column('ID'),
|
||||
[$add->ID]
|
||||
));
|
||||
$this->assertEquals([$remove->ID], $removedIds);
|
||||
}
|
||||
|
||||
public function testAddCallback()
|
||||
{
|
||||
$added = [];
|
||||
|
||||
$base = $this->objFromFixture(Company::class, 'silverstripe');
|
||||
$relation = $base->Employees();
|
||||
$add = new Employee();
|
||||
$add->write();
|
||||
|
||||
$relation->addCallbacks()->add(function ($list, $item) use (&$added) {
|
||||
$added[] = $item;
|
||||
});
|
||||
|
||||
$relation->add($add);
|
||||
$this->assertEquals([$add], $added);
|
||||
}
|
||||
|
||||
public function testRemoveCallbackOnRemove()
|
||||
{
|
||||
$removedIds = [];
|
||||
|
||||
$base = $this->objFromFixture(Company::class, 'silverstripe');
|
||||
$relation = $base->Employees();
|
||||
$remove = $relation->First();
|
||||
|
||||
$relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
|
||||
$removedIds = $ids;
|
||||
});
|
||||
|
||||
$relation->remove($remove);
|
||||
$this->assertEquals([$remove->ID], $removedIds);
|
||||
}
|
||||
|
||||
public function testRemoveCallbackOnRemoveById()
|
||||
{
|
||||
$removedIds = [];
|
||||
|
||||
$base = $this->objFromFixture(Company::class, 'silverstripe');
|
||||
$relation = $base->Employees();
|
||||
$remove = $relation->First();
|
||||
|
||||
$relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
|
||||
$removedIds = $ids;
|
||||
});
|
||||
|
||||
$relation->removeByID($remove->ID);
|
||||
$this->assertEquals([$remove->ID], $removedIds);
|
||||
}
|
||||
}
|
||||
|
@ -457,4 +457,95 @@ class ManyManyListTest extends SapphireTest
|
||||
'ManyManyDynamicField' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testCallbackOnSetById()
|
||||
{
|
||||
$addedIds = [];
|
||||
$removedIds = [];
|
||||
|
||||
$base = $this->objFromFixture(Team::class, 'team1');
|
||||
$relation = $base->Players();
|
||||
$remove = $relation->First();
|
||||
$add = new Player();
|
||||
$add->write();
|
||||
|
||||
$relation->addCallbacks()->add(function ($list, $item, $extraFields) use (&$removedIds) {
|
||||
$addedIds[] = $item;
|
||||
});
|
||||
|
||||
$relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
|
||||
$removedIds = $ids;
|
||||
});
|
||||
|
||||
$relation->setByIDList(array_merge(
|
||||
$base->Players()->exclude('ID', $remove->ID)->column('ID'),
|
||||
[$add->ID]
|
||||
));
|
||||
$this->assertEquals([$remove->ID], $removedIds);
|
||||
}
|
||||
|
||||
public function testAddCallbackWithExtraFields()
|
||||
{
|
||||
$added = [];
|
||||
|
||||
$base = $this->objFromFixture(Team::class, 'team1');
|
||||
$relation = $base->Players();
|
||||
$add = new Player();
|
||||
$add->write();
|
||||
|
||||
$relation->addCallbacks()->add(function ($list, $item, $extraFields) use (&$added) {
|
||||
$added[] = [$item, $extraFields];
|
||||
});
|
||||
|
||||
$relation->add($add, ['Position' => 'Quarterback']);
|
||||
$this->assertEquals([[$add, ['Position' => 'Quarterback']]], $added);
|
||||
}
|
||||
|
||||
public function testRemoveCallbackOnRemove()
|
||||
{
|
||||
$removedIds = [];
|
||||
|
||||
$base = $this->objFromFixture(Team::class, 'team1');
|
||||
$relation = $base->Players();
|
||||
$remove = $relation->First();
|
||||
|
||||
$relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
|
||||
$removedIds = $ids;
|
||||
});
|
||||
|
||||
$relation->remove($remove);
|
||||
$this->assertEquals([$remove->ID], $removedIds);
|
||||
}
|
||||
|
||||
public function testRemoveCallbackOnRemoveById()
|
||||
{
|
||||
$removedIds = [];
|
||||
|
||||
$base = $this->objFromFixture(Team::class, 'team1');
|
||||
$relation = $base->Players();
|
||||
$remove = $relation->First();
|
||||
|
||||
$relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
|
||||
$removedIds = $ids;
|
||||
});
|
||||
|
||||
$relation->removeByID($remove->ID);
|
||||
$this->assertEquals([$remove->ID], $removedIds);
|
||||
}
|
||||
|
||||
public function testRemoveCallbackOnRemoveAll()
|
||||
{
|
||||
$removedIds = [];
|
||||
|
||||
$base = $this->objFromFixture(Team::class, 'team1');
|
||||
$relation = $base->Players();
|
||||
$remove = $relation->column('ID');
|
||||
|
||||
$relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
|
||||
$removedIds = $ids;
|
||||
});
|
||||
|
||||
$relation->removeAll();
|
||||
$this->assertEquals(sort($remove), sort($removedIds));
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,14 @@ use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\ManyManyThroughList;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\Player;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\Team;
|
||||
use SilverStripe\ORM\Tests\ManyManyThroughListTest\Item;
|
||||
use SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyItem;
|
||||
use SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyJoinObject;
|
||||
use SilverStripe\ORM\Tests\ManyManyThroughListTest\Locale;
|
||||
use SilverStripe\ORM\Tests\ManyManyThroughListTest\FallbackLocale;
|
||||
use SilverStripe\ORM\Tests\ManyManyThroughListTest\TestObject;
|
||||
|
||||
class ManyManyThroughListTest extends SapphireTest
|
||||
{
|
||||
@ -362,4 +365,95 @@ class ManyManyThroughListTest extends SapphireTest
|
||||
$this->assertSame('International', $firstReverse->Title);
|
||||
$this->assertSame('Argentina', $secondReverse->Title);
|
||||
}
|
||||
|
||||
public function testCallbackOnSetById()
|
||||
{
|
||||
$addedIds = [];
|
||||
$removedIds = [];
|
||||
|
||||
$base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
|
||||
$relation = $base->Items();
|
||||
$remove = $relation->First();
|
||||
$add = new Item();
|
||||
$add->write();
|
||||
|
||||
$relation->addCallbacks()->add(function ($list, $item, $extraFields) use (&$removedIds) {
|
||||
$addedIds[] = $item;
|
||||
});
|
||||
|
||||
$relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
|
||||
$removedIds = $ids;
|
||||
});
|
||||
|
||||
$relation->setByIDList(array_merge(
|
||||
$base->Items()->exclude('ID', $remove->ID)->column('ID'),
|
||||
[$add->ID]
|
||||
));
|
||||
$this->assertEquals([$remove->ID], $removedIds);
|
||||
}
|
||||
|
||||
public function testAddCallbackWithExtraFields()
|
||||
{
|
||||
$added = [];
|
||||
|
||||
$base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
|
||||
$relation = $base->Items();
|
||||
$add = new Item();
|
||||
$add->write();
|
||||
|
||||
$relation->addCallbacks()->add(function ($list, $item, $extraFields) use (&$added) {
|
||||
$added[] = [$item, $extraFields];
|
||||
});
|
||||
|
||||
$relation->add($add, ['Sort' => '99']);
|
||||
$this->assertEquals([[$add, ['Sort' => '99']]], $added);
|
||||
}
|
||||
|
||||
public function testRemoveCallbackOnRemove()
|
||||
{
|
||||
$removedIds = [];
|
||||
|
||||
$base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
|
||||
$relation = $base->Items();
|
||||
$remove = $relation->First();
|
||||
|
||||
$relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
|
||||
$removedIds = $ids;
|
||||
});
|
||||
|
||||
$relation->remove($remove);
|
||||
$this->assertEquals([$remove->ID], $removedIds);
|
||||
}
|
||||
|
||||
public function testRemoveCallbackOnRemoveById()
|
||||
{
|
||||
$removedIds = [];
|
||||
|
||||
$base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
|
||||
$relation = $base->Items();
|
||||
$remove = $relation->First();
|
||||
|
||||
$relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
|
||||
$removedIds = $ids;
|
||||
});
|
||||
|
||||
$relation->removeByID($remove->ID);
|
||||
$this->assertEquals([$remove->ID], $removedIds);
|
||||
}
|
||||
|
||||
public function testRemoveCallbackOnRemoveAll()
|
||||
{
|
||||
$removedIds = [];
|
||||
|
||||
$base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
|
||||
$relation = $base->Items();
|
||||
$remove = $relation->column('ID');
|
||||
|
||||
$relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
|
||||
$removedIds = $ids;
|
||||
});
|
||||
|
||||
$relation->removeAll();
|
||||
$this->assertEquals(sort($remove), sort($removedIds));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user