Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
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.

Start your All-Access Pass
Buy just this tutorial for $6.00

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.

Leave a comment!

17
Login or Register to join the conversation

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)


class ArticleController
{ 
    private $form;
    
    public construct __construct(FormFactoryInterface $form)
    {
        $this->form = $form;
    }
    
    /**
     * @param ParamFetcherInterface $paramFetcher
     * @FOSRest\QueryParam(name="page", requirements="\d+", default="1")
     *
     * @FOSRest\View()
     */
    public function __invoke(Request $request, ParamFetcherInterface $paramFetcher)
    {
        return $this->em->getRepository(Article::class)->findAll();
    }

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 ?

Reply

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!

Reply
Default user avatar

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.

Reply

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

Reply
Default user avatar

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!

Reply

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!

1 Reply
amcastror Avatar
amcastror Avatar amcastror | posted 5 years ago

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!

Reply

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!

Reply
amcastror Avatar

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.

Reply

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!

Reply
amcastror Avatar

Ok, I'll give it a try, thanks.

Reply
Default user avatar
Default user avatar Waleed Gadelkareem | posted 5 years ago

Thanks for the tutorial! How can we do that with doctrine repository as a service in Symfony with autowire?

Reply

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!

1 Reply
Default user avatar
Default user avatar Waleed Gadelkareem | Victor | posted 5 years ago | edited

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
20 Reply

Ah, the new factory syntax, nice! Thanks for sharing it

1 Reply
Mike P. Avatar
Mike P. Avatar Mike P. | posted 5 years ago

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!

Reply

Hey Mike!

Maybe they just don't like magic, or they have lived some of the few edge cases about autowiring

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// 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
    }
}
userVoice