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 SubscribeAt your terminal, run:
bin/console debug:container --parameters
One of the kernel
parameters is called kernel.debug
. In addition to environments, Symfony has this concept of "debug mode". It's true for the dev
environment and false for prod
. And, occasionally, it comes in handy!
Here's our new challenge (mostly to see if we can do it). Inside of MixRepository
, I want to figure out if we're in debug mode. If debug mode is true, we will cache for 5 seconds. If it's false, I want to cache for 60 seconds:
... lines 1 - 8 | |
class MixRepository | |
{ | |
public function __construct( | |
... lines 12 - 13 | |
private bool $isDebug | |
) {} | |
public function findAll(): array | |
{ | |
return $this->cache->get('mixes_data', function(CacheItemInterface $cacheItem) { | |
$cacheItem->expiresAfter($this->isDebug ? 5 : 60); | |
... lines 21 - 23 | |
}); | |
} | |
} |
Let's back up for a minute. Suppose you're working inside of a service like MixRepository
. Suddenly you realize that you need some other service like the logger. What do you do to get the logger? The answer: you do the dependency injection dance. You add a private LoggerInterface $logger
argument and property... then you use it down in your code. You'll do this tons of times in Symfony.
Let me undo that... because we don't actually need the logger right now. But what we do need is similar. Right now we're inside of a service and we've suddenly realized that we need some configuration (the kernel.debug
flag) to do our work. What do we do to get that config? The same thing! Add that as an argument to our constructor. Say private bool $isDebug
, and down here, use it: if $this->isDebug
, cache for 5 seconds, else cache for 60 seconds.
But... there's a slight complication... and I bet you already know what it is. When we refresh the page... yikes! We get a Cannot resolve argument
error. If you skip a bit, it says:
Cannot autowire service
App\Service\MixRepository
: argument$isDebug
of method__construct()
is type-hintedbool
, you should configure its value explicitly.
That makes sense. Autowiring only works for services. You can't have a bool $isDebug
argument and expect Symfony to somehow realize that we want the kernel.debug
parameter. I might be a wizard, but I don't have a spell for that. I can make a whole slice of pie disappear, though. With magic. Definitely.
How do we fix this? Open a file that we haven't looked at yet: config/services.yaml
:
# This file is the entry point to configure your own services. | |
# Files in the packages/ subdirectory configure your dependencies. | |
# Put parameters here that don't need to change on each machine where the app is deployed | |
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration | |
parameters: | |
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. | |
# makes classes in src/ available to be used as services | |
# this creates a service per class whose id is the fully-qualified class name | |
App\: | |
resource: '../src/' | |
exclude: | |
- '../src/DependencyInjection/' | |
- '../src/Entity/' | |
- '../src/Kernel.php' | |
# add more service definitions when explicit configuration is needed | |
# please note that last definitions always *replace* previous ones |
So far, we haven't needed to add any configuration for our MixRepository
service. The container saw the MixRepository
class as soon as we created it... and autowiring helped the container know which arguments to pass to the constructor. But now that we have a non-autowireable argument, we need to give the container a hint. And we do that in this file.
Head down to the bottom and add the full namespace of this class: App\Service\MixRepository
:
... lines 1 - 7 | |
services: | |
... lines 9 - 25 | |
App\Service\MixRepository: | |
... lines 27 - 29 |
Below that, use the word bind
. And below that, give the container a hint to tell it what to pass to the argument by saying $isDebug
set to %kernel.debug%
:
... lines 1 - 7 | |
services: | |
... lines 9 - 25 | |
App\Service\MixRepository: | |
bind: | |
'$isDebug': '%kernel.debug%' |
I'm using $isDebug
on purpose. That needs to exactly match the name of the argument in the class. Thanks to this, the container will pass the kernel.debug
parameter value.
And when we try it... it works! The two service arguments are still autowired, but we filled in the one missing argument so that the container can instantiate our service. Nice!
I want to talk more about the purpose of this file and all of the configuration up here. It turns out that a lot of the magic we've been seeing related to services and autowiring can be explained by this code. That's next.
hey @blanx
let's think different, you need 1 little variable (string, bool or any type) so you are injecting the whole big Container to use just this 1 value? Won't it be easier to inject only var you need with services.yaml
?
As for me it will be much cleaner way and also it will be more performant way
Cheers!
Hey @sadikoff, thx for your reply!
You have a good point :) The trade off, that it is more complicated. I thought it was no big deal, since the Kernel is loaded anyway and Symfony is so smart it only uses a reference...but maybe this was wishful thinking.
IIRC it has nothing with Symfony, but PHP and of course it will be a reference to class, but what will be better to have value immediately or get some class from reference and then call some method to get same raw value?
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
}
}
If I need the a Kernel Variable I simply inject the KernelInterface
public function __construct( private KernelInterface $kernel)
{
}
This way I don't need a binding in services.yaml
Is this an other way or bad practice?