> Symfony 3 >

Course Overview

Login to bookmark this course

Symfony 3.3: Upgrade, Autowiring & Autoconfigure

Discover Symfony 3.3's new dependency injection features: autowiring, autoconfigure, auto-registration of services.

  • 1616 students
  • EN Captions
  • EN Script
  • Certificate of Completion

Your Guides

About this course

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.3.0-RC1", // v3.3.0-RC1
        "doctrine/orm": "^2.5", // 2.7.5
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.7
        "symfony/monolog-bundle": "^3.1", // v3.2.0
        "symfony/polyfill-apcu": "^1.0", // v1.23.0
        "sensio/distribution-bundle": "^5.0", // v5.0.25
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.29
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.4
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.4
        "knplabs/knp-markdown-bundle": "^1.4", // 1.7.1
        "doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
        "stof/doctrine-extensions-bundle": "^1.2" // v1.3.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.7
        "symfony/phpunit-bridge": "^3.0", // v3.4.47
        "nelmio/alice": "^2.1", // v2.3.6
        "doctrine/doctrine-fixtures-bundle": "^2.3" // v2.4.1
    }
}

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!

Next courses in the Symfony 3: Starting in Symfony 3 section of the Symfony 3 Track!

23 Comments

Sort By
Login or Register to join the conversation
Default user avatar Lasha Kitia 6 years ago

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

66 | Reply |
Default user avatar Nikolic Sladjan 6 years ago edited

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 ?

11 | Reply |

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!

| Reply |
Cameron avatar Cameron 6 years ago

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! :-)

| Reply |

Hey Fox C.

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!

1 | Reply |
Default user avatar Igor Weigel 6 years ago edited

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?

| Reply |
Default user avatar Igor Weigel Igor Weigel 6 years ago edited

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..

| Reply |

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!

| Reply |

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!

| Reply |

Hi Ryan,

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

B) I do have JMSDIExtraBundle

Thanks

| Reply |

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/JMSDiExtraBundle/master/doctrine). 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!

| Reply |

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..

| Reply |
Default user avatar Bruno Z. 6 years ago

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?

| Reply |

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!

| Reply |

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

| Reply |

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!

| Reply |
Default user avatar Bruno Z. Victor 6 years ago edited

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

| Reply |
Default user avatar Dan Costinel 6 years ago

Looking forward to it :)

| Reply |
Default user avatar Igor Weigel 6 years ago

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?

| Reply |

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!

| Reply |
Mike P. avatar Mike P. 6 years ago

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!

| Reply |

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!

| Reply |

Yea, congrats! You rock!

1 | Reply |

Delete comment?

Share this comment

astronaut with balloons in space

"Houston: no signs of life"
Start the conversation!