Problem/Motivation

FormBase is a patchwork of expectations for dependency injection.

It claims to implement ContainerInjectionInterface, but that's only so it can provide a template for subclasses. Rather than using create() to properly inject the services it offers, it only returns new static(); and then uses the anti-pattern \Drupal to access other services.

Proposed resolution

  • Have the create() method actually inject the services supported by FormBase.
  • Add documentation to the docblock of create() so that devs can learn how to do proper DI, and so they remember to call parent::create().
  • Mark getters and setters for services as deprecated: #2494709: Deprecate dependency setters and getters on FormBase

Remaining tasks

User interface changes

API changes

Data model changes

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Mile23 created an issue. See original summary.

tstoeckler’s picture

it only returns new static(); and then uses the anti-pattern \Drupal to access other services.

So this was intentionally done so that sub-classes could more easily use ContainerInjectionInterface.

I don't personally agree with that, but that was the reason at the time.

dawehner’s picture

Yeah, it was always a reason purely driven by TX, IMHO.
In general we cannot inject anything for those base classes, because this would break a hell lot of implementations out there

tim.plunkett’s picture

FileSize
4.68 KB

We can inject it, but only if we still fall back.
IMO this will confuse people about whether they need to call parent::__construct() (they don't have to)

I believe we should trait-ify the remaining service wrappers in FormBase and be done with it.

Honestly the only reason I extend FormBase is to skip an empty validateForm(), which is a terrible reason, but so it goes.

Mile23’s picture

Status: Active » Needs review
Issue tags: +TX (Themer Experience)
FileSize
1.05 KB

In general we cannot inject anything for those base classes, because this would break a hell lot of implementations out there

Yup.

I believe we should trait-ify the remaining service wrappers in FormBase and be done with it.

The traits are neeto, but for instance StringTranslationTrait allows me to take a separate step to setStringTranslation() in my factory method, but then getNumberOfPlurals() ignores injected services anyway. That's out of scope here, but yah.

Also the other traits on FormBase have the same problem: If there's no injected service they use \Drupal when they should really complain.

So then the basic idea of FormBase is a little broken. All it gives you is the knowledge that you should implement FormInterface and then holds your hand if you did a bad job of dependency injection.

As it stands, it's a very poor example of how to implement ContainerInjectionInterface, because the router is going to hand you a container, which you ignore and then infer from \Drupal because it works.

In that case, let's document that in the docblock.

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

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now 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.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

I f we want to do container injection the correct way we are making an API change and breaking backwards compatibility. So we cannot do that. Want we can do is adding a waning how container injection should be done. And the current patch has that waning. For me this is RTBC.

tim.plunkett’s picture

Category: Bug report » Task

This issue needs a new title before commit.

daffie’s picture

Title: Make FormBase really implement ContainerInjectionInterface » Add a warning how to properly add container injection into a class that extends FormBase

@tim.plukett: You are right, thanks.

Mile23’s picture

Version: 8.2.x-dev » 8.1.x-dev
Status: Reviewed & tested by the community » Needs review
FileSize
1.52 KB
1.32 KB

It's a doc change so 8.1.x.

I saw my own typo, so I fixed it, and then changed the docblock a bit. Here's the new one, easier to read here than in the patch:

/**
 * Provides a base class for forms.
 *
 * This class exists as a mid-point between dependency injection through
 * ContainerInjectionInterface, and a less-structured use of traits which
 * default to using the \Drupal accessor for service discovery.
 *
 * To properly inject services, override create() and use the setters provided
 * by the traits to inject the needed services.
 *
 * @code
 * public static function create($container) {
 *   $form = new static();
 *   // In this example we only need string translation so we use the
 *   // setStringTranslation() method provided by StringTranslationTrait.
 *   $form->setStringTranslation($container->get('string_translation'));
 *   return $form;
 * }
 * @endcode
 *
 * Alternately, you could not use FormBase, implement FormInterface, use only
 * the traits you actually need, and inject the services in a container
 * injection pattern you define.
 *
 * @ingroup form_api
 *
 * @see \Drupal\Core\DependencyInjection\ContainerInjectionInterface
 */
Mile23’s picture

Title: Add a warning how to properly add container injection into a class that extends FormBase » Improve docs for how to properly add container injection into a class that extends FormBase
Issue tags: +Documentation
daffie’s picture

Status: Needs review » Reviewed & tested by the community

So is the warning text even better.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 10: 2674508_10.patch, failed testing.

Mile23’s picture

Status: Needs work » Reviewed & tested by the community

Setting back to RTBC after spurious testbot fail.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/Form/FormBase.php
@@ -15,7 +15,30 @@
+ * Alternately, you could not use FormBase, implement FormInterface, use only
+ * the traits you actually need, and inject the services in a container
+ * injection pattern you define.

That's a lot of commas. How about:
Alternately, do not use FormBase. A class can implement FormInterface, use the traits it needs, and inject services from the container as required.

Mile23’s picture

Status: Needs work » Needs review
FileSize
1.29 KB
626 bytes

Changes per #15.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

Alexpott got what he wished for, so back to RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 2ca66f6 and pushed to 8.1.x and 8.2.x. Thanks!

  • alexpott committed 16093cc on 8.2.x
    Issue #2674508 by Mile23, tim.plunkett: Improve docs for how to properly...

  • alexpott committed 2ca66f6 on 8.1.x
    Issue #2674508 by Mile23, tim.plunkett: Improve docs for how to properly...

Status: Fixed » Closed (fixed)

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