Symfony: how to effectively use tagged services

Over the past few years I’ve written some abominations in Symfony when it comes down to handling generic operations, such as handling entity updates etc.

Usually these abominations used a mixture of reflection, annotations and some other black magic I’d rather not get into (involving a database. 🙄)

If you are a lazy programmer like me that also hates repetitive code, then I have some good news for you, Symfony offers a handy tool called tagged services.

With these tagged services, you can basically tell the dependency injection container that you’d like to give any service that inherits a certain base class, interface or what have you a tag. This also works for entire namespaces, but that’s honestly just asking for trouble.

A sample of how you’d do this, looks a bit like this:

_instanceof:
App\Domain\Grid\GridInterface:
tags: ['grid']

The code above takes all classes that inherit our GridInterface, and provides it with a happy little tag.

Now, you can easily create a service that takes all these tagged services and resolve it to whatever you need. I like to call these services resolvers, since they usually, well, resolve what service I’m looking for based upon an argument such as an Entity, or in this case a datagrid object.

The service definition of a resolver looks like this:

App\Domain\Grid\GridResolver:
arguments:
- !tagged 'grid'

And the class is as follows:

<?php

namespace App\Domain\Grid;

class GridResolver
{
/**
*
@var GridInterface[]
*/
private $grids;

public function __construct(iterable $grids)
{
$this->grids = $grids;
}

public function getGrid(string $name): ?GridInterface
{
foreach ($this->grids as $grid) {
if ($grid->getName() === $name) {
return $grid;
}
}

return null;
}
}

As you can see, it takes in the name of the requested datagrid (e.g. customers) and returns the appropriate datagrid, or null if you made a typo. 😅

With this, you can easily create a generic rendering controller as such:

<?php

namespace App\Module\Grid\Controller;

use App\Domain\Grid\GridResolver;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class RenderGridController extends AbstractController
{
private GridResolver $gridResolver;

public function __construct(GridResolver $gridResolver)
{
$this->gridResolver = $gridResolver;
}

public function __invoke(Request $request, string $grid): Response
{
$grid = $this->gridResolver->getGrid($grid);
// do some awesome stuff to render your grid (in my case JSON)
}
}

And that’s basically all there is to it!

As you can imagine, you can use this handy little tool given to you by Symfony in many versatile ways. Some example implementations could be generic invoice generation, handling entity events, exception factories for API clients etc.

Thank you for reading my article, this is my first Medium article. If you have any feedback feel free to let me know, it’s much appreciated!

Software developer, CEO, DevOps & Symfony fanatic