5

I am relatively fresh to the DI party and am struggling to get my head around how exactly to use Dependency Injection. I understand that I can pass requirements as part of a service, but what about in my own class?

Say I have the below class where I want the language manager service to be injected:

class Foo implements ContainerInjectionInterface {

  protected $languageManager; 

  public function __construct(LanguageManagerInterface $languageManager) {
    $this->languageManager = $languageManager;
  }

  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('language_manager')
    );
  }
}

When I call new Foo() I am required to pass an instance of LanguageManagerInterface. That makes sense in terms of a normal constructor method, but that would require me to instantiate the class like the below:

$languageManager = \Drupal::service('language_manager');
$foo = new Foo($languageManager)

That just doesn't feel right to me.

Is there something I am missing, or would this class always have to be a service to take advantage of dependency injection?

3

3 Answers 3

3

A class implementing ContainerInjectionInterface is instantiated by calling the factory method create().

See Drupal\Core\DependencyInjection\ClassResolver

/**
 * Implements the class resolver interface supporting class names and services.
 */
class ClassResolver implements ClassResolverInterface, ContainerAwareInterface {
  use DependencySerializationTrait;
  use ContainerAwareTrait;

  /**
   * {@inheritdoc}
   */
  public function getInstanceFromDefinition($definition) {
    if ($this->container->has($definition)) {
      $instance = $this->container->get($definition);
    }
    else {
      if (!class_exists($definition)) {
        throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $definition));
      }

      if (is_subclass_of($definition, 'Drupal\Core\DependencyInjection\ContainerInjectionInterface')) {
        $instance = $definition::create($this->container);
      }
      else {
        $instance = new $definition();
      }
    }

    if ($instance instanceof ContainerAwareInterface) {
      $instance->setContainer($this->container);
    }

    return $instance;
  }

}
2
  • Thanks @4k4. So you are saying I should do $foo = Foo::create()? When I do that with the example above I need to pass a ContianerInterface. If thats right, should this be obtained from \Drupal::getContainer() or is there a better way? Commented Aug 7, 2019 at 8:00
  • 1
    I don't know in which context you use the class. If it is a controller then it gets instantiated by the code above automatically. If you want to instantiate Foo in custom OOP code then inject the class resolver or procedural use $foo = \Drupal::classResolver(Foo::class);
    – 4uk4
    Commented Aug 7, 2019 at 8:07
1

In my humble opinion, the factory design pattern is the most appropriate way to instantiate an object (of a custom class with dependency injection).

From D.O.'s documentation:

Instantiation

Creating classes directly is discouraged. Instead, use a factory function that creates the appropriate object and returns it. This provides two benefits:

  • It provides a layer of indirection, as the function may be written to return a different object (with the same interface) in different circumstances as appropriate.
  • PHP does not allow class constructors to be chained, but does allow the return value from a function or method to be chained.

A code example from a D.O. core issue ([PP-1] Add a factory method to create FileStorage instances) shows how one would go about doing that in Drupal. So, for your example that would be something like this:

Service Definition

foo_factory:
  class: Drupal\your_module\FooFactory  
  arguments: ['@language_manager']

and the related instantiation code:

$fooFactory = \Drupal::service('foo_factory');
$foo = $fooFactory->create();
0

The definition of the class in question is correct. All you need is correctly call the object method. To do that - drupal has special mechanism - class resolver. Here is how to initialize your object correctly:

use Foo;

\Drupal::classResolver(Foo::class)->yourClassMethod($some_arguments_from_hook_or_preprocess);

Read more about class resolver

2
  • Important to note from the documentation: "This is to be used in procedural code" Commented Apr 13, 2022 at 6:30
  • @StefanosPetrakis right. Thanks Commented Apr 13, 2022 at 7:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.