mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Implement cascade_duplications
API Add DataObject::setComponent() API Support unary components as getter and setter fields API ManyManyList::add() now supports unsaved records ENHANCEMENT Animal farm
This commit is contained in:
parent
b07babb9da
commit
aa2c71424d
@ -420,6 +420,45 @@ If your object is versioned, cascade_deletes will also act as "cascade unpublish
|
||||
on a parent object will trigger unpublish on the child, similarly to how `owns` causes triggered publishing.
|
||||
See the [versioning docs](/developer_guides/versioning) for more information on ownership.
|
||||
|
||||
## Cascading duplications
|
||||
|
||||
Similar to `cascade_deletes` there is also a `cascade_duplicates` config which works in much the same way.
|
||||
When you invoke `$dataObject->duplicate()`, relation names specified by this config will be duplicated
|
||||
and saved against the new clone object.
|
||||
|
||||
Note that duplications will act differently depending on the kind of relation:
|
||||
- Exclusive relationships (e.g. has_many, belongs_to) will be explicitly duplicated.
|
||||
- Non-exclusive many_many will not be duplicated, but the mapping table values will instead
|
||||
be copied for this record.
|
||||
- Non-exclusive has_one relationships are not normally necessary to duplicate, since both parent and clone
|
||||
can normally share the same relation ID. However, if this is declared in `cascade_duplicates` any
|
||||
has one will be similarly duplicated as though it were an exclusive relationship.
|
||||
|
||||
For example:
|
||||
|
||||
```php
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class ParentObject extends DataObject {
|
||||
private static $many_many = [
|
||||
'RelatedChildren' => ChildObject::class,
|
||||
];
|
||||
private static $cascade_duplicates = [ 'RelatedChildren' ];
|
||||
}
|
||||
class ChildObject extends DataObject {
|
||||
}
|
||||
```
|
||||
|
||||
When duplicating objects you can disable recursive duplication by passing in `false` to the second
|
||||
argument of duplicate().
|
||||
|
||||
E.g.
|
||||
|
||||
```php
|
||||
$parent = ParentObject::get()->first();
|
||||
$dupe = $parent->duplicate(true, false);
|
||||
```
|
||||
|
||||
## Adding relations
|
||||
|
||||
Adding new items to a relations works the same, regardless if you're editing a **has_many** or a **many_many**. They are
|
||||
|
@ -139,7 +139,13 @@ trait FileUploadReceiver
|
||||
$items = new ArrayList();
|
||||
|
||||
// Determine format of presented data
|
||||
if (empty($value) && $record) {
|
||||
if ($value instanceof File) {
|
||||
$items = ArrayList::create([$value]);
|
||||
$value = null;
|
||||
} elseif ($value instanceof SS_List) {
|
||||
$items = $value;
|
||||
$value = null;
|
||||
} elseif (empty($value) && $record) {
|
||||
// If a record is given as a second parameter, but no submitted values,
|
||||
// then we should inspect this instead for the form values
|
||||
|
||||
@ -158,7 +164,7 @@ trait FileUploadReceiver
|
||||
// If directly passing a list then save the items directly
|
||||
$items = $record;
|
||||
}
|
||||
} elseif (!empty($value['Files'])) {
|
||||
} elseif (is_array($value) && !empty($value['Files'])) {
|
||||
// If value is given as an array (such as a posted form), extract File IDs from this
|
||||
$class = $this->getRelationAutosetClass();
|
||||
$items = DataObject::get($class)->byIDs($value['Files']);
|
||||
|
@ -1088,6 +1088,7 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li
|
||||
* list manipulation
|
||||
*
|
||||
* @param mixed $item
|
||||
* @param array|null $extraFields Any extra fields, if supported by this list
|
||||
*/
|
||||
public function add($item)
|
||||
{
|
||||
|
@ -259,7 +259,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*
|
||||
* @var DataObject[]
|
||||
*/
|
||||
protected $components;
|
||||
protected $components = [];
|
||||
|
||||
/**
|
||||
* Non-static cache of has_many and many_many relations that can't be written until this object is saved.
|
||||
@ -279,6 +279,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
private static $cascade_deletes = [];
|
||||
|
||||
/**
|
||||
* List of relations that should be cascade duplicate.
|
||||
* many_many duplications are shallow only.
|
||||
*
|
||||
* Note: If duplicating a many_many through you should refer to the
|
||||
* has_many intermediary relation instead, otherwise extra fields
|
||||
* will be omitted from the duplicated relation.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $cascade_duplicates = [];
|
||||
|
||||
/**
|
||||
* Get schema object
|
||||
*
|
||||
@ -292,7 +304,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
/**
|
||||
* Construct a new DataObject.
|
||||
*
|
||||
|
||||
* @param array|null $record Used internally for rehydrating an object from database content.
|
||||
* Bypasses setters on this class, and hence should not be used
|
||||
* for populating data on new records.
|
||||
@ -396,40 +407,105 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*
|
||||
* @param bool $doWrite Perform a write() operation before returning the object.
|
||||
* If this is true, it will create the duplicate in the database.
|
||||
* @param bool|string $manyMany Which many-many to duplicate. Set to true to duplicate all, false to duplicate none.
|
||||
* Alternatively set to the string of the relation config to duplicate
|
||||
* (supports 'many_many', or 'belongs_many_many')
|
||||
* @param array|null|false $relations List of relations to duplicate.
|
||||
* Will default to `cascade_duplicates` if null.
|
||||
* Set to 'false' to force none.
|
||||
* Set to specific array of names to duplicate to override these.
|
||||
* Note: If using versioned, this will additionally failover to `owns` config.
|
||||
* @return static A duplicate of this node. The exact type will be the type of this node.
|
||||
*/
|
||||
public function duplicate($doWrite = true, $manyMany = 'many_many')
|
||||
public function duplicate($doWrite = true, $relations = null)
|
||||
{
|
||||
// Handle legacy behaviour
|
||||
if (is_string($relations) || $relations === true) {
|
||||
if ($relations === true) {
|
||||
$relations = 'many_many';
|
||||
}
|
||||
Deprecation::notice('5.0', 'Use cascade_duplicates config instead of providing a string to duplicate()');
|
||||
$relations = array_keys($this->config()->get($relations)) ?: [];
|
||||
}
|
||||
|
||||
// Get duplicates
|
||||
if ($relations === null) {
|
||||
$relations = $this->config()->get('cascade_duplicates');
|
||||
}
|
||||
|
||||
// Create unsaved raw duplicate
|
||||
$map = $this->toMap();
|
||||
unset($map['Created']);
|
||||
/** @var static $clone */
|
||||
$clone = Injector::inst()->create(static::class, $map, false, $this->getSourceQueryParams());
|
||||
$clone->ID = 0;
|
||||
|
||||
$clone->invokeWithExtensions('onBeforeDuplicate', $this, $doWrite, $manyMany);
|
||||
if ($manyMany) {
|
||||
$this->duplicateManyManyRelations($this, $clone, $manyMany);
|
||||
// Note: Extensions such as versioned may update $relations here
|
||||
$clone->invokeWithExtensions('onBeforeDuplicate', $this, $doWrite, $relations);
|
||||
if ($relations) {
|
||||
$this->duplicateRelations($this, $clone, $relations);
|
||||
}
|
||||
if ($doWrite) {
|
||||
$clone->write();
|
||||
}
|
||||
$clone->invokeWithExtensions('onAfterDuplicate', $this, $doWrite, $manyMany);
|
||||
$clone->invokeWithExtensions('onAfterDuplicate', $this, $doWrite, $relations);
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the given relations from this object to the destination
|
||||
*
|
||||
* @param DataObject $sourceObject the source object to duplicate from
|
||||
* @param DataObject $destinationObject the destination object to populate with the duplicated relations
|
||||
* @param array $relations List of relations
|
||||
*/
|
||||
protected function duplicateRelations($sourceObject, $destinationObject, $relations)
|
||||
{
|
||||
// Get list of duplicable relation types
|
||||
$manyMany = $sourceObject->manyMany();
|
||||
$hasMany = $sourceObject->hasMany();
|
||||
$hasOne = $sourceObject->hasOne();
|
||||
$belongsTo = $sourceObject->belongsTo();
|
||||
|
||||
// Duplicate each relation based on type
|
||||
foreach ($relations as $relation) {
|
||||
switch (true) {
|
||||
case array_key_exists($relation, $manyMany): {
|
||||
$this->duplicateManyManyRelation($sourceObject, $destinationObject, $relation);
|
||||
break;
|
||||
}
|
||||
case array_key_exists($relation, $hasMany): {
|
||||
$this->duplicateHasManyRelation($sourceObject, $destinationObject, $relation);
|
||||
break;
|
||||
}
|
||||
case array_key_exists($relation, $hasOne): {
|
||||
$this->duplicateHasOneRelation($sourceObject, $destinationObject, $relation);
|
||||
break;
|
||||
}
|
||||
case array_key_exists($relation, $belongsTo): {
|
||||
$this->duplicateBelongsToRelation($sourceObject, $destinationObject, $relation);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
$sourceType = get_class($sourceObject);
|
||||
throw new InvalidArgumentException(
|
||||
"Cannot duplicate unknown relation {$relation} on parent type {$sourceType}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the many_many and belongs_many_many relations from one object to another instance of the name of object.
|
||||
*
|
||||
* @deprecated 4.1...5.0 Use duplicateRelations() instead
|
||||
* @param DataObject $sourceObject the source object to duplicate from
|
||||
* @param DataObject $destinationObject the destination object to populate with the duplicated relations
|
||||
* @param bool|string $filter
|
||||
*/
|
||||
protected function duplicateManyManyRelations($sourceObject, $destinationObject, $filter)
|
||||
{
|
||||
Deprecation::notice('5.0', 'Use duplicateRelations() instead');
|
||||
|
||||
// Get list of relations to duplicate
|
||||
if ($filter === 'many_many' || $filter === 'belongs_many_many') {
|
||||
$relations = $sourceObject->config()->get($filter);
|
||||
@ -444,25 +520,93 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicates a single many_many relation from one object to another
|
||||
* Duplicates a single many_many relation from one object to another.
|
||||
*
|
||||
* @param DataObject $sourceObject
|
||||
* @param DataObject $destinationObject
|
||||
* @param string $manyManyName
|
||||
* @param string $relation
|
||||
*/
|
||||
protected function duplicateManyManyRelation($sourceObject, $destinationObject, $manyManyName)
|
||||
protected function duplicateManyManyRelation($sourceObject, $destinationObject, $relation)
|
||||
{
|
||||
// Ensure this component exists on the destination side as well
|
||||
if (!static::getSchema()->manyManyComponent(get_class($destinationObject), $manyManyName)) {
|
||||
// Copy all components from source to destination
|
||||
$source = $sourceObject->getManyManyComponents($relation);
|
||||
$dest = $destinationObject->getManyManyComponents($relation);
|
||||
$extraFieldNames = $source->getExtraFields();
|
||||
foreach ($source as $item) {
|
||||
// Merge extra fields
|
||||
$extraFields = [];
|
||||
foreach ($extraFieldNames as $fieldName => $fieldType) {
|
||||
$extraFields[$fieldName] = $item->getField($fieldName);
|
||||
}
|
||||
$dest->add($item, $extraFields);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicates a single many_many relation from one object to another.
|
||||
*
|
||||
* @param DataObject $sourceObject
|
||||
* @param DataObject $destinationObject
|
||||
* @param string $relation
|
||||
*/
|
||||
protected function duplicateHasManyRelation($sourceObject, $destinationObject, $relation)
|
||||
{
|
||||
// Copy all components from source to destination
|
||||
$source = $sourceObject->getComponents($relation);
|
||||
$dest = $destinationObject->getComponents($relation);
|
||||
|
||||
/** @var DataObject $item */
|
||||
foreach ($source as $item) {
|
||||
// Don't write on duplicate; Wait until ParentID is available later.
|
||||
// writeRelations() will eventually write these records when converting
|
||||
// from UnsavedRelationList
|
||||
$clonedItem = $item->duplicate(false);
|
||||
$dest->add($clonedItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicates a single has_one relation from one object to another.
|
||||
* Note: Child object will be force written.
|
||||
*
|
||||
* @param DataObject $sourceObject
|
||||
* @param DataObject $destinationObject
|
||||
* @param string $relation
|
||||
*/
|
||||
protected function duplicateHasOneRelation($sourceObject, $destinationObject, $relation)
|
||||
{
|
||||
// Check if original object exists
|
||||
$item = $sourceObject->getComponent($relation);
|
||||
if (!$item->isInDB()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy all components from source to destination
|
||||
$source = $sourceObject->getManyManyComponents($manyManyName);
|
||||
$dest = $destinationObject->getManyManyComponents($manyManyName);
|
||||
foreach ($source as $item) {
|
||||
$dest->add($item);
|
||||
$clonedItem = $item->duplicate(false);
|
||||
$destinationObject->setComponent($relation, $clonedItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicates a single belongs_to relation from one object to another.
|
||||
* Note: This will force a write on both parent / child objects.
|
||||
*
|
||||
* @param DataObject $sourceObject
|
||||
* @param DataObject $destinationObject
|
||||
* @param string $relation
|
||||
*/
|
||||
protected function duplicateBelongsToRelation($sourceObject, $destinationObject, $relation)
|
||||
{
|
||||
// Check if original object exists
|
||||
$item = $sourceObject->getComponent($relation);
|
||||
if (!$item->isInDB()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$clonedItem = $item->duplicate(false);
|
||||
$destinationObject->setComponent($relation, $clonedItem);
|
||||
// After $clonedItem is assigned the appropriate FieldID / FieldClass, force write
|
||||
// @todo Write this component in onAfterWrite instead, assigning the FieldID then
|
||||
// https://github.com/silverstripe/silverstripe-framework/issues/7818
|
||||
$clonedItem->write();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -561,7 +705,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
parent::defineMethods();
|
||||
|
||||
if (static::class === self::class) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up accessors for joined items
|
||||
@ -639,7 +783,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return i18n::_t(
|
||||
static::class . '.PLURALS',
|
||||
$default,
|
||||
[ 'count' => $count ]
|
||||
['count' => $count]
|
||||
);
|
||||
}
|
||||
|
||||
@ -808,7 +952,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$parentObj = $relObj;
|
||||
$relObj = $relObj->$relation();
|
||||
// If the intermediate relationship objects haven't been created, then write them
|
||||
if ($i<sizeof($relations)-1 && !$relObj->ID || (!$relObj->ID && $parentObj !== $this)) {
|
||||
if ($i < sizeof($relations) - 1 && !$relObj->ID || (!$relObj->ID && $parentObj !== $this)) {
|
||||
$relObj->write();
|
||||
$relatedFieldName = $relation . "ID";
|
||||
$parentObj->$relatedFieldName = $relObj->ID;
|
||||
@ -1122,11 +1266,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
if ($defaults) {
|
||||
foreach ($defaults as $fieldName => $fieldValue) {
|
||||
// SRM 2007-03-06: Stricter check
|
||||
// SRM 2007-03-06: Stricter check
|
||||
if (!isset($this->$fieldName) || $this->$fieldName === null) {
|
||||
$this->$fieldName = $fieldValue;
|
||||
}
|
||||
// Set many-many defaults with an array of ids
|
||||
// Set many-many defaults with an array of ids
|
||||
if (is_array($fieldValue) && $this->getSchema()->manyManyComponent(static::class, $fieldName)) {
|
||||
/** @var ManyManyList $manyManyJoin */
|
||||
$manyManyJoin = $this->$fieldName();
|
||||
@ -1232,7 +1376,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
// Ensure this field pertains to this table
|
||||
$specification = $schema->fieldSpec($class, $fieldName, DataObjectSchema::DB_ONLY | DataObjectSchema::UNINHERITED);
|
||||
$specification = $schema->fieldSpec(
|
||||
$class,
|
||||
$fieldName,
|
||||
DataObjectSchema::DB_ONLY | DataObjectSchema::UNINHERITED
|
||||
);
|
||||
if (!$specification) {
|
||||
continue;
|
||||
}
|
||||
@ -1251,10 +1399,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if ($baseTable === $table) {
|
||||
$manipulation[$table]['fields']['LastEdited'] = $now;
|
||||
if ($isNewRecord) {
|
||||
$manipulation[$table]['fields']['Created']
|
||||
= empty($this->record['Created'])
|
||||
? $now
|
||||
: $this->record['Created'];
|
||||
$manipulation[$table]['fields']['Created'] = empty($this->record['Created'])
|
||||
? $now
|
||||
: $this->record['Created'];
|
||||
$manipulation[$table]['fields']['ClassName'] = static::class;
|
||||
}
|
||||
}
|
||||
@ -1325,7 +1472,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* - $this->onBeforeWrite() gets called beforehand.
|
||||
* - Extensions such as Versioned will ammend the database-write to ensure that a version is saved.
|
||||
*
|
||||
* @uses DataExtension->augmentWrite()
|
||||
* @uses DataExtension->augmentWrite()
|
||||
*
|
||||
* @param boolean $showDebug Show debugging information
|
||||
* @param boolean $forceInsert Run INSERT command rather than UPDATE, even if record already exists
|
||||
@ -1414,10 +1561,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function writeComponents($recursive = false)
|
||||
{
|
||||
if ($this->components) {
|
||||
foreach ($this->components as $component) {
|
||||
$component->write(false, false, false, $recursive);
|
||||
}
|
||||
foreach ($this->components as $component) {
|
||||
$component->write(false, false, false, $recursive);
|
||||
}
|
||||
|
||||
if ($join = $this->getJoin()) {
|
||||
@ -1431,7 +1576,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* Delete this data object.
|
||||
* $this->onBeforeDelete() gets called.
|
||||
* Note that in Versioned objects, both Stage and Live will be deleted.
|
||||
* @uses DataExtension->augmentSQL()
|
||||
* @uses DataExtension->augmentSQL()
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
@ -1501,7 +1646,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a component object from a one to one relationship, as a DataObject.
|
||||
* Return a unary component object from a one to one relationship, as a DataObject.
|
||||
* If no component is available, an 'empty component' will be returned for
|
||||
* non-polymorphic relations, or for polymorphic relations with a class set.
|
||||
*
|
||||
@ -1518,7 +1663,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$schema = static::getSchema();
|
||||
if ($class = $schema->hasOneComponent(static::class, $componentName)) {
|
||||
$joinField = $componentName . 'ID';
|
||||
$joinID = $this->getField($joinField);
|
||||
$joinID = $this->getField($joinField);
|
||||
|
||||
// Extract class name for polymorphic relations
|
||||
if ($class === self::class) {
|
||||
@ -1582,6 +1727,52 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return $component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign an item to the given component
|
||||
*
|
||||
* @param string $componentName
|
||||
* @param DataObject|null $item
|
||||
* @return $this
|
||||
*/
|
||||
public function setComponent($componentName, $item)
|
||||
{
|
||||
// Validate component
|
||||
$schema = static::getSchema();
|
||||
if ($class = $schema->hasOneComponent(static::class, $componentName)) {
|
||||
// Force item to be written if not by this point
|
||||
// @todo This could be lazy-written in a beforeWrite hook, but force write here for simplicity
|
||||
// https://github.com/silverstripe/silverstripe-framework/issues/7818
|
||||
if ($item && !$item->isInDB()) {
|
||||
$item->write();
|
||||
}
|
||||
|
||||
// Update local ID
|
||||
$joinField = $componentName . 'ID';
|
||||
$this->setField($joinField, $item ? $item->ID : null);
|
||||
// Update Class (Polymorphic has_one)
|
||||
// Extract class name for polymorphic relations
|
||||
if ($class === self::class) {
|
||||
$this->setField($componentName . 'Class', $item ? get_class($item) : null);
|
||||
}
|
||||
} elseif ($class = $schema->belongsToComponent(static::class, $componentName)) {
|
||||
if ($item) {
|
||||
// For belongs_to, add to has_one on other component
|
||||
$joinField = $schema->getRemoteJoinField(static::class, $componentName, 'belongs_to', $polymorphic);
|
||||
if (!$polymorphic) {
|
||||
$joinField = substr($joinField, 0, -2);
|
||||
}
|
||||
$item->setComponent($joinField, $this);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidArgumentException(
|
||||
"DataObject->setComponent(): Could not find component '$componentName'."
|
||||
);
|
||||
}
|
||||
|
||||
$this->components[$componentName] = $item;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a one-to-many relation as a HasManyList
|
||||
*
|
||||
@ -1655,7 +1846,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$remoteClass = $candidates[$relationName];
|
||||
|
||||
// If dot notation is present, extract just the first part that contains the class.
|
||||
if (($fieldPos = strpos($remoteClass, '.'))!==false) {
|
||||
if (($fieldPos = strpos($remoteClass, '.')) !== false) {
|
||||
return substr($remoteClass, 0, $fieldPos);
|
||||
}
|
||||
|
||||
@ -1752,7 +1943,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
case 'belongs_to':
|
||||
case 'has_many': {
|
||||
// These relations must have a has_one on the other end, so find it
|
||||
$joinField = $schema->getRemoteJoinField($remoteClass, $remoteRelation, $relationType, $polymorphic);
|
||||
$joinField = $schema->getRemoteJoinField(
|
||||
$remoteClass,
|
||||
$remoteRelation,
|
||||
$relationType,
|
||||
$polymorphic
|
||||
);
|
||||
if ($polymorphic) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
"%s cannot generate opposite component of relation %s.%s, as the other end appears" .
|
||||
@ -1806,7 +2002,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* Returns a many-to-many component, as a ManyManyList.
|
||||
* @param string $componentName Name of the many-many component
|
||||
* @param int|array $id Optional ID for parent of this relation, if not the current record
|
||||
* @return RelationList|UnsavedRelationList The set of components
|
||||
* @return ManyManyList|UnsavedRelationList The set of components
|
||||
*/
|
||||
public function getManyManyComponents($componentName, $id = null)
|
||||
{
|
||||
@ -1827,7 +2023,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if (!$id) {
|
||||
if (!isset($this->unsavedRelations[$componentName])) {
|
||||
$this->unsavedRelations[$componentName] =
|
||||
new UnsavedRelationList($manyManyComponent['parentClass'], $componentName, $manyManyComponent['childClass']);
|
||||
new UnsavedRelationList(
|
||||
$manyManyComponent['parentClass'],
|
||||
$componentName,
|
||||
$manyManyComponent['childClass']
|
||||
);
|
||||
}
|
||||
return $this->unsavedRelations[$componentName];
|
||||
}
|
||||
@ -2180,9 +2380,15 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$tableClass = $this->record[$field . '_Lazy'];
|
||||
$this->loadLazyFields($tableClass);
|
||||
}
|
||||
$schema = static::getSchema();
|
||||
|
||||
// Support unary relations as fields
|
||||
if ($schema->unaryComponent(static::class, $field)) {
|
||||
return $this->getComponent($field);
|
||||
}
|
||||
|
||||
// In case of complex fields, return the DBField object
|
||||
if (static::getSchema()->compositeField(static::class, $field)) {
|
||||
if ($schema->compositeField(static::class, $field)) {
|
||||
$this->record[$field] = $this->dbObject($field);
|
||||
}
|
||||
|
||||
@ -2331,9 +2537,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if ($fields) {
|
||||
foreach ($fields as $name => $level) {
|
||||
$changedFields[$name] = array(
|
||||
'before' => array_key_exists($name, $this->original) ? $this->original[$name] : null,
|
||||
'after' => array_key_exists($name, $this->record) ? $this->record[$name] : null,
|
||||
'level' => $level
|
||||
'before' => array_key_exists($name, $this->original) ? $this->original[$name] : null,
|
||||
'after' => array_key_exists($name, $this->record) ? $this->record[$name] : null,
|
||||
'level' => $level
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -2382,6 +2588,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$this->loadLazyFields($tableClass);
|
||||
}
|
||||
|
||||
// Support component assignent via field setter
|
||||
$schema = static::getSchema();
|
||||
if ($schema->unaryComponent(static::class, $fieldName)) {
|
||||
// Assign component directly
|
||||
if (is_null($val) || $val instanceof DataObject) {
|
||||
return $this->setComponent($fieldName, $val);
|
||||
}
|
||||
// Assign by ID instead of object
|
||||
unset($this->components[$fieldName]);
|
||||
$fieldName .= 'ID';
|
||||
}
|
||||
|
||||
// Situation 1: Passing an DBField
|
||||
if ($val instanceof DBField) {
|
||||
$val->setName($fieldName);
|
||||
@ -2396,7 +2614,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// Situation 2: Passing a literal or non-DBField object
|
||||
} else {
|
||||
// If this is a proper database field, we shouldn't be getting non-DBField objects
|
||||
if (is_object($val) && static::getSchema()->fieldSpec(static::class, $fieldName)) {
|
||||
if (is_object($val) && $schema->fieldSpec(static::class, $fieldName)) {
|
||||
throw new InvalidArgumentException('DataObject::setField: passed an object that is not a DBField');
|
||||
}
|
||||
|
||||
@ -2483,8 +2701,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$schema = static::getSchema();
|
||||
return (
|
||||
array_key_exists($field, $this->record)
|
||||
|| array_key_exists($field, $this->components)
|
||||
|| $schema->fieldSpec(static::class, $field)
|
||||
|| (substr($field, -2) == 'ID') && $schema->hasOneComponent(static::class, substr($field, 0, -2))
|
||||
|| $schema->unaryComponent(static::class, $field)
|
||||
|| $this->hasMethod("get{$field}")
|
||||
);
|
||||
}
|
||||
@ -2717,7 +2936,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
// Inspect relation type
|
||||
if (ClassInfo::hasMethod($component, $relation)) {
|
||||
$component = $component->$relation();
|
||||
$component = $component->$relation();
|
||||
} elseif ($component instanceof Relation || $component instanceof DataList) {
|
||||
// $relation could either be a field (aggregate), or another relation
|
||||
$singleton = DataObject::singleton($component->dataClass());
|
||||
@ -3120,7 +3339,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
],
|
||||
$childField => [
|
||||
'type' => 'index',
|
||||
'name' =>$childField,
|
||||
'name' => $childField,
|
||||
'columns' => [$childField],
|
||||
],
|
||||
];
|
||||
@ -3174,7 +3393,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$labels = $this->fieldLabels();
|
||||
|
||||
// fallback to summary fields (unless empty array is explicitly specified)
|
||||
if (! $fields && ! is_array($fields)) {
|
||||
if (!$fields && !is_array($fields)) {
|
||||
$summaryFields = array_keys($this->summaryFields());
|
||||
$fields = array();
|
||||
|
||||
@ -3288,13 +3507,28 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
];
|
||||
if ($includerelations) {
|
||||
$types['has_one'] = (array)Config::inst()->get($ancestorClass, 'has_one', Config::UNINHERITED);
|
||||
$types['has_many'] = (array)Config::inst()->get($ancestorClass, 'has_many', Config::UNINHERITED);
|
||||
$types['many_many'] = (array)Config::inst()->get($ancestorClass, 'many_many', Config::UNINHERITED);
|
||||
$types['belongs_many_many'] = (array)Config::inst()->get($ancestorClass, 'belongs_many_many', Config::UNINHERITED);
|
||||
$types['has_many'] = (array)Config::inst()->get(
|
||||
$ancestorClass,
|
||||
'has_many',
|
||||
Config::UNINHERITED
|
||||
);
|
||||
$types['many_many'] = (array)Config::inst()->get(
|
||||
$ancestorClass,
|
||||
'many_many',
|
||||
Config::UNINHERITED
|
||||
);
|
||||
$types['belongs_many_many'] = (array)Config::inst()->get(
|
||||
$ancestorClass,
|
||||
'belongs_many_many',
|
||||
Config::UNINHERITED
|
||||
);
|
||||
}
|
||||
foreach ($types as $type => $attrs) {
|
||||
foreach ($attrs as $name => $spec) {
|
||||
$autoLabels[$name] = _t("{$ancestorClass}.{$type}_{$name}", FormField::name_to_label($name));
|
||||
$autoLabels[$name] = _t(
|
||||
"{$ancestorClass}.{$type}_{$name}",
|
||||
FormField::name_to_label($name)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3428,6 +3662,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
{
|
||||
self::$subclass_access = false;
|
||||
}
|
||||
|
||||
public static function enable_subclass_access()
|
||||
{
|
||||
self::$subclass_access = true;
|
||||
@ -3523,7 +3758,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*
|
||||
* Note that you cannot have a has_one and belongs_to relationship with the same name.
|
||||
*
|
||||
* @var array
|
||||
* @var array
|
||||
* @config
|
||||
*/
|
||||
private static $has_one = [];
|
||||
|
@ -908,6 +908,19 @@ class DataObjectSchema
|
||||
return $classOnly ? $belongsToClass : $belongsTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check class for any unary component
|
||||
*
|
||||
* Alias for hasOneComponent() ?: belongsToComponent()
|
||||
* @param string $class
|
||||
* @param string $component
|
||||
* @return string|null
|
||||
*/
|
||||
public function unaryComponent($class, $component)
|
||||
{
|
||||
return $this->hasOneComponent($class, $component) ?: $this->belongsToComponent($class, $component);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $parentClass Parent class name
|
||||
|
@ -225,6 +225,10 @@ class ManyManyList extends RelationList
|
||||
if (is_numeric($item)) {
|
||||
$itemID = $item;
|
||||
} elseif ($item instanceof $this->dataClass) {
|
||||
// Ensure record is saved
|
||||
if (!$item->isInDB()) {
|
||||
$item->write();
|
||||
}
|
||||
$itemID = $item->ID;
|
||||
} else {
|
||||
throw new InvalidArgumentException(
|
||||
@ -232,7 +236,7 @@ class ManyManyList extends RelationList
|
||||
);
|
||||
}
|
||||
if (empty($itemID)) {
|
||||
throw new InvalidArgumentException("ManyManyList::add() doesn't accept unsaved records");
|
||||
throw new InvalidArgumentException("ManyManyList::add() couldn't add this record");
|
||||
}
|
||||
|
||||
// Validate foreignID
|
||||
|
@ -82,10 +82,6 @@ class UnsavedRelationList extends ArrayList implements Relation
|
||||
public function changeToList(RelationList $list)
|
||||
{
|
||||
foreach ($this->items as $key => $item) {
|
||||
if (is_object($item)) {
|
||||
/** @var DataObject $item */
|
||||
$item->write();
|
||||
}
|
||||
$list->add($item, $this->extraFields[$key]);
|
||||
}
|
||||
}
|
||||
|
@ -2,32 +2,30 @@
|
||||
|
||||
namespace SilverStripe\ORM\Tests;
|
||||
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
|
||||
class DataObjectDuplicationTest extends SapphireTest
|
||||
{
|
||||
protected static $fixture_file = 'DataObjectDuplicationTest.yml';
|
||||
|
||||
protected $usesDatabase = true;
|
||||
|
||||
protected static $extra_dataobjects = array(
|
||||
DataObjectDuplicationTest\Class1::class,
|
||||
DataObjectDuplicationTest\Class2::class,
|
||||
DataObjectDuplicationTest\Class3::class,
|
||||
DataObjectDuplicationTest\Class4::class,
|
||||
);
|
||||
protected static $extra_dataobjects = [
|
||||
DataObjectDuplicationTest\Antelope::class,
|
||||
DataObjectDuplicationTest\Bobcat::class,
|
||||
DataObjectDuplicationTest\Caribou::class,
|
||||
DataObjectDuplicationTest\Dingo::class,
|
||||
DataObjectDuplicationTest\Elephant::class,
|
||||
DataObjectDuplicationTest\Frog::class,
|
||||
DataObjectDuplicationTest\Goat::class,
|
||||
];
|
||||
|
||||
public function testDuplicate()
|
||||
{
|
||||
DBDatetime::set_mock_now('2016-01-01 01:01:01');
|
||||
$orig = new DataObjectDuplicationTest\Class1();
|
||||
$orig->text = 'foo';
|
||||
$orig->write();
|
||||
DBDatetime::set_mock_now('2016-01-02 01:01:01');
|
||||
/** @var DataObjectDuplicationTest\Antelope $orig */
|
||||
$orig = $this->objFromFixture(DataObjectDuplicationTest\Antelope::class, 'one');
|
||||
/** @var DataObjectDuplicationTest\Antelope $duplicate */
|
||||
$duplicate = $orig->duplicate();
|
||||
$this->assertInstanceOf(
|
||||
DataObjectDuplicationTest\Class1::class,
|
||||
DataObjectDuplicationTest\Antelope::class,
|
||||
$duplicate,
|
||||
'Creates the correct type'
|
||||
);
|
||||
@ -36,198 +34,79 @@ class DataObjectDuplicationTest extends SapphireTest
|
||||
$orig->ID,
|
||||
'Creates a unique record'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'foo',
|
||||
$duplicate->text,
|
||||
'Copies fields'
|
||||
);
|
||||
$this->assertEquals(
|
||||
2,
|
||||
DataObjectDuplicationTest\Class1::get()->Count(),
|
||||
'Only creates a single duplicate'
|
||||
);
|
||||
$this->assertEquals(DBDatetime::now()->Nice(), $duplicate->dbObject('Created')->Nice());
|
||||
$this->assertNotEquals($orig->dbObject('Created')->Nice(), $duplicate->dbObject('Created')->Nice());
|
||||
}
|
||||
|
||||
public function testDuplicateHasOne()
|
||||
{
|
||||
$relationObj = new DataObjectDuplicationTest\Class1();
|
||||
$relationObj->text = 'class1';
|
||||
$relationObj->write();
|
||||
|
||||
$orig = new DataObjectDuplicationTest\Class2();
|
||||
$orig->text = 'class2';
|
||||
$orig->oneID = $relationObj->ID;
|
||||
$orig->write();
|
||||
|
||||
$duplicate = $orig->duplicate();
|
||||
$this->assertEquals(
|
||||
$relationObj->ID,
|
||||
$duplicate->oneID,
|
||||
'Copies has_one relationship'
|
||||
);
|
||||
$this->assertEquals(
|
||||
2,
|
||||
DataObjectDuplicationTest\Class2::get()->Count(),
|
||||
'Only creates a single duplicate'
|
||||
);
|
||||
$this->assertEquals(
|
||||
1,
|
||||
DataObjectDuplicationTest\Class1::get()->Count(),
|
||||
'Does not create duplicate of has_one relationship'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function testDuplicateManyManyClasses()
|
||||
{
|
||||
//create new test classes below
|
||||
$one = new DataObjectDuplicationTest\Class1();
|
||||
$two = new DataObjectDuplicationTest\Class2();
|
||||
$three = new DataObjectDuplicationTest\Class3();
|
||||
|
||||
//set some simple fields
|
||||
$text1 = "Test Text 1";
|
||||
$text2 = "Test Text 2";
|
||||
$text3 = "Test Text 3";
|
||||
$one->text = $text1;
|
||||
$two->text = $text2;
|
||||
$three->text = $text3;
|
||||
|
||||
//write the to DB
|
||||
$one->write();
|
||||
$two->write();
|
||||
$three->write();
|
||||
|
||||
//create relations
|
||||
$one->twos()->add($two);
|
||||
$one->threes()->add($three);
|
||||
|
||||
$one = DataObject::get_by_id(DataObjectDuplicationTest\Class1::class, $one->ID);
|
||||
$two = DataObject::get_by_id(DataObjectDuplicationTest\Class2::class, $two->ID);
|
||||
$three = DataObject::get_by_id(DataObjectDuplicationTest\Class3::class, $three->ID);
|
||||
|
||||
//test duplication
|
||||
$oneCopy = $one->duplicate(true, true);
|
||||
$twoCopy = $two->duplicate(true, true);
|
||||
$threeCopy = $three->duplicate(true, true);
|
||||
|
||||
$oneCopy = DataObject::get_by_id(DataObjectDuplicationTest\Class1::class, $oneCopy->ID);
|
||||
$twoCopy = DataObject::get_by_id(DataObjectDuplicationTest\Class2::class, $twoCopy->ID);
|
||||
$threeCopy = DataObject::get_by_id(DataObjectDuplicationTest\Class3::class, $threeCopy->ID);
|
||||
|
||||
$this->assertNotNull($oneCopy, "Copy of 1 exists");
|
||||
$this->assertNotNull($twoCopy, "Copy of 2 exists");
|
||||
$this->assertNotNull($threeCopy, "Copy of 3 exists");
|
||||
|
||||
$this->assertEquals($text1, $oneCopy->text);
|
||||
$this->assertEquals($text2, $twoCopy->text);
|
||||
$this->assertEquals($text3, $threeCopy->text);
|
||||
|
||||
$this->assertNotEquals(
|
||||
$one->twos()->Count(),
|
||||
$oneCopy->twos()->Count(),
|
||||
"Many-to-one relation not copied (has_many)"
|
||||
);
|
||||
$this->assertEquals(
|
||||
$one->threes()->Count(),
|
||||
$oneCopy->threes()->Count(),
|
||||
"Object has the correct number of relations"
|
||||
);
|
||||
$this->assertEquals(
|
||||
$three->ones()->Count(),
|
||||
$threeCopy->ones()->Count(),
|
||||
"Object has the correct number of relations"
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
$one->ID,
|
||||
$twoCopy->one()->ID,
|
||||
"Match between relation of copy and the original"
|
||||
);
|
||||
$this->assertEquals(
|
||||
0,
|
||||
$oneCopy->twos()->Count(),
|
||||
"Many-to-one relation not copied (has_many)"
|
||||
);
|
||||
$this->assertEquals(
|
||||
$three->ID,
|
||||
$oneCopy->threes()->First()->ID,
|
||||
"Match between relation of copy and the original"
|
||||
);
|
||||
$this->assertEquals(
|
||||
$one->ID,
|
||||
$threeCopy->ones()->First()->ID,
|
||||
"Match between relation of copy and the original"
|
||||
);
|
||||
}
|
||||
|
||||
public function testDuplicateManyManyFiltered()
|
||||
{
|
||||
$parent = new DataObjectDuplicationTest\Class4();
|
||||
$parent->Title = 'Parent';
|
||||
$parent->write();
|
||||
|
||||
$child = new DataObjectDuplicationTest\Class4();
|
||||
$child->Title = 'Child';
|
||||
$child->write();
|
||||
|
||||
$grandChild = new DataObjectDuplicationTest\Class4();
|
||||
$grandChild->Title = 'GrandChild';
|
||||
$grandChild->write();
|
||||
|
||||
$parent->Children()->add($child);
|
||||
$child->Children()->add($grandChild);
|
||||
|
||||
// Duplcating $child should only duplicate grandchild
|
||||
$childDuplicate = $child->duplicate(true, 'many_many');
|
||||
$this->assertEquals(0, $childDuplicate->Parents()->count());
|
||||
// Check 'bobcats' relation duplicated
|
||||
$twoOne = $this->objFromFixture(DataObjectDuplicationTest\Bobcat::class, 'one');
|
||||
$twoTwo = $this->objFromFixture(DataObjectDuplicationTest\Bobcat::class, 'two');
|
||||
$this->assertListEquals(
|
||||
[['Title' => 'GrandChild']],
|
||||
$childDuplicate->Children()
|
||||
[
|
||||
['Title' => 'Bobcat two'],
|
||||
['Title' => 'Bobcat three'],
|
||||
],
|
||||
$duplicate->bobcats()
|
||||
);
|
||||
|
||||
// Duplicate belongs_many_many only
|
||||
$belongsDuplicate = $child->duplicate(true, 'belongs_many_many');
|
||||
$this->assertEquals(0, $belongsDuplicate->Children()->count());
|
||||
$this->assertListEquals(
|
||||
[['Title' => 'Parent']],
|
||||
$belongsDuplicate->Parents()
|
||||
$this->assertEmpty(
|
||||
array_intersect(
|
||||
$orig->bobcats()->getIDList(),
|
||||
$duplicate->bobcats()->getIDList()
|
||||
)
|
||||
);
|
||||
/** @var DataObjectDuplicationTest\Bobcat $twoTwoDuplicate */
|
||||
$twoTwoDuplicate = $duplicate->bobcats()->filter('Title', 'Bobcat two')->first();
|
||||
$this->assertNotEmpty($twoTwoDuplicate);
|
||||
$this->assertNotEquals($twoTwo->ID, $twoTwoDuplicate->ID);
|
||||
|
||||
// Duplicate all
|
||||
$allDuplicate = $child->duplicate(true, true);
|
||||
// Check 'bobcats.self' relation duplicated
|
||||
/** @var DataObjectDuplicationTest\Bobcat $twoOneDuplicate */
|
||||
$twoOneDuplicate = $twoTwoDuplicate->self();
|
||||
$this->assertNotEmpty($twoOneDuplicate);
|
||||
$this->assertNotEquals($twoOne->ID, $twoOneDuplicate->ID);
|
||||
|
||||
// Ensure 'bobcats.seven' instance is not duplicated
|
||||
$sevenOne = $this->objFromFixture(DataObjectDuplicationTest\Goat::class, 'one');
|
||||
$sevenTwo = $this->objFromFixture(DataObjectDuplicationTest\Goat::class, 'two');
|
||||
$this->assertEquals($sevenOne->ID, $twoOneDuplicate->goat()->ID);
|
||||
$this->assertEquals($sevenTwo->ID, $twoTwoDuplicate->goat()->ID);
|
||||
|
||||
// Ensure that 'caribou' many_many list exists on both, but only the mapping table is duplicated
|
||||
// many_many_extraFields are also duplicated
|
||||
$caribouList = [
|
||||
[
|
||||
'Title' => 'Caribou one',
|
||||
'Sort' => 4,
|
||||
],
|
||||
[
|
||||
'Title' => 'Caribou two',
|
||||
'Sort' => 5,
|
||||
],
|
||||
];
|
||||
// Original and duplicate lists have the same content
|
||||
$this->assertListEquals(
|
||||
[['Title' => 'Parent']],
|
||||
$allDuplicate->Parents()
|
||||
$caribouList,
|
||||
$orig->caribou()
|
||||
);
|
||||
$this->assertListEquals(
|
||||
[['Title' => 'GrandChild']],
|
||||
$allDuplicate->Children()
|
||||
$caribouList,
|
||||
$duplicate->caribou()
|
||||
);
|
||||
// Ids of each record are the same (only mapping content is duplicated)
|
||||
$this->assertEquals(
|
||||
$orig->caribou()->getIDList(),
|
||||
$duplicate->caribou()->getIDList()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test duplication of UnsavedRelations
|
||||
*/
|
||||
public function testDuplicateUnsaved()
|
||||
{
|
||||
$one = new DataObjectDuplicationTest\Class1();
|
||||
$one->text = "Test Text 1";
|
||||
$three = new DataObjectDuplicationTest\Class3();
|
||||
$three->text = "Test Text 3";
|
||||
$one->threes()->add($three);
|
||||
$this->assertListEquals(
|
||||
[['text' => 'Test Text 3']],
|
||||
$one->threes()
|
||||
);
|
||||
// Test duplicate
|
||||
$dupe = $one->duplicate(false, true);
|
||||
$this->assertEquals('Test Text 1', $dupe->text);
|
||||
$this->assertListEquals(
|
||||
[['text' => 'Test Text 3']],
|
||||
$dupe->threes()
|
||||
);
|
||||
// Ensure 'five' belongs_to is duplicated
|
||||
$fiveOne = $this->objFromFixture(DataObjectDuplicationTest\Elephant::class, 'one');
|
||||
$fiveOneDuplicate = $duplicate->elephant();
|
||||
$this->assertNotEmpty($fiveOneDuplicate);
|
||||
$this->assertEquals('Elephant one', $fiveOneDuplicate->Title);
|
||||
$this->assertNotEquals($fiveOne->ID, $fiveOneDuplicate->ID);
|
||||
|
||||
// Ensure 'five.Child' is duplicated
|
||||
$sixOne = $this->objFromFixture(DataObjectDuplicationTest\Frog::class, 'one');
|
||||
$sixOneDuplicate = $fiveOneDuplicate->Child();
|
||||
$this->assertNotEmpty($sixOneDuplicate);
|
||||
$this->assertEquals('Frog one', $sixOneDuplicate->Title);
|
||||
$this->assertNotEquals($sixOne->ID, $sixOneDuplicate->ID);
|
||||
}
|
||||
}
|
||||
|
42
tests/php/ORM/DataObjectDuplicationTest.yml
Normal file
42
tests/php/ORM/DataObjectDuplicationTest.yml
Normal file
@ -0,0 +1,42 @@
|
||||
SilverStripe\ORM\Tests\DataObjectDuplicationTest\Antelope:
|
||||
one:
|
||||
Title: 'Antelope one'
|
||||
SilverStripe\ORM\Tests\DataObjectDuplicationTest\Goat:
|
||||
one:
|
||||
Title: 'Goat one'
|
||||
two:
|
||||
Title: 'Goat two'
|
||||
SilverStripe\ORM\Tests\DataObjectDuplicationTest\Bobcat:
|
||||
one:
|
||||
Title: 'Bobcat one'
|
||||
goat: =>SilverStripe\ORM\Tests\DataObjectDuplicationTest\Goat.one
|
||||
two:
|
||||
Title: 'Bobcat two'
|
||||
antelope: =>SilverStripe\ORM\Tests\DataObjectDuplicationTest\Antelope.one
|
||||
self: =>SilverStripe\ORM\Tests\DataObjectDuplicationTest\Bobcat.one
|
||||
goat: =>SilverStripe\ORM\Tests\DataObjectDuplicationTest\Goat.two
|
||||
three:
|
||||
Title: 'Bobcat three'
|
||||
antelope: =>SilverStripe\ORM\Tests\DataObjectDuplicationTest\Antelope.one
|
||||
SilverStripe\ORM\Tests\DataObjectDuplicationTest\Caribou:
|
||||
one:
|
||||
Title: 'Caribou one'
|
||||
two:
|
||||
Title: 'Caribou two'
|
||||
DataObjectDuplicateTest_Antelope_caribou:
|
||||
one:
|
||||
DataObjectDuplicateTest_AntelopeID: =>SilverStripe\ORM\Tests\DataObjectDuplicationTest\Antelope.one
|
||||
DataObjectDuplicateTest_CaribouID: =>SilverStripe\ORM\Tests\DataObjectDuplicationTest\Caribou.one
|
||||
Sort: 4
|
||||
two:
|
||||
DataObjectDuplicateTest_AntelopeID: =>SilverStripe\ORM\Tests\DataObjectDuplicationTest\Antelope.one
|
||||
DataObjectDuplicateTest_CaribouID: =>SilverStripe\ORM\Tests\DataObjectDuplicationTest\Caribou.two
|
||||
Sort: 5
|
||||
SilverStripe\ORM\Tests\DataObjectDuplicationTest\Frog:
|
||||
one:
|
||||
Title: 'Frog one'
|
||||
SilverStripe\ORM\Tests\DataObjectDuplicationTest\Elephant:
|
||||
one:
|
||||
Title: 'Elephant one'
|
||||
Parent: =>SilverStripe\ORM\Tests\DataObjectDuplicationTest\Antelope.one
|
||||
Child: =>SilverStripe\ORM\Tests\DataObjectDuplicationTest\Frog.one
|
46
tests/php/ORM/DataObjectDuplicationTest/Antelope.php
Normal file
46
tests/php/ORM/DataObjectDuplicationTest/Antelope.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests\DataObjectDuplicationTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\HasManyList;
|
||||
use SilverStripe\ORM\ManyManyList;
|
||||
|
||||
/**
|
||||
* @method HasManyList bobcats()
|
||||
* @method ManyManyList caribou()
|
||||
* @method Elephant elephant()
|
||||
*/
|
||||
class Antelope extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'DataObjectDuplicateTest_Antelope';
|
||||
|
||||
private static $cascade_duplicates = [
|
||||
'bobcats',
|
||||
'caribou',
|
||||
'elephant',
|
||||
];
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar',
|
||||
];
|
||||
|
||||
private static $has_many = [
|
||||
'bobcats' => Bobcat::class,
|
||||
];
|
||||
|
||||
private static $many_many = [
|
||||
'caribou' => Caribou::class,
|
||||
];
|
||||
|
||||
private static $many_many_extraFields = [
|
||||
'caribou' => [
|
||||
'Sort' => 'Int',
|
||||
],
|
||||
];
|
||||
|
||||
private static $belongs_to = [
|
||||
'elephant' => Elephant::class,
|
||||
];
|
||||
}
|
30
tests/php/ORM/DataObjectDuplicationTest/Bobcat.php
Normal file
30
tests/php/ORM/DataObjectDuplicationTest/Bobcat.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests\DataObjectDuplicationTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
/**
|
||||
* @method Antelope antelope()
|
||||
* @method Bobcat self()
|
||||
* @method Goat goat()
|
||||
*/
|
||||
class Bobcat extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'DataObjectDuplicateTest_Bobcat';
|
||||
|
||||
private static $cascade_duplicates = [
|
||||
'self',
|
||||
];
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar',
|
||||
];
|
||||
|
||||
private static $has_one = [
|
||||
'antelope' => Antelope::class,
|
||||
'self' => Bobcat::class,
|
||||
'goat' => Goat::class,
|
||||
];
|
||||
}
|
23
tests/php/ORM/DataObjectDuplicationTest/Caribou.php
Normal file
23
tests/php/ORM/DataObjectDuplicationTest/Caribou.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests\DataObjectDuplicationTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\ManyManyList;
|
||||
|
||||
/**
|
||||
* @method ManyManyList antelopes()
|
||||
*/
|
||||
class Caribou extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'DataObjectDuplicateTest_Caribou';
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar',
|
||||
];
|
||||
|
||||
private static $belongs_many_many = [
|
||||
'antelopes' => Antelope::class,
|
||||
];
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests\DataObjectDuplicationTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class Class1 extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'DataObjectDuplicateTest_Class1';
|
||||
|
||||
private static $db = array(
|
||||
'text' => 'Varchar'
|
||||
);
|
||||
|
||||
private static $has_many = array(
|
||||
'twos' => Class2::class
|
||||
);
|
||||
|
||||
private static $many_many = array(
|
||||
'threes' => Class3::class
|
||||
);
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests\DataObjectDuplicationTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class Class2 extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'DataObjectDuplicateTest_Class2';
|
||||
|
||||
private static $db = array(
|
||||
'text' => 'Varchar'
|
||||
);
|
||||
|
||||
private static $has_one = array(
|
||||
'one' => Class1::class
|
||||
);
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests\DataObjectDuplicationTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class Class3 extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'DataObjectDuplicateTest_Class3';
|
||||
|
||||
private static $db = array(
|
||||
'text' => 'Varchar'
|
||||
);
|
||||
|
||||
private static $belongs_many_many = array(
|
||||
'ones' => Class1::class
|
||||
);
|
||||
}
|
@ -11,19 +11,23 @@ use SilverStripe\ORM\ManyManyList;
|
||||
* @method ManyManyList Children()
|
||||
* @method ManyManyList Parents()
|
||||
*/
|
||||
class Class4 extends DataObject implements TestOnly
|
||||
class Dingo extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'DataObjectDuplicateTest_Class4';
|
||||
private static $table_name = 'DataObjectDuplicateTest_Dingo';
|
||||
|
||||
private static $cascade_duplicates = [
|
||||
'Children',
|
||||
];
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar',
|
||||
];
|
||||
|
||||
private static $many_many = [
|
||||
'Children' => Class4::class,
|
||||
'Children' => Dingo::class,
|
||||
];
|
||||
|
||||
private static $belongs_many_many = [
|
||||
'Parents' => Class4::class,
|
||||
'Parents' => Dingo::class,
|
||||
];
|
||||
}
|
29
tests/php/ORM/DataObjectDuplicationTest/Elephant.php
Normal file
29
tests/php/ORM/DataObjectDuplicationTest/Elephant.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace SilverStripe\ORM\Tests\DataObjectDuplicationTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
/**
|
||||
* @method Antelope Parent()
|
||||
* @method Frog Child()
|
||||
*/
|
||||
class Elephant extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'DataObjectDuplicateTest_Elephant';
|
||||
|
||||
private static $cascade_duplicates = [
|
||||
'Child',
|
||||
];
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar',
|
||||
];
|
||||
|
||||
private static $has_one = [
|
||||
'Parent' => Antelope::class,
|
||||
'Child' => Frog::class,
|
||||
];
|
||||
}
|
25
tests/php/ORM/DataObjectDuplicationTest/Frog.php
Normal file
25
tests/php/ORM/DataObjectDuplicationTest/Frog.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace SilverStripe\ORM\Tests\DataObjectDuplicationTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\ManyManyList;
|
||||
|
||||
/**
|
||||
* @method ManyManyList Children()
|
||||
* @method ManyManyList Parents()
|
||||
*/
|
||||
class Frog extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'DataObjectDuplicateTest_Frog';
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar',
|
||||
];
|
||||
|
||||
private static $belongs_to = [
|
||||
'Parent' => Elephant::class,
|
||||
];
|
||||
}
|
23
tests/php/ORM/DataObjectDuplicationTest/Goat.php
Normal file
23
tests/php/ORM/DataObjectDuplicationTest/Goat.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace SilverStripe\ORM\Tests\DataObjectDuplicationTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
/**
|
||||
* Note: Not duplicated
|
||||
*/
|
||||
class Goat extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'DataObjectDuplicateTest_Goat';
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar',
|
||||
];
|
||||
|
||||
private static $belongs_to = [
|
||||
'bobcats' => Bobcat::class,
|
||||
];
|
||||
}
|
Loading…
Reference in New Issue
Block a user