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.
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.
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.
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 ahmedbhs
I'll answer your questions in the same order:
1) First of all in a Controller's action you don't inject a Response object, you're supposed to return one. You inject the Request object so you can get access to all the parameters coming from it (like $_POST, $_GET, $_SERVER)
2) The "Request" object as I mentioned above contains all those global parameter but the "RequestStack" is a collection of requests because you can have many request coming from a single request. There are two types of requests, Master and Sub request, probably if you give it a check to the "Kernel" class you will understand it better
3) I usually inject my services as method arguments, but if there is a service that I'll use on ALL controller's action, then, I would inject it on the constructor. The reason of injecting it on the method is because I don't want to instantiate objects that I won't use in the given request.
I believe this last answer, answers all your related questions about injecting services in the constructor or as a method argument
Cheers!
What about auto-wiring for method calls?
Imagine I'm defining a controller a service, and I'm not going down the route of creating an individual class for each controller - so we can agree that a controller doesn't adhere closely to SRP. Controllers have multiple methods, indexAction, postAction etc, for example.
Can I type hint for something in one of those methods, and have it auto wired for me? Or is it just constructor injection that's provided? If so, why is method injection not available? I'm not asking for setter injection, just obvious, basic method injection like the constructor has.
Hey James!
Yea, great conversation question. First, the reason auto-wiring isn't available for method calls is simply that you (other than the controller) call your methods directly - there's no information about method arguments in the service container itself. The service container only cares about instantiating your objects.
The only time that Symfony calls your method automatically is in the case of the controller. And in this special case, you can take a different route to accomplish what you need. Specifically, you could register a listener (on kernel.controller), read the type-hints from your arguments, fetch services out for those type-hinted classes, and inject them as arguments. This is a non-trivial task of course, and you probably won't want to loop over *every* service to find which match your type-hint. Instead, you'll probably need some configuration that says "type hint Foo\Bar goes to service foo_bar". For people defining controllers as services, it's actually a pretty rad idea... and if done well, could maybe be introduced into SensioFWExtraBundle.
Anyways, you can kind of get an idea on how to make something like this from SensioFrameworkExtraBundle - it's ParamConverter does exactly this (i.e. it looks at the type-hint of arguments and uses that to automatically query for an entity of that type).
Let me know how it goes :)
Hey Ryan,
There's already an injector I use called Auryn that provides a couple of methods that provide similar functionality. It has make() which creates an object and its dependencies recursively, and execute() which, given a callable, object + method etc, uses the same introspective logic to recursively instantiate dependencies of the given object's method. I've been using this in a microframework for a while for my controllers and, for me, it's really the only thing that's missing for jumping over to the full Symfomy framework.
I've done some research into the kernel and, short of rewriting the whole thing, it doesn't look like I can override the controller calling. As awesome and modular as Symfony and its Kernel is, that call_user_func() for the controller + method combo is in a private method! Of all the things! If only that call could be placed in another protected method so it can be overriden, perhaps this is something we could collaborate on if it still piques your interest?
Relevant research in my SO question: http://stackoverflow.com/qu...
I'll take a look at the framework bundle and ParamConverter. As developers tend to start working on a new feature in the controller layer, the ability to get working with it quickly in controller actions (although as you said, being an edge case, is still a common one) would be awesome.
Thanks for the quick and in depth response! I enjoy following how KNP is doing and looking forward to checking out the Symfony 3 tuts!
Hey James!
Yea, I'm definitely interested in this :). So, the stuff in Kernel is private on purpose (which I'm sure you suspected already). Symfony uses call_use_func_array(), but you can hook into both the functionality that determines the controller callable and the functionality that determines that callable's arguments. So, if Auryn is able to give you back the arguments it determines, but *not* actually call the callable, then you'll be able to hook in without any issues. You would do that either by overriding the ControllerResolver or (preferably, cause it's easier to hook into existing projects) by using my previous recommendation. Basically, if you register a listener on kernel.controller, then whatever "stuff" you add to $request->attributes becomes available as a controller arg. For example, if I say `$request->attributes->set('router', $myRouter)`, then this $myRouter will be passed to a $router argument of my controller method (if it has one). That matching is done by name, but you could have originally figured out the object to pass to the $router argument by using Auryn to "guess" based on the type-hint etc.
So, as I said, non-trivial - but all the hook points should be there :).
Cheers!
Hi, thanks for the tutorial. I have a question though... You said that if your ran into the case where multiple classes implement the interface, there is a way to tell symfony which to use. In Symfony 3.3 I think you are talking about "Manually wiring arguments" (https://symfony.com/doc/3.3... but I don't know how to do it in 3.2 and previous.
Any help would be appreciated. Thanks!
Hey Matias C.
That's a good question. Since you are not in Symfony 3.3, you cannot take advantage of auto-registration, so, what you will have to do is register a new service for each class that you want to specifically inject/use, and manually wire up the service what is going to use it.
Cheers!
Actually my problem was injecting the event_dispatcher. In Symfony 3.3 and up, I had to use the interface, otherwise, I had to use a specific dispatcher. I could have wired the service manually, but instead I removed the EventDispatcher from the constructor and used the "calls" with "@event_dispatcher". This solved the issue, hope that this helps.
Hey Matias C.
I'm glad to hear that you could fix your problem, but if you really want to inject it, you could create an alias for the "EventDispatcherInterface" and point it to the specific dispatcher that you want to use for all general cases
Maybe this videos may help you understanding how to handle problems like this
https://knpuniversity.com/s...
https://knpuniversity.com/s...
Cheers!
Thanks for the tutorial! How can we do that with doctrine repository as a service in Symfony with autowire?
Hey Waleed,
I think it's impossible out of the box since you need to use factory, i.e. call getRepository() method like:
services:
app_blog_repository:
class: AppBundle\Repository\BlogRepository
factory: ['@doctrine.orm.default_entity_manager', getRepository]
arguments:
- AppBundle\Entity\Blog
And pass an entity name as a string as the first argument to the method. But the string could not be resolved with autowiring, you have to specify it manually.
Cheers!
Thank you! new factory config looks like
services:
app_blog_repository:
class: AppBundle\Repository\BlogRepository
factory: 'doctrine.orm.default_entity_manager:getRepository'
arguments:
- AppBundle\Entity\Blog
Why should somebody hate Autowire'ing? I love it.
Iam new to the SF world, my guess would be performance because of "guessing" the arguments?
Btw. as always AWESOME way of teaching!
Hey Mike!
Maybe they just don't like magic, or they have lived some of the few edge cases about autowiring
Cheers!
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.0.*", // v3.0.0
"doctrine/orm": "~2.5@dev", // 2.7.x-dev
"doctrine/doctrine-bundle": "~1.6@dev", // 1.10.x-dev
"doctrine/doctrine-cache-bundle": "~1.2@dev", // 1.3.2
"symfony/security-acl": "~3.0@dev", // dev-master
"symfony/swiftmailer-bundle": "~2.3", // v2.3.8
"symfony/monolog-bundle": "~2.7@dev", // dev-master
"sensio/distribution-bundle": "~5.0@dev", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.11
"incenteev/composer-parameter-handler": "~2.0", // v2.1.2
"doctrine/doctrine-fixtures-bundle": "^2.3", // v2.4.1
"composer/package-versions-deprecated": "^1.11" // 1.11.99
},
"require-dev": {
"sensio/generator-bundle": "~3.0", // v3.0.0
"symfony/phpunit-bridge": "~2.7" // v2.7.6
}
}
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 ?