0

Suppose I have an ExampleService class and I need a service (current_user) to process my logic. So I have implemented my service like this.

The way 1: DI standard Drupal

namespace Drupal\example_service;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Session\AccountProxy;

class ExampleService implements ContainerInjectionInterface {
    /**
   * Drupal\Core\Session\AccountProxy definition.
   *
   * @var \Drupal\Core\Session\AccountProxy
   */
    protected $currentUser;

    public function __construct(AccountProxy $current_user) {
        $this->currentUser = $current_user;
    }

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

The way 2: Use global service

namespace Drupal\example_service;

class ExampleService {
    /**
   * Drupal\Core\Session\AccountProxy definition.
   *
   * @var \Drupal\Core\Session\AccountProxy
   */
    protected $currentUser;

    public function __construct(AccountProxy $current_user) {
        $this->currentUser = \Drupal::service('current_user');
    }
}

What is the difference between these methods?
What is better for performance?

7

In Drupal 8, you should always use dependency injection within the context of classes.

However, if you create a custom service, you don't have to manually implement the ContainerInjectionInterface.

Instead, you define your service and the arguments for the constructor within your module's example_service.services.yml file (read more about .services.yml files here and about service injection here):

services:
  example_service.my_service:
    class: Drupal\example_service\ExampleService
    arguments: ['@current_user']

And your service class like this:

<?php

namespace Drupal\example_service;

use Drupal\Core\Session\AccountProxyInterface;

/**
 * My Example service.
 */
class ExampleService {

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;

  /**
   * Construct an example service instance.
   *
   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
   *   Account proxy for the currently logged-in user.
   */
  public function __construct(AccountProxyInterface $current_user) {
    $this->currentUser = $current_user;
  }

}

Then you can create an instance of your custom service either via dependency injection as well (within the scope of another class), or within your module/theme files by using:

/* @var $my_service \Drupal\example_service\ExampleService */
$my_service = \Drupal::service('example_service.my_service');
|improve this answer|||||
  • I know that. But I want to know difference when I use DI and use Global service in __contruct. – Jonh Jan 8 '18 at 7:05
  • 1
    Because using dependency injection eases testing and modular programming, it is best practice within Drupal 8 (and Symfony) to use it over statically requesting a service. Using the above described mechanisms also eases keeping an overview of the dependencies your service has. You have to check the service definitions only to find whether a service requires the account proxy, instead having to screen the entire code for static service getters. – Mario Steinitz Jan 8 '18 at 7:21
  • This just shouldn’t be done. Statically requesting a service eases the remaining procedural areas of Drupal when you need to use them. Otherwise, don’t. – Kevin Jan 8 '18 at 18:57
5

Both the examples are wrong.

A service doesn't normally implement the ContainerInjectionInterface interface. It can be implemented by other classes, for example the AggregatorController class, which is not a service, since it is not listed in the aggregator.services.yml file of the Aggregator module.
As described in Services and Dependency Injection Container, what a module maintainer needs to do to define a new service is:

  • Choose a unique machine name for your service. Typically, this should start with your module name. Example: mymodule.myservice.
  • Create a PHP interface to define what your service does.
  • Create a default class implementing your interface that provides your service. If your class needs to use existing services (such as database access), be sure to make these services arguments to your class constructor, and save them in member variables. Also, if the needed services are provided by other modules and not Drupal Core, you'll want these modules to be dependencies of your module.
  • Add an entry to a modulename.services.yml file for the service. See Discovering existing services above, or existing *.services.yml files in Core, for the syntax; it will start with your machine name, refer to your default class, and list the services that need to be passed into your constructor.  

 

The second example is wrong because, instead of accessing \Drupal::service('current_user'), the constructor should just use the argument it gets.

Generally speaking, a service should never use the \Drupal class, but use the service container passed as argument to its create() (or createInstance()) method, or define a service as its arguments. In the latter case, the service will be passed as argument of its constructor.
The only case where a Drupal core service uses the \Drupal class in its constructor is when the arguments of a service has been changed. In that case, the service constructor uses code similar to the following one. (The code is the one used for JsCollectionOptimizer::__construct().)

public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptimizerInterface $optimizer, AssetDumperInterface $dumper, StateInterface $state, FileSystemInterface $file_system = NULL) {
  $this->grouper = $grouper;
  $this->optimizer = $optimizer;
  $this->dumper = $dumper;
  $this->state = $state;
  if (!$file_system) {
    @trigger_error('The file_system service must be passed to JsCollectionOptimizer::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/3006851.', E_USER_DEPRECATED);
    $file_system = \Drupal::service('file_system');
  }
  $this->fileSystem = $file_system;
}

As for the preferable/correct way to implement a service, which should not be confused with overriding the default class used for a service, there is just a way, which is the one I quoted.

|improve this answer|||||
  • Hello just came here lookinf for something in your answer, to avoid creating a new question. Why, if we should never use the \Drupal... classes like translator() or messenger() are we allowed to do it? Are there special circumstances where we should? Thanks – JorgeeFG Sep 30 '18 at 2:43
  • I am not sure I understand the question. The messenger is a service and it's possible to inject it in services. There are cases where it's only possible to use the \Drupal class, for example in static methods and in some methods of entity classes. Generally speaking, it depends from accessing the container in some way, for example because passed to the constructor, or a method. If that is not possible, then the container is accessed, directly or indirectly, through the \Drupal class. – kiamlaluno Sep 30 '18 at 8:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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