Buy

Service Class Name as Service id

Traditionally, our service ids looked like this: some underscored, lowercased version of the class name:

41 lines app/config/services.yml
... lines 1 - 5
services:
... lines 7 - 10
app.markdown_transformer:
class: AppBundle\Service\MarkdownTransformer
arguments: ['@markdown.parser', '@doctrine_cache.providers.my_markdown_cache']
... lines 14 - 41

That's still totally legal. But in Symfony 3.3, the best practice has changed: a service's id should now be equal to its class name. I know, crazy, right! This makes life simpler. First, you don't need to invent an arbitrary service id. And second, class names as ids will work much better with autowiring and service auto-registration... as you'll see soon.

The only complication is when you have multiple services that point to the same class. We will handle this:

41 lines app/config/services.yml
... lines 1 - 5
services:
... lines 7 - 31
app.encouraging_message_generator:
class: AppBundle\Service\MessageGenerator
... lines 34 - 36
app.discouraging_message_generator:
class: AppBundle\Service\MessageGenerator
... lines 39 - 41

Using Class Service ids

So, let's start changing service ids! I'll copy the old app.markdown_transformer id and replace it with AppBundle\Service\MarkdownTransformer. When your service id is your class name, you can actually remove the class key to shorten things:

39 lines app/config/services.yml
... lines 1 - 8
services:
... lines 10 - 13
AppBundle\Service\MarkdownTransformer:
arguments: ['@markdown.parser', '@doctrine_cache.providers.my_markdown_cache']
... lines 16 - 39

Nice!

But when we did that... we broke our app! Any code referencing the old service id will explode! We could hunt through our code and find those now, but let's save that for later. There's a faster, safer way to upgrade to the new format.

Create Legacy Aliases to not Break Things

Create a new file in config called legacy_aliases.yml. Inside, add the normal services key, then paste the old service id set to @. Copy the new service id - the class name - and paste it: @AppBundle\Service\MarkdownTransformer:

7 lines app/config/legacy_aliases.yml
services:
app.markdown_transformer: '@AppBundle\Service\MarkdownTransformer'
... lines 3 - 7

This creates something called a service alias. This is not a new feature, though this shorter syntax is. A service alias is like a symbolic link: whenever some code asks for the app.markdown_transformer service, the AppBundle\Service\MarkdownTransformer service will be returned. This will keep our old code working with no effort.

To load that file, at the top of services.yml, add an imports key with resource set to legacy_aliases.yml:

39 lines app/config/services.yml
# Learn more about services, parameters and containers at
# http://symfony.com/doc/current/book/service_container.html
imports:
- { resource: legacy_aliases.yml }
... lines 5 - 39

Now, our app is happy again.

Updating all of the Service

Let's repeat that for the rest of our services. Honestly, when we upgraded KnpUniversity, this was the most tedious step: going one-by-one, copying each service id, copying the class name, and then adding it to legacy_aliases.yml. We wrote a dirty script that used the Yaml class to load services.yml and at least create the legacy_aliases.yml file for us.

Notice that LoginFormAuthenticator has no configuration anymore! Cool! Just set it to a ~:

39 lines app/config/services.yml
... lines 1 - 8
services:
... lines 10 - 19
AppBundle\Security\LoginFormAuthenticator: ~
... lines 21 - 39

Perfect! Let's finish the rest:

39 lines app/config/services.yml
... lines 1 - 8
services:
... lines 10 - 13
AppBundle\Service\MarkdownTransformer:
arguments: ['@markdown.parser', '@doctrine_cache.providers.my_markdown_cache']
AppBundle\Twig\MarkdownExtension:
#arguments: ['@app.markdown_transformer']
AppBundle\Security\LoginFormAuthenticator: ~
AppBundle\Doctrine\HashPasswordListener:
tags:
- { name: doctrine.event_subscriber }
AppBundle\Form\TypeExtension\HelpFormExtension:
tags:
- { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FormType }
... lines 29 - 39

7 lines app/config/legacy_aliases.yml
services:
app.markdown_transformer: '@AppBundle\Service\MarkdownTransformer'
app.markdown_extension: '@AppBundle\Twig\MarkdownExtension'
app.security.login_form_authenticator: '@AppBundle\Security\LoginFormAuthenticator'
app.doctrine.hash_password_listener: '@AppBundle\Doctrine\HashPasswordListener'
app.form.help_form_extenion: '@AppBundle\Form\TypeExtension\HelpFormExtension'

Multiple Services for the Same Class

39 lines app/config/services.yml
... lines 1 - 8
services:
... lines 10 - 29
app.encouraging_message_generator:
class: AppBundle\Service\MessageGenerator
... lines 32 - 34
app.discouraging_message_generator:
class: AppBundle\Service\MessageGenerator
... lines 37 - 39

The last two services are a problem: we can't set the id to the class name, because the class is the same for each! When this happens, use the old id naming convention. We're going to talk more about this situation soon.

And, we're done! We just changed all of the service ids to class names... but thanks to legacy_aliases.yml, our code won't break. This may not have felt significant, but it was actually a big step forward. Now, we can talk about private services.

Leave a comment!