mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Implement many_many through polymorphic (from only) (#7928)
* API Support many_many through polymorphic relations (from side only) Fixes #7911 Fixes #3136 * Add extra docs and allow optional arguments * ENHANCEMENT Enable quiet to be turned off * BUG Fix issue with manymanythroughlist duplication
This commit is contained in:
parent
1ea14382ee
commit
257ff69e32
@ -339,6 +339,59 @@ $supporters = $team->Supporters()->where(['"TeamSupporter"."Ranking"' => 1]);
|
||||
Note: ->filter() currently does not support joined fields natively due to the fact that the
|
||||
query for the join table is isolated from the outer query controlled by DataList.
|
||||
|
||||
### Polymorphic many_many (Experimental)
|
||||
|
||||
Using many_many through, it is possible to support polymorphic relations on the mapping table.
|
||||
Note, that this feature is currently experimental, and has certain limitations:
|
||||
- This feature only works with many_many through
|
||||
- This feature will only allow polymorphic many_many, but not belongs_many_many. However,
|
||||
you can have a has_many relation to the mapping table on this side, and iterate through this
|
||||
to collate parent records.
|
||||
|
||||
For instance, this is how you would link an arbitrary object to many_many tags.
|
||||
|
||||
```php
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class SomeObject extends DataObject
|
||||
{
|
||||
// This same many_many may also exist on other classes
|
||||
private static $many_many = [
|
||||
"Tags" => [
|
||||
'through' => TagMapping::class,
|
||||
'from' => 'Parent',
|
||||
'to' => 'Tag',
|
||||
]
|
||||
];
|
||||
}
|
||||
class Tag extends DataObject
|
||||
{
|
||||
// has_many works, but belongs_many_many will not
|
||||
private static $has_many = [
|
||||
'TagMappings' => TagMapping::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Example iterator placeholder for belongs_many_many.
|
||||
* This is a list of arbitrary types of objects
|
||||
* @return Generator|DataObject[]
|
||||
*/
|
||||
public function TaggedObjects()
|
||||
{
|
||||
foreach ($this->TagMappings() as $mapping) {
|
||||
yield $mapping->Parent();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
class TagMapping extends DataObject
|
||||
{
|
||||
private static $has_one = [
|
||||
'Parent' => DataObject::class, // Polymorphic has_one
|
||||
'Tag' => Tag::class,
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Using many_many in templates
|
||||
|
||||
|
@ -98,10 +98,12 @@ abstract class DBSchemaManager
|
||||
|
||||
/**
|
||||
* Enable supression of database messages.
|
||||
*
|
||||
* @param bool $quiet
|
||||
*/
|
||||
public function quiet()
|
||||
public function quiet($quiet = true)
|
||||
{
|
||||
$this->supressOutput = true;
|
||||
$this->supressOutput = $quiet;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -678,10 +678,12 @@ class DB
|
||||
|
||||
/**
|
||||
* Enable supression of database messages.
|
||||
*
|
||||
* @param bool $quiet
|
||||
*/
|
||||
public static function quiet()
|
||||
public static function quiet($quiet = true)
|
||||
{
|
||||
self::get_schema()->quiet();
|
||||
self::get_schema()->quiet($quiet);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,6 +104,7 @@ use stdClass;
|
||||
* @property string $ClassName Class name of the DataObject
|
||||
* @property string $LastEdited Date and time of DataObject's last modification.
|
||||
* @property string $Created Date and time of DataObject creation.
|
||||
* @property string $ObsoleteClassName If ClassName no longer exists this will be set to the legacy value
|
||||
*/
|
||||
class DataObject extends ViewableData implements DataObjectInterface, i18nEntityProvider, Resettable
|
||||
{
|
||||
@ -1982,7 +1983,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$manyMany['join'],
|
||||
$manyMany['parentField'], // Reversed parent / child field
|
||||
$manyMany['childField'], // Reversed parent / child field
|
||||
$extraFields
|
||||
$extraFields,
|
||||
$manyMany['childClass'], // substitute child class for parentClass
|
||||
$remoteClass // In case ManyManyThroughList needs to use PolymorphicHasManyList internally
|
||||
);
|
||||
$this->extend('updateManyManyComponents', $result);
|
||||
|
||||
@ -2040,10 +2043,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$manyManyComponent['join'],
|
||||
$manyManyComponent['childField'],
|
||||
$manyManyComponent['parentField'],
|
||||
$extraFields
|
||||
$extraFields,
|
||||
$manyManyComponent['parentClass'],
|
||||
static::class // In case ManyManyThroughList needs to use PolymorphicHasManyList internally
|
||||
);
|
||||
|
||||
|
||||
// Store component data in query meta-data
|
||||
$result = $result->alterDataQuery(function ($query) use ($extraFields) {
|
||||
/** @var DataQuery $query */
|
||||
|
@ -940,7 +940,8 @@ class DataObjectSchema
|
||||
'relationClass' => ManyManyThroughList::class,
|
||||
'parentClass' => $parentClass,
|
||||
'childClass' => $joinChildClass,
|
||||
'parentField' => $specification['from'] . 'ID',
|
||||
/** @internal Polymorphic many_many is experimental */
|
||||
'parentField' => $specification['from'] . ($parentClass === DataObject::class ? '' : 'ID'),
|
||||
'childField' => $specification['to'] . 'ID',
|
||||
'join' => $joinClass,
|
||||
];
|
||||
@ -982,10 +983,18 @@ class DataObjectSchema
|
||||
if (!$otherManyMany) {
|
||||
return null;
|
||||
}
|
||||
foreach ($otherManyMany as $inverseComponentName => $nextClass) {
|
||||
if ($nextClass === $parentClass) {
|
||||
foreach ($otherManyMany as $inverseComponentName => $manyManySpec) {
|
||||
// Normal many-many
|
||||
if ($manyManySpec === $parentClass) {
|
||||
return $inverseComponentName;
|
||||
}
|
||||
// many-many through, inspect 'to' for the many_many
|
||||
if (is_array($manyManySpec)) {
|
||||
$toClass = $this->hasOneComponent($manyManySpec['through'], $manyManySpec['to']);
|
||||
if ($toClass === $parentClass) {
|
||||
return $inverseComponentName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -1106,7 +1115,13 @@ class DataObjectSchema
|
||||
}
|
||||
|
||||
// Check for polymorphic
|
||||
/** @internal Polymorphic many_many is experimental */
|
||||
if ($relationClass === DataObject::class) {
|
||||
// Currently polymorphic 'from' is supported.
|
||||
if ($key === 'from') {
|
||||
return $relationClass;
|
||||
}
|
||||
// @todo support polymorphic 'to'
|
||||
throw new InvalidArgumentException(
|
||||
"many_many through relation {$parentClass}.{$component} {$key} references a polymorphic field "
|
||||
. "{$joinClass}::{$relation} which is not supported"
|
||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\ORM;
|
||||
|
||||
use BadMethodCallException;
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
|
||||
/**
|
||||
@ -25,14 +26,30 @@ class ManyManyThroughList extends RelationList
|
||||
* @param string $localKey The key in the join table that maps to the dataClass' PK.
|
||||
* @param string $foreignKey The key in the join table that maps to joined class' PK.
|
||||
*
|
||||
* @param array $extraFields Ignored for ManyManyThroughList
|
||||
* @param string $foreignClass 'from' class
|
||||
* @param string $parentClass Parent class (should be subclass of 'from')
|
||||
* @example new ManyManyThroughList('Banner', 'PageBanner', 'BannerID', 'PageID');
|
||||
*/
|
||||
public function __construct($dataClass, $joinClass, $localKey, $foreignKey)
|
||||
{
|
||||
public function __construct(
|
||||
$dataClass,
|
||||
$joinClass,
|
||||
$localKey,
|
||||
$foreignKey,
|
||||
$extraFields = [],
|
||||
$foreignClass = null,
|
||||
$parentClass = null
|
||||
) {
|
||||
parent::__construct($dataClass);
|
||||
|
||||
// Inject manipulator
|
||||
$this->manipulator = ManyManyThroughQueryManipulator::create($joinClass, $localKey, $foreignKey);
|
||||
$this->manipulator = ManyManyThroughQueryManipulator::create(
|
||||
$joinClass,
|
||||
$localKey,
|
||||
$foreignKey,
|
||||
$foreignClass,
|
||||
$parentClass
|
||||
);
|
||||
$this->dataQuery->pushQueryManipulator($this->manipulator);
|
||||
}
|
||||
|
||||
@ -64,7 +81,7 @@ class ManyManyThroughList extends RelationList
|
||||
}
|
||||
|
||||
// Create parent record
|
||||
$record = parent::createDataObject($row);
|
||||
$record = parent::createDataObject($row);
|
||||
|
||||
// Create joined record
|
||||
if ($joinRow) {
|
||||
@ -145,6 +162,10 @@ class ManyManyThroughList extends RelationList
|
||||
if (is_numeric($item)) {
|
||||
$itemID = $item;
|
||||
} elseif ($item instanceof $this->dataClass) {
|
||||
/** @var DataObject $item */
|
||||
if (!$item->isInDB()) {
|
||||
$item->write();
|
||||
}
|
||||
$itemID = $item->ID;
|
||||
} else {
|
||||
throw new InvalidArgumentException(
|
||||
@ -152,7 +173,7 @@ class ManyManyThroughList extends RelationList
|
||||
);
|
||||
}
|
||||
if (empty($itemID)) {
|
||||
throw new InvalidArgumentException("ManyManyThroughList::add() doesn't accept unsaved records");
|
||||
throw new InvalidArgumentException("ManyManyThroughList::add() could not add record without ID");
|
||||
}
|
||||
|
||||
// Validate foreignID
|
||||
@ -169,7 +190,8 @@ class ManyManyThroughList extends RelationList
|
||||
|
||||
// Update existing records
|
||||
$localKey = $this->manipulator->getLocalKey();
|
||||
$foreignKey = $this->manipulator->getForeignKey();
|
||||
// Foreign key (or key for ID field if polymorphic)
|
||||
$foreignKey = $this->manipulator->getForeignIDKey();
|
||||
$hasManyList = $this->manipulator->getParentRelationship($this->dataQuery());
|
||||
$records = $hasManyList->filter($localKey, $itemID);
|
||||
/** @var DataObject $record */
|
||||
@ -185,12 +207,27 @@ class ManyManyThroughList extends RelationList
|
||||
unset($foreignIDsToAdd[$foreignID]);
|
||||
}
|
||||
|
||||
// Once existing records are updated, add missing mapping records
|
||||
foreach ($foreignIDsToAdd as $foreignID) {
|
||||
$record = $hasManyList->createDataObject($extraFields ?: []);
|
||||
$record->$foreignKey = $foreignID;
|
||||
$record->$localKey = $itemID;
|
||||
$record->write();
|
||||
// Check if any records remain to add
|
||||
if (empty($foreignIDsToAdd)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add item to relation
|
||||
$hasManyList = $hasManyList->forForeignID($foreignIDsToAdd);
|
||||
$record = $hasManyList->createDataObject($extraFields ?: []);
|
||||
$record->$localKey = $itemID;
|
||||
$hasManyList->add($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extra fields used by this list
|
||||
*
|
||||
* @return array a map of field names to types
|
||||
*/
|
||||
public function getExtraFields()
|
||||
{
|
||||
// Inherit config from join table
|
||||
$joinClass = $this->manipulator->getJoinClass();
|
||||
return Config::inst()->get($joinClass, 'db');
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ namespace SilverStripe\ORM;
|
||||
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\ORM\Queries\SQLSelect;
|
||||
|
||||
/**
|
||||
@ -36,6 +37,20 @@ class ManyManyThroughQueryManipulator implements DataQueryManipulator
|
||||
*/
|
||||
protected $foreignKey;
|
||||
|
||||
/**
|
||||
* Foreign class 'from' property. Normally not needed unless polymorphic.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $foreignClass;
|
||||
|
||||
/**
|
||||
* Class name of instance that owns this list
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $parentClass;
|
||||
|
||||
/**
|
||||
* Build query manipulator for a given join table. Additional parameters (foreign key, etc)
|
||||
* will be infered at evaluation from query parameters set via the ManyManyThroughList
|
||||
@ -43,12 +58,24 @@ class ManyManyThroughQueryManipulator implements DataQueryManipulator
|
||||
* @param string $joinClass Class name of the joined dataobject record
|
||||
* @param string $localKey The key in the join table that maps to the dataClass' PK.
|
||||
* @param string $foreignKey The key in the join table that maps to joined class' PK.
|
||||
* @param string $foreignClass the 'from' class name
|
||||
* @param string $parentClass Name of parent class. Subclass of $foreignClass
|
||||
*/
|
||||
public function __construct($joinClass, $localKey, $foreignKey)
|
||||
public function __construct($joinClass, $localKey, $foreignKey, $foreignClass = null, $parentClass = null)
|
||||
{
|
||||
$this->setJoinClass($joinClass);
|
||||
$this->setLocalKey($localKey);
|
||||
$this->setForeignKey($foreignKey);
|
||||
if ($foreignClass) {
|
||||
$this->setForeignClass($foreignClass);
|
||||
} else {
|
||||
Deprecation::notice('5.0', 'Arg $foreignClass will be mandatory in 5.x');
|
||||
}
|
||||
if ($parentClass) {
|
||||
$this->setParentClass($parentClass);
|
||||
} else {
|
||||
Deprecation::notice('5.0', 'Arg $parentClass will be mandatory in 5.x');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,6 +122,33 @@ class ManyManyThroughQueryManipulator implements DataQueryManipulator
|
||||
return $this->foreignKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets ID key name for foreign key component
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getForeignIDKey()
|
||||
{
|
||||
$key = $this->getForeignKey();
|
||||
if ($this->getForeignClass() === DataObject::class) {
|
||||
return $key . 'ID';
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Class key name for foreign key component (or null if none)
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getForeignClassKey()
|
||||
{
|
||||
if ($this->getForeignClass() === DataObject::class) {
|
||||
return $this->getForeignKey() . 'Class';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $foreignKey
|
||||
* @return $this
|
||||
@ -114,11 +168,21 @@ class ManyManyThroughQueryManipulator implements DataQueryManipulator
|
||||
public function getParentRelationship(DataQuery $query)
|
||||
{
|
||||
// Create has_many
|
||||
$list = HasManyList::create($this->getJoinClass(), $this->getForeignKey());
|
||||
if ($this->getForeignClass() === DataObject::class) {
|
||||
/** @internal Polymorphic many_many is experimental */
|
||||
$list = PolymorphicHasManyList::create(
|
||||
$this->getJoinClass(),
|
||||
$this->getForeignKey(),
|
||||
$this->getParentClass()
|
||||
);
|
||||
} else {
|
||||
$list = HasManyList::create($this->getJoinClass(), $this->getForeignKey());
|
||||
}
|
||||
$list = $list->setDataQueryParam($this->extractInheritableQueryParameters($query));
|
||||
|
||||
// Limit to given foreign key
|
||||
if ($foreignID = $query->getQueryParam('Foreign.ID')) {
|
||||
$foreignID = $query->getQueryParam('Foreign.ID');
|
||||
if ($foreignID) {
|
||||
$list = $list->forForeignID($foreignID);
|
||||
}
|
||||
return $list;
|
||||
@ -190,6 +254,8 @@ class ManyManyThroughQueryManipulator implements DataQueryManipulator
|
||||
}
|
||||
|
||||
// Apply join and record sql for later insertion (at end of replacements)
|
||||
// By using a string placeholder $$_SUBQUERY_$$ we protect field/table rewrites from interfering twice
|
||||
// on the already-finalised inner list
|
||||
$sqlSelect->addInnerJoin(
|
||||
'(SELECT $$_SUBQUERY_$$)',
|
||||
"\"{$joinTableAlias}\".\"{$localKey}\" = {$childField}",
|
||||
@ -223,4 +289,40 @@ class ManyManyThroughQueryManipulator implements DataQueryManipulator
|
||||
$dataQuery->setQueryParam('Foreign.JoinTableSQL', null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getForeignClass()
|
||||
{
|
||||
return $this->foreignClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $foreignClass
|
||||
* @return $this
|
||||
*/
|
||||
public function setForeignClass($foreignClass)
|
||||
{
|
||||
$this->foreignClass = $foreignClass;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getParentClass()
|
||||
{
|
||||
return $this->parentClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $parentClass
|
||||
* @return ManyManyThroughQueryManipulator
|
||||
*/
|
||||
public function setParentClass($parentClass)
|
||||
{
|
||||
$this->parentClass = $parentClass;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -67,9 +67,8 @@ class PolymorphicHasManyList extends HasManyList
|
||||
if (is_numeric($item)) {
|
||||
$item = DataObject::get_by_id($this->dataClass, $item);
|
||||
} elseif (!($item instanceof $this->dataClass)) {
|
||||
user_error(
|
||||
"PolymorphicHasManyList::add() expecting a $this->dataClass object, or ID value",
|
||||
E_USER_ERROR
|
||||
throw new InvalidArgumentException(
|
||||
"PolymorphicHasManyList::add() expecting a $this->dataClass object, or ID value"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ namespace SilverStripe\ORM\Tests;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\ManyManyThroughList;
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyItem;
|
||||
|
||||
class ManyManyThroughListTest extends SapphireTest
|
||||
{
|
||||
@ -14,7 +14,11 @@ class ManyManyThroughListTest extends SapphireTest
|
||||
protected static $extra_dataobjects = [
|
||||
ManyManyThroughListTest\Item::class,
|
||||
ManyManyThroughListTest\JoinObject::class,
|
||||
ManyManyThroughListTest\TestObject::class
|
||||
ManyManyThroughListTest\TestObject::class,
|
||||
ManyManyThroughListTest\PolyItem::class,
|
||||
ManyManyThroughListTest\PolyJoinObject::class,
|
||||
ManyManyThroughListTest\PolyObjectA::class,
|
||||
ManyManyThroughListTest\PolyObjectB::class,
|
||||
];
|
||||
|
||||
protected function setUp()
|
||||
@ -185,4 +189,76 @@ class ManyManyThroughListTest extends SapphireTest
|
||||
$schema->manyManyComponent(ManyManyThroughListTest\Item::class, 'Objects')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: polymorphic many_many support is currently experimental
|
||||
*/
|
||||
public function testPolymorphicManyMany()
|
||||
{
|
||||
/** @var ManyManyThroughListTest\PolyObjectA $objA1 */
|
||||
$objA1 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectA::class, 'obja1');
|
||||
/** @var ManyManyThroughListTest\PolyObjectB $objB1 */
|
||||
$objB1 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectB::class, 'objb1');
|
||||
/** @var ManyManyThroughListTest\PolyObjectB $objB2 */
|
||||
$objB2 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectB::class, 'objb2');
|
||||
|
||||
// Test various parent class queries
|
||||
$this->assertListEquals([
|
||||
['Title' => 'item 1'],
|
||||
['Title' => 'item 2'],
|
||||
], $objA1->Items());
|
||||
$this->assertListEquals([
|
||||
['Title' => 'item 2'],
|
||||
], $objB1->Items());
|
||||
$this->assertListEquals([
|
||||
['Title' => 'item 2'],
|
||||
], $objB2->Items());
|
||||
|
||||
// Test adding items
|
||||
$newItem = new PolyItem();
|
||||
$newItem->Title = 'New Item';
|
||||
$objA1->Items()->add($newItem);
|
||||
$objB2->Items()->add($newItem);
|
||||
$this->assertListEquals([
|
||||
['Title' => 'item 1'],
|
||||
['Title' => 'item 2'],
|
||||
['Title' => 'New Item'],
|
||||
], $objA1->Items());
|
||||
$this->assertListEquals([
|
||||
['Title' => 'item 2'],
|
||||
], $objB1->Items());
|
||||
$this->assertListEquals([
|
||||
['Title' => 'item 2'],
|
||||
['Title' => 'New Item'],
|
||||
], $objB2->Items());
|
||||
|
||||
// Test removing items
|
||||
$item2 = $this->objFromFixture(ManyManyThroughListTest\PolyItem::class, 'child2');
|
||||
$objA1->Items()->remove($item2);
|
||||
$objB1->Items()->remove($item2);
|
||||
$this->assertListEquals([
|
||||
['Title' => 'item 1'],
|
||||
['Title' => 'New Item'],
|
||||
], $objA1->Items());
|
||||
$this->assertListEquals([], $objB1->Items());
|
||||
$this->assertListEquals([
|
||||
['Title' => 'item 2'],
|
||||
['Title' => 'New Item'],
|
||||
], $objB2->Items());
|
||||
|
||||
// Test set-by-id-list
|
||||
$objB2->Items()->setByIDList([
|
||||
$newItem->ID,
|
||||
$this->idFromFixture(ManyManyThroughListTest\PolyItem::class, 'child1'),
|
||||
]);
|
||||
$this->assertListEquals([
|
||||
['Title' => 'item 1'],
|
||||
['Title' => 'New Item'],
|
||||
], $objA1->Items());
|
||||
$this->assertListEquals([], $objB1->Items());
|
||||
$this->assertListEquals([
|
||||
['Title' => 'item 1'],
|
||||
['Title' => 'New Item'],
|
||||
], $objB2->Items());
|
||||
}
|
||||
}
|
||||
|
@ -17,3 +17,37 @@ SilverStripe\ORM\Tests\ManyManyThroughListTest\JoinObject:
|
||||
Sort: 2
|
||||
Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\TestObject.parent1
|
||||
Child: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\Item.child2
|
||||
SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyObjectA:
|
||||
obja1:
|
||||
Title: 'object A1'
|
||||
SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyObjectB:
|
||||
objb1:
|
||||
Title: 'object B1'
|
||||
objb2:
|
||||
Title: 'object B2'
|
||||
SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyItem:
|
||||
child1:
|
||||
Title: 'item 1'
|
||||
child2:
|
||||
Title: 'item 2'
|
||||
SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyJoinObject:
|
||||
join1:
|
||||
Title: 'join 1'
|
||||
Sort: 4
|
||||
Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyObjectA.obja1
|
||||
Child: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyItem.child1
|
||||
join2:
|
||||
Title: 'join 2'
|
||||
Sort: 2
|
||||
Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyObjectA.obja1
|
||||
Child: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyItem.child2
|
||||
join3:
|
||||
Title: 'join 3'
|
||||
Sort: 2
|
||||
Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyObjectB.objb1
|
||||
Child: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyItem.child2
|
||||
join4:
|
||||
Title: 'join 4'
|
||||
Sort: 2
|
||||
Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyObjectB.objb2
|
||||
Child: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyItem.child2
|
||||
|
@ -19,6 +19,7 @@ class Item extends DataObject implements TestOnly
|
||||
];
|
||||
|
||||
private static $belongs_many_many = [
|
||||
'Objects' => 'SilverStripe\\ORM\\Tests\\ManyManyThroughListTest\\TestObject.Items'
|
||||
// Intentionally omit parent `.Items` specifier to ensure it's not mandatory
|
||||
'Objects' => TestObject::class,
|
||||
];
|
||||
}
|
||||
|
36
tests/php/ORM/ManyManyThroughListTest/PolyItem.php
Normal file
36
tests/php/ORM/ManyManyThroughListTest/PolyItem.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests\ManyManyThroughListTest;
|
||||
|
||||
use Generator;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class PolyItem extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'ManyManyThroughListTest_PolyItem';
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar'
|
||||
];
|
||||
|
||||
private static $has_many = [
|
||||
'JoinObject' => PolyJoinObject::class . '.Items',
|
||||
];
|
||||
|
||||
/**
|
||||
* Placeholder for missing belongs_many_many for polymorphic relation
|
||||
*
|
||||
* @todo Make this work for belongs_many_many
|
||||
* @return Generator|DataObject[]
|
||||
*/
|
||||
public function Objects()
|
||||
{
|
||||
foreach ($this->JoinObject() as $object) {
|
||||
$objectParent = $object->Parent();
|
||||
if ($objectParent && $objectParent->exists()) {
|
||||
yield $objectParent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
tests/php/ORM/ManyManyThroughListTest/PolyJoinObject.php
Normal file
21
tests/php/ORM/ManyManyThroughListTest/PolyJoinObject.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests\ManyManyThroughListTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class PolyJoinObject extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'ManyManyThroughListTest_PolyJoinObject';
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar',
|
||||
'Sort' => 'Int',
|
||||
];
|
||||
|
||||
private static $has_one = [
|
||||
'Parent' => DataObject::class, // Polymorphic parent
|
||||
'Child' => PolyItem::class,
|
||||
];
|
||||
}
|
28
tests/php/ORM/ManyManyThroughListTest/PolyObjectA.php
Normal file
28
tests/php/ORM/ManyManyThroughListTest/PolyObjectA.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests\ManyManyThroughListTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\ManyManyThroughList;
|
||||
|
||||
/**
|
||||
* @property string $Title
|
||||
* @method ManyManyThroughList Items()
|
||||
*/
|
||||
class PolyObjectA extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'ManyManyThroughListTest_PolyObjectA';
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar'
|
||||
];
|
||||
|
||||
private static $many_many = [
|
||||
'Items' => [
|
||||
'through' => PolyJoinObject::class,
|
||||
'from' => 'Parent',
|
||||
'to' => 'Child',
|
||||
]
|
||||
];
|
||||
}
|
28
tests/php/ORM/ManyManyThroughListTest/PolyObjectB.php
Normal file
28
tests/php/ORM/ManyManyThroughListTest/PolyObjectB.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests\ManyManyThroughListTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\ManyManyThroughList;
|
||||
|
||||
/**
|
||||
* @property string $Title
|
||||
* @method ManyManyThroughList Items()
|
||||
*/
|
||||
class PolyObjectB extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'ManyManyThroughListTest_PolyObjectB';
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar'
|
||||
];
|
||||
|
||||
private static $many_many = [
|
||||
'Items' => [
|
||||
'through' => PolyJoinObject::class,
|
||||
'from' => 'Parent',
|
||||
'to' => 'Child',
|
||||
]
|
||||
];
|
||||
}
|
@ -10,7 +10,7 @@ use SilverStripe\ORM\ManyManyThroughList;
|
||||
* Basic parent object
|
||||
*
|
||||
* @property string $Title
|
||||
* @method ManyManyThroughList Items()
|
||||
* @method ManyManyThroughList Items()
|
||||
*/
|
||||
class TestObject extends DataObject implements TestOnly
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user