Here are the important nuggets:
- DI is a design pattern used in programming.
- DI uses composition.
- DI achieves inversion of control.
- Dependency == service that your class needs == object of a certain type.
- Inject == provide == compose == assemble.
- Container == service container == dependency container.
- Instead of using
\Drupal::service('foo_service')
, get the service from the$container
if using a class.
And the important reasons:
- Externalizing dependencies makes code easier to test.
- It allows dependencies to be replaced without interfering with other functionality.
- Retrieving dependencies from the container is better for performance.
Services: node.grant_storage
The easiest examples to find are services that have arguments, because you can search *.services.yml files for the word "arguments".
In node.services.yml for example, there is this entry:
node.grant_storage:
class: Drupal\node\NodeGrantDatabaseStorage
arguments: ['@database', '@module_handler', '@language_manager']
tags:
- { name: backend_overridable }
That is saying that for the
node.grant_storage
service, the Drupal\node\NodeGrantDatabaseStorage
class will be used, and three arguments will be passed to it when creating an instance of it. The @
symbol means that these are instances of other services. An instance of a database
service, a module_handler
service, and a language_manager
service will be provided to this node.grant_storage service. These services are just objects of designated types.Here's the relevant portion of the NodeGrantDatabaseStorge class. I've added line breaks to this and other code samples for readability.
class NodeGrantDatabaseStorage implements NodeGrantDatabaseStorageInterface {
protected $database;
protected $moduleHandler;
protected $languageManager;
/**
* Constructs a NodeGrantDatabaseStorage object.
*
* @param \Drupal\Core\Database\Connection $database
* The database connection.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(
Connection $database,
ModuleHandlerInterface $module_handler,
LanguageManagerInterface $language_manager
) {
$this->database = $database;
$this->moduleHandler = $module_handler;
$this->languageManager = $language_manager;
}
// HERE is NO create function .....
}
Controller: NodeViewController
So let's look at a controller, the NodeViewController. It extends
EntityViewController
which implements ContainerInjectionInterface
, so it is container aware. This is a very frequently used interface for classes that are container aware.
The main thing to notice about it is its
create()
method. It's a factory method. Whenever an instance is created, the create() method is used instead of the constructor. See ClassResolver. So create()
is the entry point, instead of__construct()
. And return new static()
means "return a new instances of the current class, using these passed arguments in the class's constructor".
Let's look at the actual code.
class NodeViewController extends EntityViewController {
public function __construct(
EntityManagerInterface $entity_manager,
RendererInterface $renderer,
AccountInterface $current_user = NULL
) {
parent::__construct($entity_manager, $renderer);
$this->currentUser = $current_user ?: \Drupal::currentUser();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager'),
$container->get('renderer'),
$container->get('current_user')
);
}
}
create()
, the three services we need are retrieved and passed to the constructor.
The parent class is already handling the
entity_manager
and current_user
services, so we only concern ourselves with current_user
. That service can potentially be null, so a ternary operator is used. (The currentUser
property is set to the passed $current_user
if it's not empty. Otherwise, we look it up fresh using \Drupal::currentUser()
.)
The dependency injection is getting the current_user service in the create() method, passing it to the constructor, and using it there.
Comments
Post a Comment