1

Is it possible to pass dynamic parameters when calling a service?

mymodule.services.yml

services:
  my_module.default:
    class: Drupal\services\DefaultService
    arguments: ['@entity_type.manager']

DefaultService.php

namespace Drupal\my_module;
use Drupal\Core\Entity\EntityTypeManagerInterface;

/**
 * Class DefaultService.
 */
class DefaultService implements DefaultServiceInterface {

  /**
   * Drupal\Core\Entity\EntityTypeManagerInterface definition.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  private $myValue;

  /**
   * Constructs a new DefaultService object.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
    $this->entityTypeManager = $entity_type_manager;

    //how set $myValue?
    // $this->myValue = ???
  }

}

Is there a way to dynamically set myValue when calling this service, for example while calling the service from hook_ENTITY_TYPE_update(), passing the $entity->id() value?

I've found similar questions, such as How do I pass parameters to a service? or Create service with argument, but they don't answer my problem.

6
  • 3
    The links are not connected, they are about dependency injection. You need to put public methods in the service so that you can use it. In this case for example a setter method doing nothing else than setting this value.
    – 4uk4
    Oct 29, 2020 at 15:01
  • Yes, as I had to create the service, I've resorted to that solution meanwhile, but I didn't think it was the optimal solution. I mean, I'm calling that service already in three different points, and every time I need to also call $service->setMyValue(). Not too good. Passing all the necessary data to the "constructor" would be much more better, and more "oop", wouldn't it? However, if it not possible I'll stick to this way. Thank you nevertheless.
    – Giuseppe
    Oct 29, 2020 at 17:09
  • Solution for what problem? You didn't tell so far, only that you want to set a value for whatever reason. I mean setting a value in a service normally has a purpose, most times for retrieving and persisting a state in a request.
    – 4uk4
    Oct 29, 2020 at 17:39
  • 1
    Even if you use a service factory similar to DatabaseBackendFactory, you should still call a method to set that property.
    – apaderno
    Oct 29, 2020 at 19:25
  • 1
    Nothing wrong with this approach, but it seems like the arguments are local to the method performing the logic and you can pass them when you call the method.
    – 4uk4
    Oct 30, 2020 at 9:30

2 Answers 2

3

Giuseppe, I recommend defining your service using the Factory design pattern. You can then instance the factory and call a copy of your business logic class, initialized with the nid. I believe that this pattern is what @kiamlaluno was hinting at in his comment about using DatabaseBackendFactory.

Factory injection example

A simple and common example of this pattern would be getting an instance of a storage controller from the EntityTypeManager.

In your .services.yml file:

example_service:
  class: Drupal\my_module\MyExampleClass
  arguments: ['@entity_type.manager']

In your service class:

<?php

use Drupal\Core\Entity\EntityTypeManagerInterface;

class MyExampleClass {

  /**
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $termStorage;

  /**
   * @inheritDoc
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
    $this->termStorage = $entity_type_manager->getStorage('taxonomy_term');
  }
}

In the example above, the EntityTypeManager, which acts as a factory, has been injected, and then we call the getStorage() method to get an instance of \Drupal\Core\Entity\EntityStorageInterface that has been initialized for the taxonomy_term entity type.

Similarly, you could define a service that is, itself, a factory, and the factory would then be responsible for creating instances of your business logic class.

You could inject your factory service, as in the example above, by replacing the EntityTypeManager service with your own. You can also create an instance in a hook, like this:

my_module_node_view($node, $view_mode, $langcode) {
  // Get instance of your factory service.
  $my_service = \Drupal::service('my_service');
  // Get an instance of your business logic class from the factory,
  // initialized to use the nid that we provided from $node->id().
  $node_processor = $my_service->get($node->id());
  // Make the call to process the nid.
  $node_process->doBusinessLogic();
}

Plugins

It might also be helpful to know about Drupal 8's plugin system. Drupal 8's plugin system is essentially a specialized version of the factory pattern. If you have Drupal Console, then generating new Annotation plugins is easy. Depending on all of the requirements of your project, a custom plugin type might be exactly what you need.

Here are some tutorials and references on plugins that I have found to be useful:

2

Like @4k4 said you could approach it like this:

namespace Drupal\my_module;

use Drupal\Core\Entity\EntityTypeManagerInterface;

/**
 * Class DefaultService.
 */
class DefaultService implements DefaultServiceInterface {

  ...       

  private $myValue;

  /**
   * Constructs a new DefaultService object.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * Setter method.
   */
  public function setMyValue($value) {
    $this->myValue = $value;
    return $this;
  }

}

And then you can call it like:

/** @var \Drupal\my_module\DefaultServiceInterface $my_service */
$my_service = \Drupal::service('my_module.default');
$my_service->setMyValue($your_dynamic_value);

// Do more stuff.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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