Service Class Name as Service id
Traditionally, our service ids looked like this: some underscored, lowercased version of the class name:
// ... 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:
// ... 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:
// ... 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
:
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
:
# 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 ~
:
// ... lines 1 - 8 | |
services: | |
// ... lines 10 - 19 | |
AppBundle\Security\LoginFormAuthenticator: ~ | |
// ... lines 21 - 39 |
Perfect! Let's finish the rest:
// ... 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 |
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
// ... 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.