Problem/Motivation

DisplayBuildableInterface extends ContainerFactoryPluginInterface, and DisplayBuildablePluginBase::create() already injects entity_type.manager, current_user and module_handler. None of the four buildable plugins used them. They reached for the container statically, and resolved their entity in the constructor with EntityViewDisplay::load(), PageLayoutEntity::load() or View::load().

The cause is ordering: create() does new static(...) first and assigns the services after, so nothing the constructor needs can be injected. Each constructor carried a "No dependency injection in plugin constructors" comment, which describes the problem rather than justifying it.

This matters beyond tidiness: a contributed module writing its own integration will open EntityView and copy it. It is the reference implementation whether we call it that or not.

Proposed resolution

  • Constructors resolve nothing. They only normalize ::$configuration, keeping the identifier the plugin was built with and stashing an object the caller passed in.
  • Each plugin resolves lazily through $this->entityTypeManager: EntityView::getDisplay(), EntityViewOverride::getDisplay() and ::getField(), PageLayout::getEntity(), ViewDisplay::getExtender().
  • Extra services come from an overridden create() that calls parent::create() and assigns on the result: sampleEntityGenerator, dataConverter, time, displayBuildableManager, currentPath, pluginCacheClearer.
  • Removed the seven lazy \Drupal::service() accessors in EntityViewOverride and PageLayout, plus EntityViewOverride::entityTypeManager(), which shadowed the injected property and had no callers.
  • The base create() docblock now documents the extension pattern and why a constructor must not resolve its display.
Plugin \Drupal:: before After
EntityViewOverride 10 5
EntityView 5 3
PageLayout 5 2
ViewDisplay 2 2

Every remaining call sits in a static method the interface mandates: collectInstances(), checkAccess(), getUrlFromInstanceId() and their static helpers. Those have no $this to inject into and cannot be fixed without changing the contract. Nothing in instance context reaches for the container.

Remaining tasks

  • Review.

User interface changes

None.

API changes

PageLayout::$entity was public and is now protected, read through ::getEntity(). It had no callers outside the plugin. Everything else is protected or private internals. Plugin IDs, stored configuration keys, DisplayBuildableInterface and DisplayBuildablePluginBase::create() are unchanged.

Data model changes

None.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mogtofu33 created an issue. See original summary.

mogtofu33’s picture

Issue summary: View changes
mogtofu33’s picture

Issue summary: View changes

mogtofu33’s picture

Assigned: mogtofu33 » pdureau
Status: Needs work » Needs review
mogtofu33’s picture

Assigned: pdureau » Unassigned
Status: Needs review » Fixed

It is pretty safe with tests, so ok to auto merge.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

  • mogtofu33 committed 907e0c9e on 1.0.x
    task: #3616225 Buildable plugins bypass their own dependency injection...
pdureau’s picture

Indeed, discussed in weekly, it was ok 👍

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.