Skip to main content
Home
Drupal life hacks

Main navigation

  • Drupal
  • React
  • WP
  • Contact
  • About
User account menu
  • Log in

Breadcrumb

  1. Home

Mastering Dependency Injection in Drupal: A Practical Guide

By admin, 17 September, 2024

Dependency Injection (DI) is a design pattern in which an object receives its dependencies from external sources rather than creating them itself. The main idea is to separate the creation of an object's dependencies from the object's own functionality. This promotes modularity, flexibility, testability, and loose coupling between different parts of a program.

Key Concepts of Dependency Injection:


1. Dependencies: Objects or services that another object relies on to function. For example, a class may rely on a database connection or a logging service.
  
2. Injection: Instead of creating dependencies inside a class, they are passed (injected) from the outside, typically through:
  - Constructor injection: Dependencies are passed via the class constructor.
  - Setter injection: Dependencies are passed via setter methods.
  - Interface injection: Dependencies are passed by implementing an interface that forces the injection.

3. Inversion of Control (IoC): Dependency injection is a form of IoC where the control of object creation is transferred from the class itself to an external source, typically a DI container or service container. This container resolves and injects dependencies.

Example of Dependency Injection:

Here is an example to illustrate how dependency injection works in PHP using constructor injection.

Without Dependency Injection:

class Database {
   public function connect() {
       // connect to database
   }
}
class UserService {
   private $db;
  public function __construct() {
       $this->db = new Database();  // Dependency created inside the class
   }
  public function getUserData() {
       return $this->db->connect();  // Using the Database dependency
   }
}



In this example, UserService creates an instance of Database itself, making it tightly coupled to Database. If you wanted to use a different database class, you'd have to modify the UserService class.

With Dependency Injection:

class Database {
   public function connect() {
       // connect to database
   }
}
class UserService {
   private $db;
  // Constructor injection
   public function __construct(Database $db) {
       $this->db = $db;  // Dependency is injected from outside
   }
  public function getUserData() {
       return $this->db->connect();  // Using the injected Database dependency
   }
}


Now, the UserService does not create its own Database instance but receives it from the outside. This makes UserService more flexible because you can inject different types of Database objects if needed.

Advantages of Dependency Injection:


1. Loose Coupling: The dependent class doesn’t need to know how its dependencies are created. This leads to a more flexible and maintainable codebase.
  
2. Testability: By injecting dependencies, you can easily mock or replace dependencies when testing. This makes unit testing much simpler.

3. Single Responsibility Principle (SRP): Classes are not responsible for creating and managing their dependencies, adhering to SRP. They focus on their own core functionality.

4. Flexibility: DI allows you to change or replace dependencies without modifying the dependent class, promoting reusability and flexibility.

Dependency Injection Container (DI Container):


In larger applications, managing dependencies manually can become complex. A DI container (or service container) automates this process by managing the instantiation and injection of dependencies.

Example with a DI Container (Using Symfony’s Service Container):


In frameworks like Symfony, you define services and their dependencies in a container, and the framework automatically injects them when needed.

class UserService {
   private $db;
  public function __construct(Database $db) {
       $this->db = $db;
   }
}
// Defining services in the DI container
$container = new \Symfony\Component\DependencyInjection\ContainerBuilder();
$container->register('database', 'Database');
$container->register('user_service', 'UserService')
         ->addArgument(new Reference('database'));
// Getting the UserService with its dependencies injected
$userService = $container->get('user_service');


The container takes care of creating the Database instance and injecting it into the UserService class automatically.

Types of Dependency Injection:


1. Constructor Injection: Dependencies are provided through the class constructor. This is the most common type because it ensures that dependencies are available as soon as the object is instantiated.
2. Setter Injection: Dependencies are provided through setter methods. It allows optional dependencies, but it makes it possible to create an object without having all its dependencies, which could be problematic.
3. Interface Injection: The class implements an interface that requires a method for dependency injection. It’s less common and often considered more complex than necessary.

Real-world Usage:


- Frameworks like Symfony, Laravel, and Zend heavily use DI for managing services and components.
- Testing and Mocking: DI makes it easier to swap out real services for mock services during testing.

In summary, Dependency Injection makes code more modular, maintainable, and testable by allowing external control over dependencies, reducing hard-coded connections between objects.

 

In Drupal, Dependency Injection (DI) is a core concept, especially starting from Drupal 8, which is built on the Symfony framework. Drupal uses a service container to manage and inject dependencies for various classes and services, which promotes flexibility, testability, and maintainability in your code.

Key Concepts of Dependency Injection in Drupal:

1. Service Container: A centralized container that holds the available services (objects that perform specific tasks). The container automatically resolves and injects dependencies when they are needed.

