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 SubscribeIn practice, you rarely need to do anything inside of services.yaml
. Most of the time, when you add an argument to the constructor of a service, it's autowireable. So you add the argument, give it a type-hint... and keep coding!
... lines 1 - 8 | |
class MixRepository | |
{ | |
public function __construct( | |
private HttpClientInterface $httpClient, | |
private CacheInterface $cache, | |
private bool $isDebug | |
) {} | |
... lines 16 - 25 | |
} |
But the $isDebug
argument is not autowireable... since it's not a service. And that forced us to completely override the service so we could specify that one argument with bind
. It works but... that was... kind of a lot of typing to do such a small thing!
... lines 1 - 12 | |
services: | |
... lines 14 - 30 | |
App\Service\MixRepository: | |
bind: | |
'$isDebug': '%kernel.debug%' |
_defaults
So here's a different solution. Copy that bind
key, delete the service entirely, and up, under _defaults
, paste:
... lines 1 - 12 | |
services: | |
# default configuration for services in *this* file | |
_defaults: | |
autowire: true # Automatically injects dependencies in your services. | |
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. | |
bind: | |
'$isDebug': '%kernel.debug%' | |
... lines 20 - 32 |
When we move over and try this... the page still works! How cool is that? And, it makes sense. This section will automatically register MixRepository
as a service... and then anything under _defaults
will be applied to that service. So the end result is exactly what we had before.
I love doing this! It allows me to set up project-wide conventions. Now that we have this, we could add an $isDebug
argument to the constructor of any service and it will instantly work.
By the way, if you want, you can also include the type with the bind.
So this would now only work if we use the bool
type-hint with the argument:
... lines 1 - 12 | |
services: | |
# default configuration for services in *this* file | |
_defaults: | |
autowire: true # Automatically injects dependencies in your services. | |
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. | |
bind: | |
'bool $isDebug': '%kernel.debug%' | |
... lines 20 - 32 |
If we used string
, for example, Symfony would not try to pass in that value.
So the global bind is awesome. But starting in Symfony 6.1, there's another way to specify a non-autowireable argument. Comment out the global bind
. I do still like doing this... but let's try the new way:
... lines 1 - 12 | |
services: | |
# default configuration for services in *this* file | |
_defaults: | |
autowire: true # Automatically injects dependencies in your services. | |
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. | |
# bind: | |
# 'bool $isDebug': '%kernel.debug%' | |
... lines 20 - 32 |
If we refresh now, we get an error because Symfony doesn't know what to pass to the $isDebug
argument. To fix that, go into MixRepository
and, above the argument (or before the argument if you're not using multiple lines), add a PHP 8 attribute called Autowire
. Normally, PHP 8 attributes will auto-complete, but this isn't auto-completing for me. That's actually due to a bug in PhpStorm. To get around this, I'm going to type out Autowire
... then go to the top and start adding the use
statement for this manually, which does give us an option to auto-complete. Hit "tab" and... tah dah! If you want to make them alphabetical, you can move it around.
You may also notice that it's underlined with a message:
Attribute cannot be applied to a property [...]
Again, PhpStorm is a bit confused because this is both a property and an argument.
Anyway, go ahead and pass this an argument %kernel.debug%
:
... lines 1 - 6 | |
use Symfony\Component\DependencyInjection\Attribute\Autowire; | |
... lines 8 - 9 | |
class MixRepository | |
{ | |
public function __construct( | |
... lines 13 - 14 | |
#[Autowire('%kernel.debug%')] | |
private bool $isDebug | |
) {} | |
... lines 18 - 27 | |
} |
Refresh now and... got it! Pretty cool, right?
Next: most of the time when you autowire an argument like HttpClientInterface
, there's only one service in the container that implements that interface. But what if there were multiple HTTP clients in our container? How could we choose the one we want? It's time to talk about named autowiring.
This Autowire
Annoation is very cool. I like to use the ParameterBag (ParameterBagInterface) object whenever I need some parameter inside my services.
But when I think about it more, it might be better for testing reasons to use the parameter itself inside the constructor.
Anyways I love to watch symfonycast videos even after all these years. It doesn't stop to be interesting.
Hey Braunstetter,
Yeah, it's always a trade-off :) That service is cool, but passing parameters into the constructor might be more straightforward and so simpler to test. Probably it depends on the number of parameters you need, if it's 1-3 params - I'd go with passing them into the constructor. If you need a lot of them - the service would be better in this case.
Cheers!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"knplabs/knp-time-bundle": "^1.18", // v1.19.0
"symfony/asset": "6.1.*", // v6.1.0-RC1
"symfony/console": "6.1.*", // v6.1.0-RC1
"symfony/dotenv": "6.1.*", // v6.1.0-RC1
"symfony/flex": "^2", // v2.1.8
"symfony/framework-bundle": "6.1.*", // v6.1.0-RC1
"symfony/http-client": "6.1.*", // v6.1.0-RC1
"symfony/monolog-bundle": "^3.0", // v3.8.0
"symfony/runtime": "6.1.*", // v6.1.0-RC1
"symfony/twig-bundle": "6.1.*", // v6.1.0-RC1
"symfony/ux-turbo": "^2.0", // v2.1.1
"symfony/webpack-encore-bundle": "^1.13", // v1.14.1
"symfony/yaml": "6.1.*", // v6.1.0-RC1
"twig/extra-bundle": "^2.12|^3.0", // v3.4.0
"twig/twig": "^2.12|^3.0" // v3.4.0
},
"require-dev": {
"symfony/debug-bundle": "6.1.*", // v6.1.0-RC1
"symfony/maker-bundle": "^1.41", // v1.42.0
"symfony/stopwatch": "6.1.*", // v6.1.0-RC1
"symfony/web-profiler-bundle": "6.1.*" // v6.1.0-RC1
}
}
The Autowire annotation is amazing!
Heads-up for anyone else who might have trouble using the
#[Autowire()]
attribute:It requires Symfony 6.1 (and PHP 8.1)
My dev environment used PHP 8.0.8, and as a result always installed Symfony 6.0.
After upgrading PHP to v 8.1.11, my new Symfony project start out using Symfony 6.2, and the annotation works.