Symfony 3.3: Upgrade, Autowiring & Autoconfigure

1:03:27
Buy Access

Symfony 3.3 comes with some FRESH new dependency injection changes! Yes, autowiring, autoconfigure, auto-registration of services! Autobots, roll out! In this tutorial, we will:

  • Upgrade to Symfony 3.3
  • Learn about the new _defaults keyword
  • Update service ids to be class names
  • Discuss public versus private services
  • Upgrade to the new services.yml format without breaking our project
  • Auto-register all classes in src/AppBundle as services (what!?)
  • Handling classes that have multiple services registered
  • Understanding autowiring and aliases
  • Using named arguments when autowiring fails

Say hello to the bigger, faster, strong way of service configuration!


Your Guides

Ryan Weaver

Questions? Conversation?

  • 2018-07-20 Diego Aguiar

    Hey Cameron Burns

    Haha, definitely you *must* throw your laptop into a swamp... Ok, being serious, I think you only have to exclude the "WebTestCase" class from being registered as a service.


    services:
    Twencha\Bundle\EventRegistrationBundle\:
    ...
    exclude: '../../src/Twencha/Bundle/EventRegistrationBundle/{entity,repository, WebTestCase.php}' # Probably you will have to update this to point to where "WebTestCase.php" actually lives

    Cheers!

  • 2018-07-20 Cameron Burns

    FileLoaderLoadException

    This is a strange error.

    When I add the lines:
    Twencha\Bundle\EventRegistrationBundle\:
    resource: '../../src/Twencha/Bundle/EventRegistrationBundle/*'
    exclude: '../../src/Twencha/Bundle/EventRegistrationBundle/{entity,repository}'

    Twencha\Bundle\EventRegistrationBundle\Controller\:
    resource: '../../src/Twencha/Bundle/EventRegistrationBundle/Controller'
    public: true
    tags: ['controller.service_arguments']

    to the app's app/config/services.yml file, it generates this error (on web refresh):
    Class Symfony\Bundle\FrameworkBundle\Test\WebTestCase not found in /home/cameron/twenchaFF/app/config/services.yml (which is being imported from "/home/cameron/twenchaFF/app/config/config.yml").

    the services.yml file (in full):
    imports:
    - { resource: legacy_aliases.yml }

    services:
    Twencha\Bundle\EventRegistrationBundle\:
    resource: '../../src/Twencha/Bundle/EventRegistrationBundle/*'
    exclude: '../../src/Twencha/Bundle/EventRegistrationBundle/{entity,repository}'

    Twencha\Bundle\EventRegistrationBundle\Controller\:
    resource: '../../src/Twencha/Bundle/EventRegistrationBundle/Controller'
    public: true
    tags: ['controller.service_arguments']

    note:
    if I remove:
    resource: '../../src/Twencha/Bundle/EventRegistrationBundle/*'
    it generates a different error.

    I've found the WebTestCase usage in boilerplate code (that I didn't write) in my app bundle:
    class DefaultControllerTest extends WebTestCase

    What do you think I should do? (throwing laptop out the window and into a swamp is an accepted answer)
    Any help is always appreciated! :-)

  • 2018-05-21 Victor Bocharsky

    Hey Nikolic,

    Your import statement looks valid except the important fact that this statement should be first in the file, try to move it to the top of file, so first you have import and then you have services declaration. But yes, it's totally doable to split services in different files like you said.

    P.S. Oh, and do not forget that "_defaults" option applies to the current file only, so you'll need to have its own _defaults in "listener_service.yml". Well, if you needed of course :)

    Cheers!

  • 2018-05-18 Nikolic Sladjan

    Hi, is there any chance to split services.yml into multiple files to keep it organized ?

    In example all services with tag which has additional params have to be defined manually. In example I have multiple listeners and I want to move them to separate file. This example works:

     
    services:
    _defaults:
    autowire: true
    autoconfigure: true
    public: false

    AppBundle\:
    resource: '../../src/AppBundle/*'
    exclude: '../../src/AppBundle/{Entity,Repository}'

    AppBundle\Listener\SomeListener:
    tags:
    - { name: kernel.event_listener, event: kernel.request, method: someMethodName}

    But if I do something like this

     
    services:
    _defaults:
    autowire: true
    autoconfigure: true
    public: false

    AppBundle\:
    resource: '../../src/AppBundle/*'
    exclude: '../../src/AppBundle/{Entity,Repository}'

    imports:
    - { resource: services/listener_service.yml }

    It doesn't work. I tried both to put import keyword at the beginning of the file and at the end but without success.

    Content of services/listener_service.yml is:

     
    services:
    AppBundle\Listener\SomeListener:
    tags:
    - { name: kernel.event_listener, event: kernel.request, method: someMethodName}

    Any ideas ?

  • 2018-02-26 Bruno Z.

    Hey Victor Bocharsky
    Oh yes, clear! I go with you second solution. Thanks!

  • 2018-02-26 Victor Bocharsky

    Hey Bruno,

    You can make this alias public:


    services:
    test_alias.AppBundle\Path\To\Service:
    class: '@AppBundle\Path\To\Service'
    public: true

    Or better make all the services inside services_test.yml file public with:


    // services_test.yml
    services:
    # default configuration for services in *this* file
    _defaults:
    autowire: true
    autoconfigure: true
    public: true

    test_alias.AppBundle\Path\To\Service: '@AppBundle\Path\To\Service'

    Cheers!

  • 2018-02-26 Bruno Z.

    ok, but with these declaration the service is not public automatically. right?
    I've to set the service public globally in services.yml File.

  • 2018-02-23 Diego Aguiar

    Hey Bruno Z.

    That's a good question! I'm not sure if there is an official way to do it yet, but here at Knp we like to tackle this problem by specifying some "test" aliases that we configure on "config_test.yml" or "services_test.yml" (is up to you). It should look something like this:


    // services_test.yml
    services:
    test_alias.AppBundle\Path\To\Service: '@AppBundle\Path\To\Service'

    Cheers!

  • 2018-02-23 Bruno Z.

    Hi,
    Problem: Using Services in Symfony Unit Tests. $client->getContainer()->get('....service...');
    This throws: The "...service..." service is private, getting it from the container is deprecated since Symfony 3.2 and will fail in 4.0. You should either make the service public, or stop using the container directly and use dependency injection instead.

    But how to inject a Service into a Unit Test Class in Symfony?

  • 2017-09-03 weaverryan

    Yea, congrats! You rock!

  • 2017-08-29 Victor Bocharsky

    Hey Mike,

    You're welcome! That's really cool when you can ask questions to someone who can help you during the learning, I know that ;)
    And congrats with finishing the course, well done!

    Cheers!

  • 2017-08-29 Mike

    Ive done the entire Starting in Symfony 3 course now and it was awesome! Great way to teach, encouraging and superb help if it was necessary! Thanks Team KNP!

  • 2017-07-31 weaverryan

    Hi Igor!

    Ah, then I think we have the cause! It looks like JMSDIExtraBundle creates a proxy for the entity manager. And - I believe because that proxy is just saved to a file in the cache directory - this class_exists() check fails and the entire container rebuilds... constantly. To know if this is the issue, add this config to config.yml


    jms_di_extra:
    doctrine_integration: false

    This will remove the proxy. However, this also removes the ability to inject dependencies into your repository (http://jmsyst.com/bundles/J.... Try the config to see if it helps. I believe this is a bug in JMSDiExtraBundle - a bug with its compatibility with something new in Symfony 3.3 (but I'm not 100% sure - could be a subtle Symfony bug). If you're interested, I would open an issue on the JMSDiExtraBundle repo.

    Let me know if it works!

  • 2017-07-26 Igor Weigel

    Hi Ryan,

    A) I do have the long proxy class: EntityManager597857be7d816_546a8d27f194334ee012bfe64f629947b07e4919\__CG__\Doctrine\ORM\EntityManager

    B) I do have JMSDIExtraBundle

    Thanks

  • 2017-07-10 weaverryan

    Hi Igor!

    Apologies for my slow reply! Ok, this is very interesting, and you've done some great debugging! Your ClassExistenceResource dump is really interesting: something is actually looking for the EntityManager as a proxy class. I don't see that in my config (I tried on this project). So, I have a few questions:

    A) If you run ./bin/console debug:container doctrine.orm.default_entity_manager, what is the class? Is it Doctrine\ORM\EntityManager or is it this long EntityManager...\__CG__\... proxy class?

    B) Do you have any special bundles installed that might be adding any other magic. Like JMSDIExtraBundle? I'm trying to figure out what causes the EntityManager to become proxied like this. I expect the proxy is at the root of the problem, but I can't repeat it.

    Cheers!

  • 2017-07-05 Diego Aguiar

    Hey Igor Weigel

    We are investigating your situation, it's really interesting :)
    Can you show us how look's like your test code ? the whole file if possible

    Cheers!

  • 2017-06-30 Igor Weigel

    I debugged a bit more and here is what happens in phpunit.
    I have some @dataProvider for some of my tests, which are called in early stage. If I debug the Kernel $cache->isFresh() at this point (while in a @dataProvider), the cache is fresh (all good).
    But once I'm in a test (so not in the @dataProvider anymore), $cache->isFresh() returns false, and the problems begin...^^"
    But I have no idea why..

  • 2017-06-30 Igor Weigel

    I tried to debug it for a while, and here is the difference with and without the autowiring:
    docroot/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Kernel.php:541


    if (!$cache->isFresh()) {

    with autowiring, the test result is true, therefore cache is rebuilt. without autowiring it doesn't go in the if statement.
    I've debug a little more to see what changed the result, and it seems that it comes from here:
    vendor/symfony/symfony/src/Symfony/Component/Config/ResourceCheckerConfigCache.php:113


    if ($checker->isFresh($resource, $time)) {


    and when I print the $resource, here is what I have:


    object(Symfony\Component\Config\Resource\ClassExistenceResource)#14539 (2) {
    ["resource":"Symfony\Component\Config\Resource\ClassExistenceResource":private]=>
    string(101) "EntityManager59564fd03a693_546a8d27f194334ee012bfe64f629947b07e4919\__CG__\Doctrine\ORM\EntityManager"
    ["existsStatus":"Symfony\Component\Config\Resource\ClassExistenceResource":private]=>
    int(0)
    }

    I was not able to understand more from here.. or to change it..

  • 2017-06-30 Igor Weigel

    I seem to face a major problem, with tests (unit and functional).
    Since now all dependencies are handled on container build (which takes more time with autowire), and phpunit rebuild the container on every request with


    \Symfony\Bundle\FrameworkBundle\Test\KernelTestCase::bootKernel()


    my tests take forever now, and when I mean forever, I mean that I was still not able to run them all (over night..).
    Do you have any solution for that by chance?

  • 2017-06-28 weaverryan

    Hey Igor Weigel!

    GREAT question - and it's something we need to document. The proper solution would be to create a public alias for the service you want to test in the test environment only:


    # app/config/config_test.yml
    services:
    test_alias.AppBundle\Service\MyService: ' @AppBundle\Service\MyService'

    Then, you would actually fetch test_alias.AppBundle\Service\MyService out of the container in your unit test. It's a nice idea too: you only need to expose your service as public in the test environment.

    Cheers!

  • 2017-06-28 Igor Weigel

    What if you do container->get() some services in tests.
    I see one example from the Symfony documentation here http://symfony.com/doc/curr...
    How would this be handled with private services?

  • 2017-06-15 Lasha Kitia

    As always, Knp creates useful courses and everything is clearly explained.
    Thank you Ryan for this!

  • 2017-05-26 Dan Costinel

    Looking forward to it :)