Score:1

Injecting a NEW instance of a service into another service class

de flag

I have a service (for sending error alerts) which is used by multiple other services.

The alert service is injected into those classes via the relevant services.yml files and through their constructors.

However, both classes share the same instance of the service (something I actually didn't realise until now). If I set a property from one class, it's available for the other class to use later.

In some cases this will be great, but in my use case I want each class to start with a fresh instantation of the alert service class.

Is there a way, through services.yml, the constructors, or any other means, to achieve this?

Jaypan avatar
de flag
Services are by design singletons - they are only ever meant to be instantiated a single time. Services provide functionality, they aren't necessarily meant for data storage, so likely you need to rethink the architecture behind how you are storing the data, and store it relative to an object, or in the class calling the service.
Geat avatar
de flag
That makes sense. Is there a non-singleton equivalent of a service that can be injected into a class?
Jaypan avatar
de flag
No, but if it wasn't a singleton, it wouldn't need to be injected, you can just instantiate a new instance of your class as needed.
Geat avatar
de flag
I prefer the idea of using a factory service, especially for classes that have a host of constructor arguments.
4uk4 avatar
cn flag
In Drupal services are by default singletons (shared). But you can define a non-shared service. See https://symfony.com/doc/current/service_container/shared.html
Geat avatar
de flag
@4k4 I get the impression this isn't how Drupal intends services to be used, but it is exactly what I was looking for.
Score:3
cn flag

The direct answer is quoting the Symfony documentation:

In the service container, all services are shared by default. This means that each time you retrieve the service, you’ll get the same instance. This is usually the behavior you want, but in some cases, you might want to always get a new instance.

In order to always get a new instance, set the shared setting to false in your service definition:

# config/services.yaml
services:
    App\SomeNonSharedService:
        shared: false
        # ...

https://symfony.com/doc/current/service_container/shared.html

Discussing Drupal intentions is difficult because there aren't many references. When Drupal 8 was designed this service setting didn't exist and the predecessor was back then already disputed because of the now removed request scope. I couldn't find any references that Drupal is biased against using non-shared services. In the contrary, here a discussion where core finally switched from a factory to a non-shared service, which in fact is also a factory based solution, only that this is a factory already implemented in Symfony:

https://www.drupal.org/project/drupal/issues/2660124#comment-12318542

Score:2
ph flag

There are a couple options that come to mind:

  1. Create a factory service that provides instances of your non-service alert class
  2. Implement your alert class as a plugin

Which one you choose really depends on your architecture.

Geat avatar
de flag
I refactored the code, and the factory service works beautifully.
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.