Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Manual Service Config in services.yaml

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 $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

At 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
});
}
}

Dependency Injection!

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.

Non-Autowireable Arguments

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-hinted bool, 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.

Configuring MixRepository in services.yaml

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.

Leave a comment!

5
Login or Register to join the conversation

If I need the a Kernel Variable I simply inject the KernelInterface

public function __construct( private KernelInterface $kernel)
{

    $kernel->isDebug();

}

This way I don't need a binding in services.yaml

Is this an other way or bad practice?

Reply

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!

1 Reply

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.

Reply

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!

1 Reply

@sadikoff Thx for your explanation, appreciate it!

Reply
Cat in space

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

What PHP libraries does this tutorial use?

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