Skip to content

[5.x]: Adding nested elements programmatically does not work when saving a provisional draft #18043

@MoritzLost

Description

@MoritzLost

What happened?

Description

I need to create some nested elements programmatically – specifically, an Address elements in an Addresses fields and some Entry elements in some Matrix fields. Here's a minimal example, triggered on Entry::EVENT_BEFORE_SAVE:

public function handleBeforeEntrySave(ModelEvent $e)
{
    $entry = $e->sender;

    if (!$entry->section || $entry->section->handle !== 'offers') {
        return;
    }

    if (ElementHelper::isDraftOrRevision($entry)) {
        return;
    }

    $entry->setFieldValue('contact', [
        'sortOrder' => ['new:1'],
        'entries' => [
            'new:1' => [
                'type' => 'contact_block',
                'fields' => [
                    'myCustomField' => 'foobar',
                ],
            ],
        ],
    ]);
}

Steps to reproduce

  1. In the code above, replace offers with any section handle, contact with the name of a matrix field, contact_block with the handle of an entry type that's allowed in the Matrix fields, and the fields array with some field values for that entry type.
  2. Trigger that function on Entry::EVENT_BEFORE_SAVE.
  3. Modify some fields on an entry in that section and save it.

Expected behavior

I would expect my hook to always create a nested entry of type contact_block in the contact field.

Actual behavior

The hook only works if the entry form is submitted without any pending changes, i.e. if the user does not have a provisional draft for that entry.

If any field in the entry is modified (which means a provisional draft) is created, the nested field in the matrix entry is not created. I tried to find out what's going on:

  • I checked with the debugger – the field value is definitely being set, even if the entry is saved from a provisional draft. It just has no visible effect.
  • I tested this with an Addresses field as well. I can see the new row in the addresses and the elements table, but it gets a dateDeleted value set to the same value as dateCreated immediately.

I think this might be an issue with the lifecycle of nested entries.

I found one workaround. If I execute the hook for drafts as well as canonical entries, it works correctly:

if ($entry->getIsRevision()) {
    return;
}

But I'm not sure if that has any unintended side effects. And since the field value is still being set in the scenario above, I assume it's just a bug in Craft.

Craft CMS version

5.8.18

PHP version

8.3

Operating system and version

No response

Database type and version

No response

Image driver and version

No response

Installed plugins and versions

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions