Symfony service subscribers

Nacho Colomina
3 min readJan 8, 2023

--

Symfony service subscribers are useful when we have a service that needs other services but we don’t know which one it will use. Instead of injecting all services in the constructor, we can create a service subscriber and inject it on the constructor. Let’s see how it works using an example.

Imagine we have a service which have to download a document but, depending on the country, it has to use a different service to perform the download.

The countries which differ on how to perform the download are:

  • Spain (ES)
  • France (FR)
  • Germany (DE)

The rest of the countries use the same method to perform the download. So, we will need four services:

  • One for handle ES download
  • One for handle FR download
  • One for handle DE download
  • One for handle other countries download
class ESDownloadHandler implements DownloadHandlerInterface
{
public function download(string $document): string
{
// download implementation
}
}

class FRDownloadHandler implements DownloadHandlerInterface
{
public function download(string $document): string
{
// download implementation
}
}

class DEDownloadHandler implements DownloadHandlerInterface
{
public function download(string $document): string
{
// download implementation
}
}

class DefaultDownloadHandler implements DownloadHandlerInterface
{
public function download(string $document): string
{
// download implementation
}
}

interface DownloadHandlerInterface
{
public function download(string $document): string;
}

As we can see in the code, we have one service for each custom country download. All of them implements DownloadHandlerInterface. Method download will perform it and return the path where file have been downloaded.

Now, let’s see how our service subscriber looks like:

use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;

class DownloadServiceSubscriber implements ServiceSubscriberInterface
{

public function __construct(
private readonly ContainerInterface $locator
) { }

public static function getSubscribedServices(): array
{
return [
'ES' => ESDownloadHandler::class,
'FR' => FRDownloadHandler::class,
'DE' => DEDownloadHandler::class,
'default' => DefaultDownloadHandler::class
];
}

/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function get(string $country): DownloadHandlerInterface
{
return ($this->locator->has($country)) ? $this->locator->get($country) : $this->locator->get('default');
}
}

A service which implements Symfony\Contracts\Service\ServiceSubscriberInterface (thanks to symfony autowiring) automatically becomes a service subscriber. When implementing it, you have to define the static method getSubscribedServices which must return a key/value array.

  • Keys: Contains the label which identify the service
  • Values: Contains the service identifier. (When using autowiring it’s normally the fully class name).

Finally, method get decides which service it’s going to be used. To do this, it makes use of locator service which holds services returned by getSubscribedServices. In this case, if there is a service identified by the country received as a parameter, that service will be used, otherwise service identified by default key will be used.

Now, let’s use our service subscriber in another service:

use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;

class DownloadHandler
{
public function __construct(
private readonly DownloadServiceSubscriber $downloadServiceSubscriber
){ }

/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function download(string $country, string $document): string
{
$downloadHandler = $this->downloadServiceSubscriber->get($country);
return $downloadHandler->download($document);
}
}

And thats all, our service injects DownloadServiceSubscriber and use it to get the download handler to use depending on country.

If you want to learn more about service subscribers and locators, visit symfony docs: https://symfony.com/doc/current/service_container/service_subscribers_locators.html

I hope you find this content useful :)

--

--