The Power Of Symfony Autoconfiguration And Autowiring: Tags for interfaces

Pedro Carlos Abreu Jiménez
3 min readJul 18, 2019

--

After been using the new Symfony autoconfiguration and autowiring for a while, I realized that I am a little lazy writing configurations. I just want to write a service class and require it in other one. And that’s ok, I don’t really need to write so much services config with those super power on Symfony. You only need to focus on your code and your business logic.

In this post, I’ll brind you some real life example of how to take advantage of autoconfiguring tags, the easy way (Next time about tagged collection). Let’s see an example that does not require extra configuration:

Tagging Doctrine entity listeners

When we want to execute some operation over an entity when it is going to be persisted, we can use entity listeners for particulars classes. Doctrine EntityListener is a lifecycle listener class used for an entity. It’s easy to implement. By default it doesn’t require anything to work properly.

namespace App\Entity;/** @Entity @EntityListeners({"App\Doctrine\Listener\UserListener"}) */
class User
{
// …
}

But… What happens if we want to inject some additional service to perform the operation we want to do?

namespace App\Doctrine\Listener;
use App\Operators\Operator;
class UserListener
{
private $operator;
public function __construct(Operator $operator)
{
$this->operator = $operator;
}

public function preUpdate(User $user, PreUpdateEventArgs $event)
{
$this->operator->process($user);
}
}

If you do this, you have to go to the services configuration, require the operator service in arguments, then you have to register the listener service to the doctrine listener resolver and that is a lot of work.

Let’s simplify this. First we can Autowire our services so if we have Symfony configured to register all classes as services you don’t have to require anything on arguments. And second Symfony DoctrineBundle got you covered. You can tag your service with `doctrine.orm.entity_listener` y woala it works.

services:
App\Doctrine\Listener\UserListener:
tags:
— { name: doctrine.orm.entity_listener }

We reduced a lot our work but we still have to do the same thing for every listener we create. When we have a big application our `services.yaml` is going be fat on every listener we add. The solution here is Autoconfiguring Tags.

Tagging with interfaces

With tags we can mark services to have a special behiavor. And when we are talking about behiavor with are talking about interfaces. So we can create our own EntityListenerInterface interface just to say “Hey this class right here is an entity listener” and then all entity listener with that interface can by tagged with `doctrine.orm.entity_listener`.

First, create the interface, (As far as I know there is not interface for it. If not, it could be a good PR to DoctrineBundle).

namespace App\Doctrine\Listener;interface EntityListenerInterface
{
}

The good part on Symfony 4 is that now we don’t have to create a Compiler Pass to register the tag, we can just configure under `_instanceof` special caracter on your services config file:

services:
_defaults:
# .. other configs like autowiring and autoconfiguration on true
_instanceof:
App\Doctrine\Listener\EntityListenerInterface:
tags: ['doctrine.orm.entity_listener']

If you don’t like yaml you can use php configuration:

// src/Kernel.php
class Kernel extends BaseKernel
{
// …
protected function build(ContainerBuilder $container)
{
$container->registerForAutoconfiguration(EntityListenerInterface::class)
->addTag(‘doctrine.orm.entity_listener’);
}
}

Now just by implementing the EntityListenerInterface interface on every entity listener, you can require any service you want also it will be registered by doctrine listeners resolver without doing any extra config for each one.

namespace App\Doctrine\Listener;
use App\Operators\Operator;
use App\Doctrine\Listener\EntityListenerInterface;
class UserListener implement EntityListenerInterface
{
// ...
}

Just like that, it’s ready to use!

--

--