Skip to content

[5.x]: project-config/apply --force crashes with "Trying to access array offset on null" when a deleted entry type produces a null → null config path #19300

Description

@DavidOliver

What happened?

I experienced this issue while applying a Craft update in a project's develop branch after having created sections and fields in a feature branch (which, of course, don't exist in the develop branch's config). Claude Code found out the cause, and so I had it generate a bug report, a modified version of which is below. Thanks.

Also see #12163, #16150, #15787, #19031.

Description

Running php craft project-config/apply --force throws a fatal yii\base\ErrorException: Trying to access array offset on null at src/services/Entries.php:1675 when the config diff includes the deletion of an entry type, and the deletion cascades such that the entryTypes.<uid> path resolves to null in both the existing and incoming config.

The crash only occurs with --force. Without it, the same apply completes cleanly.

Root cause

--force sets ProjectConfig::$forceUpdate = true (src/console/controllers/ProjectConfigController.php:314).

In ProjectConfigData::commitChanges(), forceUpdate unconditionally forces $valueChanged = true (src/models/ProjectConfigData.php:64-68), even when $oldValue === null && $newValue === null (which happens for an entry-type path whose parent removal has already been processed earlier in the same apply run).

The event-dispatch block then can't classify it as add or remove, so it falls into the else branch and fires EVENT_UPDATE_ITEM (src/models/ProjectConfigData.php:74-86):

if ($newValue === null && $oldValue !== null) {
    // removeItem
} elseif ($oldValue === null && $newValue !== null) {
    // addItem
} else {
    // updateItem   <-- both null lands here
    $this->projectConfig->trigger(ProjectConfigService::EVENT_UPDATE_ITEM, $event);
}

That routes to Entries::handleChangedEntryType() (the add/update handler), which immediately dereferences the null newValue:

// src/services/Entries.php:1675
$entryTypeRecord->name = $data['name'];   // $data = $event->newValue = null

An entry-type removal should never reach handleChangedEntryType(); a null → null path should not fire updateItem at all.

Stack trace (abridged)

yii\base\ErrorException: Trying to access array offset on null
  in src/services/Entries.php:1675
#1 Entries->handleChangedEntryType()
#2 ProjectConfig->handleChangeEvent()  (EVENT_UPDATE_ITEM)
#5 ProjectConfigData->commitChanges(oldValue: NULL, newValue: NULL, path: 'entryTypes.<uid>', triggerUpdate: FALSE, force: TRUE)
#8 ProjectConfig->_applyChanges(['newItems' => ...])
#9 ProjectConfigController->actionApply()

Suggested fix

Guard the else branch (or handleChangedEntryType) against a null newValue, and/or skip event dispatch when $oldValue === null && $newValue === null even under forceUpdate.

Steps to reproduce

  1. Have an entry type present in the loaded/DB project config but absent from the config/project/ YAML (i.e. a pending deletion).
  2. Run php craft project-config/apply --force.
  3. During apply, the entry type's parent path is removed first; a later entryTypes.<uid> path then commits with both old and new value null.

Expected behavior

The entry type is deleted (via handleDeletedEntryType) and apply completes — as it does without --force.

Actual behavior

Fatal Trying to access array offset on null; apply aborts partway, leaving project config in an inconsistent, partially-applied state.

Craft CMS version

5.10.7

PHP version

8.3

Operating system and version

NixOS 26.05

Database type and version

MySQL 8.0

Image driver and version

No response

Installed plugins and versions

No response

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