Services and Dependency Injection in Drupal

Services are PHP classes that help to reuse the functionality in object oriented ways. Drupal uses a services container to declare services and inject these services in other classes. So the other classes can utilize all functionalities available from the injected classes. This is called dependency injection. This means that one object is dependent on others.

Services are decoupled i.e., changes made to one system will not affect any other systems that bind to.

How to create services in Drupal?

Create a modulename.services.yml file in the module root folder.

Add the below lines on the services.yml file

services:
  example.example_sum:
    class: Drupal\example\ExampleSum

Our service name is example_sum. The example_sumservices are defined with ExampleSum classes under the src directory of the example module. You can use different names for modules.

class ExampleSum {
  public function getSum(int $a, int $b) : int
  {
        return$a + $b;
    }
}

How to call service from other services?

This can be done by dependency injection. Create another service in the same services.yml file.

services:
  example.example_sum:
    class: Drupal\example\ExampleSum
  example.example_calculator:
    class: Drupal\example\ExampleCalculator
    arguments: ['@example.example_sum']

Look at the arguments part above. This means the calculator service is dependent on ExampleSum. The next step is to create a constructor on ExampleCalculator service class to inject ExampleSum object.

namespace Drupal\example\ExampleCalculator;
class ExampleCalculator {
  public function __construct(private ExampleSum $exampleSum) {}

  public function calculateSum(int $a, int $b) : int
  {
        return $this->exampleSum->getSum($a + $b);
    }
}

How dependency injection is possible with controller or form?

Using dependency injection with a controller or form is little different than the above example.

Create a controller ExampleController under src/controller directory.

namespace Drupal\example\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\example\ExampleCalculator;
use Symfony\Component\DependencyInjection\ContainerInterface;

final class ExampleController extends ControllerBase {
    protected $exampleCalculator;
    public function __construct(protected ExampleCalculator $exampleCalculator) {}
    public static function create(ContainerInterface $container) {
        return new static($container->get('example.example_calculator'));
    }
}

Here create() method is a factory method. The controller gets instantiated via create() method and a container is passed to it. The same method can be used with Drupal forms also.

You can also use drush generate command to create service.

drush generate service:custom