This course is archived!
Service Autowiring
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
The next new feature is called autowiring
. And yes, it is magic. And yes, it's
super controversial - it's like the celebrity gossip of the Symfony world, right
along side annotations.
Autowiring makes registering services easier. Normally, a service needs a name, a class and its constructor arguments:
// ... lines 1 - 5 | |
services: | |
weird_authenticator: | |
class: AppBundle\Security\WeirdFormAuthenticator | |
arguments: ['@doctrine.orm.entity_manager', '@router'] |
What autowiring says is, "maybe we don't need to give it the arguments?". So remove
the arguments
key and instead type autowire: true
:
// ... lines 1 - 5 | |
services: | |
weird_authenticator: | |
class: AppBundle\Security\WeirdFormAuthenticator | |
# arguments: ['@doctrine.orm.entity_manager', '@router'] | |
autowire: true |
Head back to the browser, refresh the login page and press the login button. It doesn't
explode! How was it able to create the WeirdFormAuthenticator
!!!??? That's autowiring.
How does it Work?
It works via type-hints. In WeirdFormAuthenticator
we're type-hinting the first argument
with EntityManager
and the second argument with RouterInterface
:
// ... lines 1 - 2 | |
namespace AppBundle\Security; | |
// ... lines 4 - 18 | |
class WeirdFormAuthenticator extends AbstractGuardAuthenticator | |
{ | |
// ... lines 21 - 23 | |
public function __construct(EntityManager $em, RouterInterface $router) | |
{ | |
// ... lines 26 - 27 | |
} | |
// ... lines 29 - 106 | |
} |
Behind the scenes, well, in a "compiler pass" if you're super geeky and curious, autowiring
looks in the container and says "could you please show me all the services type-hinted with
EntityManager
?". Since there is only one, it auto-wires that service as the first
argument.
The same is true for RouterInterface
: it sees that there is only one service whose
class implements RouterInterface
, so it injects the router
service.
What if there are Multiple Services with the Class?
But what if there are multiple services that implement an interface or match a class? Well, frankly, your computer will catch on fire....
I'm kidding! Autowiring won't work, but it will throw a clear exception. Autowiring is magical... but not completely magical: it won't try to guess which entity manager you want. It's the Symfony-spin on auto-wiring.
If you do hit this problem, there is a way for you to tell the container which service you want to inject for the type-hinted class.
But the real beauty of autowiring is when it just works. The whole point is to save time so you can rapidly develop.
Moar Magic: Injecting Non-Existent Services
Autowiring has one more interesting trick. In the Security
directory, create a new
super important class called EvilSecurityRobot
. Give it one method:
public function doesRobotAllowAccess()
:
// ... lines 1 - 2 | |
namespace AppBundle\Security; | |
class EvilSecurityRobot | |
{ | |
public function doesRobotAllowAccess() | |
{ | |
return rand(0, 10) >= 5; | |
} | |
} |
Basically, the evil robot will decide, randomly, whether or not we can login. So even if I enter all the login fields correctly, the evil security robot could still say "NOPE! You're not getting in and I'm not sorry. <3 The Evil Robot".
The EvilSecurityRobot
is ready. To use this in WeirdFormAuthenticator
, pass it
as the third argument to the constructor: EvilSecurityRobot $robot
. Now create
a $robot
property and set it:
// ... lines 1 - 18 | |
class WeirdFormAuthenticator extends AbstractGuardAuthenticator | |
{ | |
// ... lines 21 - 22 | |
private $robot; | |
// ... line 24 | |
public function __construct(EntityManager $em, RouterInterface $router, EvilSecurityRobot $robot) | |
{ | |
// ... lines 27 - 28 | |
$this->robot = $robot; | |
} | |
// ... lines 31 - 114 | |
} |
In checkCredentials()
, if (!$this->robot->doesRobotAllowAccess())
then throw a
really clear new CustomUserMessageAuthenticationException()
that says "RANDOM
SECURITY ROBOT SAYS NO!":
// ... lines 1 - 60 | |
public function checkCredentials($credentials, UserInterface $user) | |
{ | |
if (!$this->robot->doesRobotAllowAccess()) { | |
throw new CustomUserMessageAuthenticationException( | |
'RANDOM SECURITY ROBOT SAYS NO!' | |
); | |
} | |
// ... lines 68 - 85 | |
} | |
// ... lines 87 - 116 |
And I'll even put quotes around that.
This is when we would normally go to services.yml
and update the arguments
key to inject the EvilSecurityRobot
. But wait! Before we do that, we need to register
the EvilSecurityRobot
as a service first.
Resist the urge! Instead, do nothing: absolutely nothing. Refresh the page. Fill in
weaverryan
and try to login until you se the error. Bam!
RANDOM SECURITY ROBOT SAYS NO!
It works! What the heck is going on?
Remember, weird_authenticator
is autowired. Because of that, Symfony sees that
the third argument is type-hinted with EvilSecurityRobot
. Next, it looks in the
container, but finds no services with this class. But instead of failing, it
creates a private service in the container automatically and injects that. This
actually works pretty well: EvilSecurityRobot
itself is created with autowiring.
So if it had a couple of constructor arguments, it would try to autowire those
automatically.
Oh, and if you have multiple autowired services that need an EvilSecurityRobot
,
the container will create just one private service and re-use it.
Some people will love this new feature and some people will hate it and complain on Twitter. But it's cool: like most things, you're not forced to use it. But, if you're doing some rapid application development, try it! It won't work in all cases, and isn't able to inject configuration yet, but when it works, it can save time, just like it did here for us.
Wield this new weapon wisely.
Hey, thank you for the autowiring explanation. It's amazing.
But still have a question about Constructor injection vs invoke injection.
Imagine that we have this ArtilceControler with a simple __invoke() function (Kind of ADR pattern)
Some times we need to inject arguments : objects, classes that behave like services, it could be Response, Request, ParameFetecher, MailerInterface, FormFactoryInterface Loggerinterface, CacheItemPoolInterface, RequestStack, ContainerInterface ...
I noticed that most of the time we inject some arguments typehinted with Request or Response object at the __invoke(Request $request... function of our controller. And most of interfaces like FormFactoryInterface are injected at the level of the constructor (after defining private properties)
1) Why Response and Request are directly injected into our functions ? Why they are not injected into our construtor? Are they considred as services that comes from our service container?I means that it's the autowiring who is regitring the Request or Response services?
2) What is the difference between RequestStack and Request from Httpfoundation, why we inject RequestStack at the level of our constructor and Request at the level of our __invoke (or any function inside our controller)?
3) For example, if we use the mailer, ther's 2 possibility to acess to mailer: We can inject MailerInterface either in the constructor or in __invoke. But what is the most recommended way?
4) According to which criteria we could decide if a class will be injected in __invoke or in the constructor ?