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 SubscribeRun:
php bin/console debug:container
And... I'll make this a bit smaller so that everything shows up on one line. As we know, this command shows all of the services in our container... but only a small number of these are autowireable. We know that because a service is autowireable only if its ID, which is this over here, is a class or interface name.
So at first, it might look like the Twig service is not autowireable. After all, its ID - twig
- is definitely not a class or interface. But if you scroll up to the top... let's see... yep! There's another service in the container whose ID is Twig\Environment
, which is an alias to the service twig
. This is a little trick Symfony does to make services autowireable. If we type-hint an argument with Twig\Environment
, we get the twig
service.
However, most of services in this list do not have an alias like that. So they are not autowireable. And, that's usually fine. If a service isn't autowireable, it's probably because you'll never need to use it. But let's pretend that we do want to use one of these.
Check this one out! It's called twig.command.debug
. Open up another tab. Earlier, we ran:
php bin/console debug:twig
This shows us all of the functions and filters from Twig... which is nice! Well, surprise! This command comes from the twig.command.debug
service! Because, "everything in Symfony is done by a service" - even console commands.
As a challenge, let's see if we can inject this service into MixRepository
, execute it, and dump its output.
First things first. In MixRepository
, we just discovered that, in order to do our work, we need access to another service. What do we do? The answer: Dependency injection, which is that fancy word for adding another construct argument and setting it onto a property, which we can do all at once with private $twigDebugCommand
:
... lines 1 - 9 | |
class MixRepository | |
{ | |
public function __construct( | |
... lines 13 - 15 | |
private bool $isDebug, | |
private $twigDebugCommand, | |
) {} | |
... lines 19 - 28 | |
} |
If we stopped right now and refreshed... no surprise! We get an error. Symfony has no idea what to pass for that argument.
What if we added the type for this class? Back over in our terminal, we can see that this service is an instance of DebugCommand
. Over here, let's add that type-hint: DebugCommand
... we want the one from Symfony\Bridge\Twig\Command
. Hit "tab" to autocomplete that:
... lines 1 - 5 | |
use Symfony\Bridge\Twig\Command\DebugCommand; | |
... lines 7 - 11 | |
class MixRepository | |
{ | |
public function __construct( | |
... lines 15 - 18 | |
private DebugCommand $twigDebugCommand, | |
) {} | |
... lines 21 - 30 | |
} |
And then... refresh. Still an error! Okay, we should add the type-hint because we're good programmers. But... no matter how hard we try, this is not an autowireable service. So, how do we fix this?
There are two main ways. I'll show you the old way first, which I'm mostly doing because you'll see it in documentation and blog posts all over the place. In config/services.yaml
, just like we did earlier for the $isDebug
argument, override our service entirely. Say App\Service\MixRepository
, and add a bind
key. Then, we're going to hint what to pass to the $twigDebugCommand
argument.
The only tricky thing is figuring out what value to set. For example, if I go and copy the service ID - twig.command.debug
- and paste that here... that's not going to work! That's literally going to pass that string. If you refresh, yup!
Argument 4 must be of type
DebugCommand
, string given.
We need to tell Symfony to pass the service that has this ID. In these YAML files, there's a special syntax to do just that: prefix the service ID with the @
symbol:
... lines 1 - 12 | |
services: | |
... lines 14 - 32 | |
App\Service\MixRepository: | |
bind: | |
$twigDebugCommand: '@twig.command.debug' |
As soon as we do that... the fact that this doesn't explode means it's working!
But... let's remove this. Because I want to show you the new way do this... which leverages that same fancy Autowire
attribute.
Up here, say #[Autowire()]
, but instead of just passing a string, say service: 'twig.command.debug'
:
... lines 1 - 11 | |
class MixRepository | |
{ | |
public function __construct( | |
... lines 15 - 18 | |
#[Autowire(service: 'twig.command.debug')] | |
private DebugCommand $twigDebugCommand, | |
) {} | |
... lines 22 - 31 | |
} |
I love that! Before we try this, let's actually use the service. Head down to findAll()
. Executing a console command manually in your PHP code is totally possible. It's a little weird, but cool! We need to create an $output = new BufferedOutput()
object... then we can execute the command by saying $this->twigDebugCommand->run(new ArrayInput())
- this is, sort of, faking the command-line arguments - pass that an empty []
- then $output
. Whatever the command outputs will be set onto that object.
To see if it's working, just dd($output)
:
... lines 1 - 6 | |
use Symfony\Component\Console\Input\ArrayInput; | |
use Symfony\Component\Console\Output\BufferedOutput; | |
... lines 9 - 13 | |
class MixRepository | |
{ | |
... lines 16 - 24 | |
public function findAll(): array | |
{ | |
$output = new BufferedOutput(); | |
$this->twigDebugCommand->run(new ArrayInput([]), $output); | |
dd($output); | |
... lines 30 - 35 | |
} | |
} |
Testing time! Refresh... and got it! How fun is that?
All right, now that this is working, let's comment out this silliness. I'll keep our $twigDebugCommand
injected just for reference.
The key takeaway is this: most arguments to services will be autowireable. Yay! But when you hit an argument that is not autowireable, you can use the Autowire
attribute to point to the value or service you need.
Next: Remember when I told you that MixRepository
was the first service we ever created? Well... I lied. It turns out that our controllers have been services this whole time!
"Houston: no signs of life"
Start the conversation!
// 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
}
}