Symfony 3.3: Upgrade, Autowiring & Autoconfigure
Discover Symfony 3.3's new dependency injection features: autowiring, autoconfigure, auto-registration of services.
About this course
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
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 ?
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!
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! :-)
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!
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?
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..
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!
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!
Hi Ryan,
A) I do have the long proxy class: EntityManager597857be7d816_546a8d27f194334ee012bfe64f629947b07e4919\__CG__\Doctrine\ORM\EntityManager
B) I do have JMSDIExtraBundle
Thanks
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!
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..
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?
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!
ok, but with these declaration the service is not public automatically. right?
I've to set the service public globally in services.yml File.
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!
Hey victor
Oh yes, clear! I go with you second solution. Thanks!
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?
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!
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!
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!
As always, Knp creates useful courses and everything is clearly explained.
Thank you Ryan for this!