If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeAll this auto-registration stuff works smoothly: unused services are removed, you can override a service to configure it further, and if there are any problems autowiring a service, you get a clear exception when you try to load any page.
But, there's one place where it's not as clean: when you have multiple services that point to the same class:
... lines 1 - 8 | |
services: | |
... lines 10 - 45 | |
app.encouraging_message_generator: | |
class: AppBundle\Service\MessageGenerator | |
... lines 48 - 51 | |
app.discouraging_message_generator: | |
class: AppBundle\Service\MessageGenerator | |
... lines 54 - 57 |
In other words, when you have services where the id can't be the class name. Right now, there are two services for the MessageGenerator
class, right? Actually, there are three.
Find your terminal and run:
php bin/console debug:container --show-private | grep Message
Surprise! This lists our two services plus a third service that was automatically registered. Now technically, that's not a problem. Nobody is referencing the new auto-registered service, so it's removed. But, it may cause an issue with autowiring in the future.
First, we need to talk about how autowiring works. It's super simple... or at least, it will be simple in Symfony 4.
Let's look at HashPasswordListener
. As you can see, this is type-hinted with UserPasswordEncoder
:
... lines 1 - 7 | |
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder; | |
class HashPasswordListener implements EventSubscriber | |
{ | |
... lines 12 - 13 | |
public function __construct(UserPasswordEncoder $passwordEncoder) | |
{ | |
... line 16 | |
} | |
... lines 18 - 63 | |
} |
In services.yml
, this argument is not specified:
... lines 1 - 8 | |
services: | |
... lines 10 - 37 | |
AppBundle\Doctrine\HashPasswordListener: | |
tags: | |
- { name: doctrine.event_subscriber } | |
... lines 41 - 57 |
It works because the container is autowiring it.
But how does it know which service to pass here? First, autowiring looks for a service whose id exactly matches the type-hint. In other words, it looks for a service whose id is Symfony\Component\Security\Core\Encoder\UserPasswordEncoder
:
... lines 1 - 7 | |
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder; | |
class HashPasswordListener implements EventSubscriber | |
{ | |
... lines 12 - 13 | |
public function __construct(UserPasswordEncoder $passwordEncoder) | |
{ | |
... line 16 | |
} | |
... lines 18 - 63 | |
} |
If that exists, it's used... always. This is the main way that autowiring works. It's not magic: you explicitly configure a service or alias for each type-hint.
That's also why we started using class names as our service ids: this allows all of our services to be autowired into other classes.
Before we keep going: I want to repeat this one more time: autowiring works by looking for a service - or alias - whose id exactly matches the type-hint. It's that simple.
I wanted to repeat that, because - if there is not a service whose id matches the type-hint, autowiring tries two other things. But, but but! One of these things will be removed in Symfony 4 and the other thing will never happen in practice.
But, we need to talk about these two things so that your 3.3 app makes sense. If there is not a service whose id matches the type-hint, autowiring will look at every service in the container and see which services have the class or interface. If two or more services have it, Symfony will throw a clear exception. We'll see that later. But if exactly one service is found, that service is used. However, this is deprecated and will not work in Symfony 4.
If autowiring finds zero services that have the class, it will auto-register that class as a new autowired service. But, this will never happen for us... because we've auto-registered all of our classes as services. And this magic auto-registration is not done for vendor classes.
So basically, autowiring looks for a service whose id exactly matches the type-hint. And in Symfony 4, if that service doesn't exist, it will throw a clear exception. Until then, some of your type-hints may still be autowired via the old deprecated logic. We'll see and fix this in a few minutes.
Next, let's see how this relates to our MessageGenerator
services, and how we can fix them.
Because typehinting on interface has much more flexibility than on exact class. That allows you to change realisation of injected class without changing all the typehints where it's used.
Hey Trafficmanagertech
As far as I know that change was made to encourage programmers to depend upon interfaces instead of concrete classes
Have a nice day
But if there's two or more classes in the container, which implement that interface or extend that concrete class, autowiring won't work, right?
// 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
}
}
I get this deprecation error: "Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won't be supported in version 4.0. Try changing the type-hint for argument "$em" of method "AppBundle\Service\Settings::__construct()" to one of its parents: interface "Doctrine\ORM\EntityManagerInterface", or interface "Doctrine\Common\Persistence\ObjectManager"."
I don't understand, why I have to typehint the EntityManagerInterface and not the EntityManager?