The new Symfony 3.3 Service Configuration Changes Explained

tl;dr Symfony 3.3 comes with some big new service configuration features. We've explained them here: The Symfony 3.3 DI Container Changes Explained

In less than 2 weeks, Symfony 3.3 will be released. It comes with a lot of new stuff, but there is one feature that stands out: the new service configuration. I am very excited about these changes: they're designed to accelerate development, make Symfony easier to learn and encourage best-practices (e.g. injecting specific dependencies instead of using $container->get())... without sacrificing predictability and stability.

If you haven't seen it yet, the services.yml file for a new Symfony 3.3 project will look like this:

services:
    # default configuration for services in *this* file
    _defaults:
        # automatically injects dependencies in your services
        autowire: true
        # automatically registers your services as commands, event subscribers, etc.
        autoconfigure: true
        # this means you cannot fetch services directly from the container via $container->get()
        # if you need to do this, you can override this setting on individual services
        public: false

    # makes classes in src/AppBundle available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    AppBundle\:
        resource: '../../src/AppBundle/*'
        # you can exclude directories or files
        # but if a service is unused, it's removed anyway
        exclude: '../../src/AppBundle/{Entity,Repository}'

    # controllers are imported separately to make sure they're public
    # and have a tag that allows actions to type-hint services
    AppBundle\Controller\:
        resource: '../../src/AppBundle/Controller'
        public: true
        tags: ['controller.service_arguments']

There's a lot going on, including service auto-registration, autowiring and auto-tagging (autoconfigure).

Of course, these features are (and will always be) optional: you can upgrade your project to Symfony 3.3 without making any changes. But, I hope you'll give these new features a chance: I've already upgraded a large project and love them.

We've written an in-depth article explaining all of this further on the documentation: The Symfony 3.3 DI Container Changes Explained.

Try it, and let us know what you think!

Comments

I'm happy for these great changes and glad you made this more deep explanation. Good job!

I've put together similar post, but much shorter and with before/after examples for those with lack of time :)
https://www.tomasvotruba.cz/blog/2017/05/07/how-to-refactor-to-new-dependency-injection-features-in-symfony-3-3/
@Tomáš I had seen that post, and was happy about it - great explanation!
Ryan, how does this new autowire and autoconfig ability affect referencing services via global variables in Twig? Can it reference only public services via service tags, in a pre-3.3 manner? Could you please update the docs here: http://symfony.com/doc/master/templating/global_variables.html
Hey Mark!

GREAT question :). First, the services used here do not need to be public. That's actually true anywhere: if you have a private service, and need to use that service's id to configure something (e.g. a Twig global, a Guard authentication or the session handler_id (http://symfony.com/doc/current/session/sessions_directory.html)), you can use public or private services here. The only thing you *can't* do with a private service is access it from the container via $container->get().

Second, when you auto-register the services like in the new default configuration, the id of your services will match your class. So, in the link, your new config will look something like :

user_management: '@AppBundle\Service\UserManagement'

And yes, I must have missed that doc page - we've switched to showing class names as service ids in most places in the docs (though of course, both are valid still!)

Cheers!
@Ryan Thanks a lot!
Great feature, has something changed with accessing services in functional tests?
@Konrad Yes and no. In integration tests (e.g. where you boot up the kernel and access services via the container to try them directly), you aren't able to access private services. In this situation, for now, you'll need to mark those services specifically as public. We may think of a fancier solution in the future, we'll see :).

Cheers!
@Konrad About my last comment, here's the proper solution. Suppose you have a service: AppBundle\Service\FooManager that is private... but you want to test it directly via an integration test. Add the following:

# app/config/config_test.yml
services:
test.foo_manager: '@AppBundle\Service\FooManager'

This will create a public service called 'test.foo_manager'. You can use that in your test. And the rest of your configuration stays nice and clean :).

Cheers!
@saliniverma so we are now just posting spam here huh ...
What about services who depend on container parameters (e.g. arguments: [ '%api_key%' ]). How are these arguments autowired?
I'm new to symfony and I have a lot of doubts when I read documentation.
After a long time I could figure out that fully-qualified means e.g. AppBundle\Service\InvoiceGenerator. (Everything is in documentation... I'm a newbie)

My problem was that I was expecting to see my new Service class in the list of the container.
$ bin/console debug:container ProductService
The new class is never shown on this debug info.

The other thing that was not clear for me, is that from comments I was trying to add a Class in a Service folder and I could use it in the controller. Not so simple as it seems.
_defaults publics is false so I had an error by doing this:

$ps = $this->get('AppBundle\Service\ProductService');

my solution was change the service.yml a little bit:
AppBundle\:
resource: '../../src/AppBundle/*'
exclude: '../../src/AppBundle/{Entity,Repository,Service}'

and add this:

AppBundle\Service\:
resource: '../../src/AppBundle/Service'
public: true
tags: ['service.service_arguments']

I don't know if it is a good solution, as I said before, I'm starting to develop with this wonderful framework and I'm not sure of anything.
I made a comment for two reasons... someone give me a hint or help someone else on the same situation.
@Marc Verney looks like they will not be autowired and need to be configured manually. But just this one. E.g:

arguments: ["@mailer", "@translator.default", '%email.default.receiver%']

You can change to:

arguments:
$receiver: '%email.default.receiver%'

Comments are closed.

To ensure that comments stay relevant, they are closed for old posts.