2. Services: Reusable objects that perform specific tasks (e.g., logging, database connections, or rendering). These services are defined in the container and injected when needed.

3. Constructor Injection: In Drupal, the most common way to inject dependencies is through constructor injection. Dependencies are passed to the class via its constructor.

Example of Dependency Injection in Drupal:

Suppose you need to inject a service into a custom block or controller.

Step-by-Step Example (Custom Block with Dependency Injection):

1. Create a Service: First, define a service that you want to inject.

In my_module.services.yml:

yaml
services:
 my_module.custom_service:
   class: Drupal\my_module\Service\CustomService
   arguments: ['@logger.channel.default']


 The my_module.custom_service is defined in the services YAML file. It refers to the CustomService class, and its dependency (the logger service) is injected as an argument (@logger.channel.default).

2. Create the Service Class:

In src/Service/CustomService.php:


namespace Drupal\my_module\Service;
use Psr\Log\LoggerInterface;
class CustomService {
protected $logger;
public function __construct(LoggerInterface $logger) {
   $this->logger = $logger;
 }
public function logMessage($message) {
   $this->logger->info($message);
 }
}

 

This service class depends on the logger service, which is injected through the constructor.

3. Inject the Service into a Block:

Now, we create a custom block that uses this service.

In src/Plugin/Block/MyCustomBlock.php:

namespace Drupal\my_module\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\my_module\Service\CustomService;
/
* Provides a custom block.
*
* @Block(
*   id = "my_custom_block",
*   admin_label = @Translation("My Custom Block"),
* )
*/
class MyCustomBlock extends BlockBase implements ContainerFactoryPluginInterface {
protected $customService;
public function __construct(array $configuration, $plugin_id, $plugin_definition, CustomService $customService) {
   parent::__construct($configuration, $plugin_id, $plugin_definition);
   $this->customService = $customService;
 }
/
  * {@inheritdoc}
  */
 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
   return new static(
     $configuration,
     $plugin_id,
     $plugin_definition,
     $container->get('my_module.custom_service')  // Dependency Injection
   );
 }
/
  * {@inheritdoc}
  */
 public function build() {
   // Use the service to log a message.
   $this->customService->logMessage('This is a log message from my custom block!');
  return [
     '#markup' => $this->t('This is my custom block using a service.')
   ];
 }
}

Key Points in the Custom Block:


- create(ContainerInterface $container): This static method uses the service container to fetch the dependencies. In this case, it gets the my_module.custom_service.
 
- Constructor Injection: The CustomService object is passed through the constructor of the block class, making it available for use in the build() method.

How Dependency Injection Works in Drupal:

- Service Container: Drupal uses Symfony's service container. It defines services in *.services.yml files and automatically injects them wherever needed.
 
- ContainerFactoryPluginInterface: When creating blocks, forms, controllers, or other plugin types in Drupal, you can implement this interface to leverage dependency injection.

More Examples of DI in Drupal:

 

1. Injecting Services into a Controller:

namespace Drupal\my_module\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\my_module\Service\CustomService;
class MyCustomController extends ControllerBase {
protected $customService;
public function __construct(CustomService $customService) {
   $this->customService = $customService;
 }
public static function create(ContainerInterface $container) {
   return new static(
     $container->get('my_module.custom_service')
   );
 }
public function content() {
   $this->customService->logMessage('Log from controller');
   return [
     '#markup' => $this->t('Content generated from controller using a service.'),
   ];
 }
}

 

2. Injecting Services into Forms:


You can also inject services into forms by implementing FormInterface and using ContainerFactoryPluginInterface for dependency injection.

Benefits of Using Dependency Injection in Drupal:

 

1. Loose Coupling: Classes and services are decoupled, making the code more modular and easier to maintain.
  
2. Testability: DI allows easier testing of classes, as you can pass mock objects during unit tests without relying on the actual service implementation.

3. Reusability: Services can be reused across different parts of the application, improving code reusability.

4. Flexibility: Dependencies can be swapped easily, allowing for more flexibility in the code. For example, you could inject different services depending on the environment (e.g., development vs production).

Commonly Injected Services in Drupal:

- Logger Service: @logger.channel.default for logging messages.
- EntityTypeManager: @entity_type.manager for working with entities.
- Database Connection: @database for interacting with the database.
- Config Factory: @config.factory for retrieving configuration.
- Messenger: @messenger for displaying messages to users.

Summary:


In Drupal, Dependency Injection enhances the modularity and flexibility of the application by allowing services to be injected where needed. It leverages the Symfony service container** to resolve dependencies and inject them into blocks, controllers, forms, or any other class. This pattern promotes best practices such as loose coupling and improves the testability of the code.

Tags

  • #Drupal Planet

Comments

About text formats

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Powered by Drupal