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); 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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.