Buy

Service Autowiring

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:

10 lines app/config/services.yml
... 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:

11 lines app/config/services.yml
... 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:

108 lines src/AppBundle/Security/WeirdFormAuthenticator.php
... 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():

12 lines src/AppBundle/Security/EvilSecurityRobot.php
... 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:

116 lines src/AppBundle/Security/WeirdFormAuthenticator.php
... 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!":

116 lines src/AppBundle/Security/WeirdFormAuthenticator.php
... 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.

Leave a comment!

  • 2016-01-19 weaverryan

    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!

  • 2016-01-19 J7mbo

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

    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!

  • 2016-01-19 weaverryan

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

  • 2016-01-19 J7mbo

    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.