Drupal8.x and Symfony – Controller Practices: Part V Series

So today I ran into Drupal Controllers core component or folder. And I found this ContentFormControllerSubscriber under the Subscriber folder:

 * Subscriber for setting wrapping form logic.
class ContentFormControllerSubscriber implements EventSubscriberInterface, ContainerAwareInterface {
  use ContainerAwareTrait;

The trait here basically has the setter method to inject and class variable to hold the container. Something that is really a violation of DI into a service locator. See Paul Jones for more information on this. Right from the very beginning I am always suspicious of abuse on traits usage. It is actually a smell to me, or well most of the times. It is easy to detect this on developers :).

Taking a look at the constructor of this subscriber I saw it was injecting already the services needed:

  public function __construct(
    ClassResolverInterface $class_resolver,
    ControllerResolverInterface $controller_resolver,
    FormBuilderInterface $form_builder
  ) {
    $this->classResolver = $class_resolver;
    $this->controllerResolver = $controller_resolver;
    $this->formBuilder = $form_builder;

So based on the request format, this subscriber will set the _controller field in the request. So it will of course get the request from the event dispatched, then detect whether this event is a form submission, then if it is proceeds to handle it as such, a form submission, and attempts to wrap and encapsulate everything inside a _content field which is the product of a specific controller invoked, the HtmlFormController.

In other words, if you submit a form in Drupal, this subscriber will take your request, identify it and put everything into the Request _content field.

In fewer words, for every form on your app it will end up processed under a _content field. The work of choosing which controller will handle your form has been simplified. You are not in control because Drupal wants you to reuse their form system.

Until here, nothing bad, however I see a direct instantiation of HtmlFormController:

  public function onRequestDeriveFormWrapper(GetResponseEvent $event) {
    $request = $event->getRequest();
    if ($form = $request->attributes->get('_form')) {
      $wrapper = new HtmlFormController($this->classResolver, $this->controllerResolver, $this->container, $form, $this->formBuilder);
      $request->attributes->set('_content', array($wrapper, 'getContentResult'));

What happened here, explains why this Subscriber had to use a trait and why it had to be injected the whole container. It is imo wrong to inject the container if you are just passing it, why the HtmlFormController was not rather injected as a service. I don’t know, I can only think there is still a good reason. In the case that the HtmlFormController would have been injected we would not have needed to inject or use any traits nor inject the whole container. The container is not used anywhere else, it is just passed straight as an argument to the HtmlFormController. The HtmlFormController is not used anywhere else but here. It was created mainly for this subscriber and to detect and channelize form submissions to Drupal’s form system.

Here is the service definition of our ContentFormControllerSubscriber:

    class: Drupal\Core\EventSubscriber\ContentFormControllerSubscriber
    arguments: ['@class_resolver', '@controller_resolver', '@form_builder']
    parent: container.trait
      - { name: event_subscriber }

    abstract: true
      - [setContainer, ['@service_container']]

Seeing this does not cheer my spirits, but oh well, it is good to know that you could do this on Symfony service definitions. The DI component allows you to do things like that even though they will harm your design later on by basically making all your services that use this HtmlFormController or similar to have to use the dreaded trait and worst to inject the whole container.

At least let’s hope the container gets used inside HtmlFormController… nope! I tried to look hard and maybe it is used in some class that extends this, however, I could not find it. It just adorns the constructor of that class gets assigned and then that is it.

I also skim through lines saying:

* @todo Make this a trait in PHP 5.4.

At first I thought, let’s send a PR, however, I am not going to do it because I have started to dislike how traits are being used in Drupal core. Basically I don’t see them as a way to clean up classes. I see it as a smell. They could only be useful when they are better than interfaces, but in most cases good design uses very good interfaces so there is no need for traits, they should only go in implementations and where there is an actual use case with many methods, not just one setter and especially the setter container! Traits sometimes are a quick shortcut for not thinking hard enough on the refactor you are doing, once in another file and with the wrong name the code becomes blur. There are instances in which I would use them, but so far the usage I have seen in this codebase does not motivate me to do the same. I have seen Traits in other packages and they are fine, just that not always it is the case.

Let’s pick another one, AjaxController.

    class: Drupal\Core\Controller\AjaxController
    arguments: ['@controller_resolver', '@ajax_response_renderer']

However oh surprise when we see the class:

class AjaxController implements ContainerAwareInterface {
  use ContainerAwareTrait;

It injects the container again, but never uses it and it is a service all well defined and round. I tried to look for usages of it and did not find one, maybe it is reserved to be extended or handled in a certain way, but let me point that the container injection did not seem as though it was thought well if it is not being used at all. Things are quick to inject and that is the problem. Doing damage is easy, repairing the damage is of course hard. Maybe the scope of things were sketched but not seen until implemented and that could explain many injections done. The job of each class has become more clear so it is now time to clean them up and get rid as much as possible of the container injection.

I hope I had not made many mistakes here and that the usages are justified, if so let me recant and correct me please. I would be thankful to learn from you.

Again totally undeserved to be flying to Austin or presenting a talk there, even working or even breathing with joy. Such grace!

your friend, hope this helps you! Feel free to support me! thanks!

One thought on “Drupal8.x and Symfony – Controller Practices: Part V Series

  1. Hola!

    Yes, Drupal is using container-awareness in too many places. In some cases it’s necessary. In some of the ones you list, I agree it’s not. Often it was necessary at one point but has since become unnecessary due to later refactoring, but the container-awareness was never cleaned out.

    At one point we were using ContainerAwareBase a lot to avoid code duplication. It looks like someone blindly switched all of those to ContainerAwareTrait sometime after we adopted 5.4. I do believe that’s an improvement; it’s not more of a pollutant than ContainerAwareBase is but avoids taking over the “is-a” extends relationship. IF a class legitimately needs to implement ContainerAwareInterface, then ContainerAwareTrait is the right way to do that in Drupal.

    That said, I would actually ask and encourage you, yes, please do file patches for some of these if you feel you can clean them up. Remember, Drupal’s core dev team has… never shipped a product based on PHP > 4. 🙂 There’s a LOT we’re still collectively learning, very quickly but still learning. Having good guidance and examples from those with more experience (eg, you) is very helpful. Feel free to ping me in IRC or assign the issue to me if you want my review or advise on one.

    As for traits in general, no, they’re not an alternative to interfaces. At all. They’re by design orthogonal. They’re a replacement for abstract classes. This is my official stance on traits in PHP: http://www.garfieldtech.com/blog/beyond-abstract

    I’m pushing for Drupal to follow that pattern, generally speaking. I won’t entirely win (not enough time), but I’m trying. 🙂

Leave a Reply to Larry Garfield Cancel reply

Your email address will not be published. Required fields are marked *