Problems / Motivations

#1909418: Allow Entity Controllers to have their dependencies injected Brings good ideas in term of object encapsulation but instantation of components is still done the old way (through "isset" and "if" mechanisms). The controllers are ContainerAware but does not seems to use the provided ContainerAwareInterface, thus it seems it was not invented here and is bad practice in general: controllers configures themselves in a static createInstance method. Moreover the complexity of discovering controllers can be cached at container compile-time, avoiding expensives operations at runtime.

Proposed resolution

  • Entity Controllers need to be services so they can get things like database connection or cache backends/bins injected.
  • The entity manager should be able to get instances of the controllers, without getting them injected, as we want them to be lazy loaded.

The proposed service name for these controllers would be:

'ENTITY_TYPE.controller.CONTROLLER_TYPE' which is a sort of namespace by component.

There is also the option of using: 'entity.ENTITY_TYPE.CONTROLLER_TYPE'

For the user storage controller:

<?php

$container->setDefinition('user.controller.storage', new Definition('Drupal\user\UserStorageController',
  array('user', new Reference('database'), new Reference('password'), new Reference('user.data')));

?>

Then the refactored Manager, or "service locator".

<?php
  public function hasController($entity_type, $controller_type) {
    return $this->container->has("$entity_type.controller.$controller_type");
  }

  // TODO: Remove the getControllerClass().

  /**
   * @return Drupal\Core\Entity\EntityStorageControllerInterface
   */
  public function getStorageController($entity_type) {
    return $this->container->get("$entity_type.controller.storage");
  }
?>

We can then get rid of EntityControllerInterface, which mimics the provided ContainerAwareInterface in term of functionalities. Remove the createInstance methods and de-couple controllers from container. Tree inheritance and dependency management becomes less complexe and easier to maintain as we all know principles of good programming: loose coupling and high cohesion.

CommentFileSizeAuthor
#14 entity-1980310-14.patch3.32 KBtim.plunkett

Comments

Sylvain Lecoy’s picture

Issue summary: View changes

typo

Sylvain Lecoy’s picture

Issue summary: View changes

typo

Sylvain Lecoy’s picture

In the code, we'll also avoid the need for:

<?php
$controller = drupal_container()->get('plugin.manager.entity')
      ->getStorageController('menu_link');

// Not type hinted.
$controller-> ????
?>

But instead use:

<?php
// Through a locator class.
$controller = EntityControllerLocator::getStorageController('menu_link');
// For the purists, through a classical method.
// It does not break object oriented principles.
$controller = entity_storage_controller('menu_link');

// Type hinted.
$controller->load()
           ->delete()
           ->etc.
?>

A bit like the Drupal super-class service locator. Of course experimented programmers who need menu storage as dependency of their object can still configure the container to build their object and inject dependencies for them. The EntityControllerLocator would be then a short-cut method useful to know the name of services and to help procedural code leveraging lazy-loading of components.

fubhy’s picture

We had a similiar issue a while ago: #1821662: Register entity controllers with the DIC

Sylvain Lecoy’s picture

So let's use the 'entity.ENTITY_TYPE.CONTROLLER_TYPE' pattern :-)

Sylvain Lecoy’s picture

The basic idea is then to register all controllers through *.services.yml.

Here we'll be able to override controllers thanks to Symfony overriding service capability, hence we'll eliminate the need for entity_info_alter when redefining a specific controller.

However, I am blocked with form controller as they need an $operation argument.

Having a look at EntityFormController, the $operation class member does not have any cohesion with the class, other than an accessor getOperation() defined through EntityFormControllerInterface. Low cohesive classes are not only a bad design habit, but it also prevent me configuring the service within the container, as this operation argument is a runtime parameter.

To fully implement this patch, I'll have to remove the operation from constructor and instead pass it as an argument in a setOperation method for now.

andypost’s picture

@Sylvain Lecoy +1 to idea!!!

I really dislike the way to store container in entity manager as approached in #1909418: Allow Entity Controllers to have their dependencies injected
It brings us to situation when EM and container have circular references

'entity.ENTITY_TYPE.CONTROLLER_TYPE' should be added to summary

Waiting a patch to review!

tim.plunkett’s picture

By my count, there are 115 entity controllers in core right now, and we're not done.

What is your plan to scale that to contrib? Crell has estimated that the scaling of the DIC breaks down around 2000 services.

tim.plunkett’s picture

Issue summary: View changes

typo

msonnabaum’s picture

The controllers are ContainerAware but does not seems to use the provided ContainerAwareInterface, thus it seems it was not invented here and is bad practice in general: controllers configures themselves in a static createInstance method.

They are not container aware. The instances have no knowledge of it, only the factory method. It's no different than having a separate factory class.

Sylvain Lecoy’s picture

It is a difference as it violate SRP rule. Factory are a separate class for a reason: to loosen the coupling.

msonnabaum’s picture

It really doesn't violate SRP at all. The downsides to violating SRP are nonexistent here. It's simply Factory Method.

Also, SRP is in no way a rule that we need to adhere to 100% (not that this is an issue of violating it…)

Sylvain Lecoy’s picture

Factories are a well-known creational pattern defined and used since 1994 and maybe before. The abstract factory provide an interface for creating families of related or depedent object without specifying their concrete classes while the factory method defines an interface for creating an object, but let subclasses decide which class to instantiate. Thus, Factory Method lets a class defer instantiation to subclasses.

The controller object, by implementing this behaviour, breaks SRP. This object set of feature has been augmented, to include a factory. Plus, we also have augmented the coupling between the Controller and the Container. By configuring the controller through an external factory, we loosen the coupling. Best; if we use correctly DI mechanisms, we don't even need factories and we elminates the need for an external factory.

Sylvain Lecoy’s picture

Issue summary: View changes

typo

Sylvain Lecoy’s picture

Related problem in plug-in definitions where we lock the __construct() signature as highlighted in #1821662-11: Register entity controllers with the DIC.

effulgentsia’s picture

The routing system allows routing.yml files to specify (route) controllers either as classes or as service ids. Is there any reason for EntityManager not to allow @EntityType annotations to specify (entity) controllers the same way? If not, would it make sense to use this issue to implement the needed changes to support that choice, and then have a follow up for discussing which option should be used by core when?

If this was already discussed and shot down in other issue comments, please provide links to them.

tim.plunkett’s picture

#2168333: Ensure custom EntityType controllers can be provided by modules would pave the way for that.
Though in *.routing.yml, you must specify a method as well, which is not applicable here.

tim.plunkett’s picture

Issue summary: View changes
StatusFileSize
new3.32 KB

Actually nevermind my worry in #13, I was thinking of #2165475: Provide a generic class resolver, and that doesn't even handle the service part.

This will not apply now, but will work fine once #2168333: Ensure custom EntityType controllers can be provided by modules is in.

eclipsegc’s picture

FWIW, for those of you who've seen my attempt at utilizing events for entity controller instantiation, it was trying to find a middle ground between what tim is doing in #14 and what core does currently. This would basically remove my existing arguments against what core does (though I don't see a need for the static createInstance() method if we support this, but w/e seems like the sort of thing we could discuss later).

Eclipse

tim.plunkett’s picture

The reason to keep that is because we're not *forcing* anything. Both a vanilla class, and one implementing ContainerInjectionInterface would continue to work. That's how route controllers work.

tim.plunkett’s picture

Also, since this is an established pattern already, I don't see how using events for declaring something like this would be a middleground. That was a completely new approach we hadn't seen before, which is why I think there was pushback.

(The approach #16 mentions is https://gist.github.com/EclipseGc/8385561)

eclipsegc’s picture

Yeah, the one big difference is that you can't iteratively create a bunch of services, though the flips side to that is that we can process the definition to add those, again that leaves contrib out in the cold to a certain degree, but it's much less of an issue with this case I think. After a night of sleep, still ++

Eclipse

dawehner’s picture

Actually nevermind my worry in #13, I was thinking of #2165475: Provide some generic class resolver, and that doesn't even handle the service part.

Well, I think we should just add that.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Status: Active » Postponed (maintainer needs more info)
Issue tags: +stale-issue-cleanup
tim.plunkett’s picture

Status: Postponed (maintainer needs more info) » Closed (outdated)

Confirmed.