Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cascade_duplicate on many_many does not cascade duplicate #9774

Open
OldStarchy opened this issue Nov 18, 2020 · 0 comments
Open

cascade_duplicate on many_many does not cascade duplicate #9774

OldStarchy opened this issue Nov 18, 2020 · 0 comments

Comments

@OldStarchy
Copy link
Contributor

Affected Version

At least 4.6.0, probably all versions

Description

TL;DR: workaround at the bottom

So this is technically by design, see this note from the docs:

Non-exclusive many_many will not be duplicated, but the mapping table values will instead be copied for this record.

And while I get that this would be useful, it would be better to at least provide an option to recurse duplications into many_many fields.

My use case is an ordered list where the SortOrder is stored as an extrafield.

Code
class Bar extends DataObject {}

class Foo extends DataObject
{
    private static $many_many = [
        'Bars' => Bar::class,
    ];

    private static $many_many_extraFields = [
        'Bars' => [
            'SortOrder' => 'Int',
        ],
    ];

    private static $cascade_duplicates = [
        'Bars',
    ];
}

class Baz extends DataObject {
    private static $has_one = [
        'Bar' => Bar::class,
    ];
}

In this example, Foo has a list of Bar stored in a many_many with a SortOrder extra field, and Baz has_one Bar.

I didn't put SortOrder on Bar because SortOrder is a property of the list, not an intrinsic property of the related object. Bar shouldn't care, or even know, that it's appearing in a list, let alone have properties that relate to being in one.
Additionally, Bar may be used in other places where it obviously makes no sense to have a SortOrder, eg. in a has_one relation on Baz.

Therefore, a built-in way to have the duplication recurse into the related object would directly allow this to work.

For anyone else with this problem, the workaround I'm using at the moment is to overwrite the duplicateManyManyRelation method from DataObject to clone the related objects.

Code
class Bar extends DataObject {}

class Foo extends DataObject
{
    private static $many_many = [
        'Bars' => Bar::class,
    ];

    private static $many_many_extraFields = [
        'Bars' => [
            'SortOrder' => 'Int',
        ],
    ];

    private static $cascade_duplicates = [
        'Bars',
    ];
    
    /**
     * Overwrites the parent version so that it actually clones Bars rather than rereferencing them
     *
     * @param DataObject $sourceObject
     * @param DataObject $destinationObject
     * @param string $relation
     */
    protected function duplicateManyManyRelation($sourceObject, $destinationObject, $relation)
    {
        // Decide if we need to cascade the duplication
        if ($relation !== 'Bars') {
            return parent::duplicateManyManyRelation($sourceObject, $destinationObject, $relation);
        }

        // Copy all components from source to destination
        $source = $sourceObject->getManyManyComponents($relation);
        $dest = $destinationObject->getManyManyComponents($relation);

        if ($source instanceof ManyManyList) {
            $extraFieldNames = $source->getExtraFields();
        } else {
            $extraFieldNames = [];
        }

        foreach ($source as $item) {
            // Merge extra fields
            $extraFields = [];
            foreach ($extraFieldNames as $fieldName => $fieldType) {
                $extraFields[$fieldName] = $item->getField($fieldName);
            }

            // This part is the change from `parent::duplicateManyManyRelation` - actually duplicate the object
            $clonedItem = $item->duplicate(false);
            $dest->add($clonedItem, $extraFields);
        }
    }
}

class Baz extends DataObject {
    private static $has_one = [
        'Bar' => Bar::class,
    ];
}
@OldStarchy OldStarchy changed the title Cascade Duplicate on many_many does not cascade duplicate cascade_duplicate on many_many does not cascade duplicate Nov 18, 2020
@OldStarchy OldStarchy changed the title cascade_duplicate on many_many does not cascade duplicate cascade_duplicate on many_many does not cascade duplicate Nov 18, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants