SetEcaLogLevel cannot be instantiated when eca.settings is absent, so the action vanishes from the action list

Problem/Motivation

SetEcaLogLevel declares a non-nullable, uninitialized typed property and populates it unconditionally from configuration:

modules/base/src/Plugin/Action/SetEcaLogLevel.php:43

protected int $configuredLogLevel;

modules/base/src/Plugin/Action/SetEcaLogLevel.php:50

$instance->configuredLogLevel = $container->get('config.factory')->get('eca.settings')->get('log_level');

When eca.settings is absent, or present without log_level, get() returns NULL and the assignment raises:

TypeError: Cannot assign null to property Drupal\eca_base\Plugin\Action\SetEcaLogLevel::$configuredLogLevel of type int

Actions::createInstance() (src/Service/Actions.php:134-150) catches \Exception | \Throwable, logs an error, and returns NULL. Actions::actions() then simply skips the plugin (src/Service/Actions.php:113).

The net effect is that the action disappears from the available-actions list rather than failing loudly. A watchdog error is written, so it is not entirely silent — but nothing at the point of use indicates why the action is missing, and in a kernel test the log is easy to miss entirely.

Because the property is also read in execute() (:61) and cleanupAfterSuccessors() (:73), an uninitialized property would additionally raise Error: Typed property ... must not be accessed before initialization if an instance ever reached those paths without create() having succeeded.

Steps to reproduce

  1. In a kernel test, install the eca_base module without installConfig(['eca']).
  2. Ask eca.service.action for the available actions (->actions()).

Result: eca_set_eca_log_level is not in the list; a watchdog error records the TypeError. Expected: either the action is available with a sensible fallback log level, or the failure is surfaced where a developer will see it.

The same shape applies to any site or test fixture where eca.settings has not been installed.

Proposed resolution

Make the plugin resilient to absent configuration. Options, roughly in increasing order of invasiveness:

  1. Type the property as ?int (or give it a default) and coerce the read, e.g. fall back to a documented default log level when the setting is missing.
  2. Read the configuration lazily at the point of use rather than in create(), so constructing the plugin cannot fail.
  3. Separately, consider whether Actions::createInstance() swallowing \Throwable is the right default. It keeps one broken plugin from taking down the whole action list, which is valuable — but a TypeError in a plugin's own constructor is a coding error rather than an environmental one, and converting it into "this action does not exist" makes it hard to find. At minimum the error message could distinguish the two.

Options 1 and 2 are local and low-risk. Option 3 is a broader behavioral question and could be split out.

Remaining tasks

  • Make SetEcaLogLevel constructible without eca.settings
  • Decide on a documented fallback log level
  • Add test coverage instantiating the action with eca.settings absent
  • Optionally revisit the Throwable handling in Actions::createInstance()

User interface changes

None, other than the action no longer vanishing from the action list.

Data model changes

None.


Found while backporting #3590375 (closed) to 3.0.x: this defect hid eca_set_eca_log_level.log_level from the runtime plugin enumeration used by that issue's guard test, which is why the key was invisible to a runtime-based audit. The schema itself is correct on both branches.

AI-Generated: Yes (Used OpenCode to investigate why a plugin was missing from runtime enumeration while backporting #3590375 (closed). All file and line references were verified against the 3.1.x source.)