Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Binding Global Arguments

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

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

Login Subscribe

In this file, we've registered everything in src/ as a service and activated autowiring on all of them. That's all you need... most of time. But sometimes, a service needs a bit more configuration.

That's what we're doing at the bottom for MarkdownHelper:

... lines 1 - 8
services:
... lines 10 - 26
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\Service\MarkdownHelper:
bind:
$isDebug: '%kernel.debug%'

This service is registered above thanks to auto-registration. But down here, we're overriding that service: we're replacing the auto-registered one with our own so that we can add the extra bind config. This still has autowire and autoconfigure enabled on it, thanks to the _defaults section:

... lines 1 - 8
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.
... lines 14 - 33

But we can now add any extra config that we need on this one service.

Moving bind to _defaults

When you have an argument that can't be autowired, there's actually another, easier way to fix it. Our custom config says that we want the $isDebug argument to MarkdownHelper - that's the third argument - to be set to the kernel.debug parameter. What if we moved this up to the _defaults section?

Do that: copy the bind lines, delete that service entirely:

... lines 1 - 8
services:
... lines 10 - 29
App\Service\MarkdownHelper:
bind:
$isDebug: '%kernel.debug%'

And then, up under _defaults, paste:

... lines 1 - 8
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 16 - 31

Let's see if it works! When we refresh, no errors... and the $isDebug flag that we're dumping is still true! This is awesome!

When you add a bind to _defaults, you're setting up a global convention: we can now have an $isDebug argument in any of our services and Symfony will automatically know to pass the kernel.debug parameter. Thanks to this, we no longer need to override the MarkdownHelper service to add the bind: it's already there! This is how I typically handle non-autowireable arguments.

Adding a Type to the Bind

Oh, and if you want, you can add the type to the bind - like bool $isDebug:

... lines 1 - 8
services:
# default configuration for services in *this* file
_defaults:
... lines 12 - 13
bind:
bool $isDebug: '%kernel.debug%'
... lines 16 - 31

This will still work because we have the bool type-hint in MarkdownHelper. When we refresh, yep! No errors.

But now, remove the bool type-hint and refresh again. Error!

Cannot autowire MarkdownHelper: argument $isDebug has no type-hint, you should configure its value explicitly.

Pretty cool. Let's put our bool type-hint back so it matches our bind exactly. And... now that it's working, I'll remove the dump() from MarkdownHelper:

... lines 1 - 7
class MarkdownHelper
{
... lines 10 - 13
public function __construct(MarkdownParserInterface $markdownParser, CacheInterface $cache, bool $isDebug)
{
... lines 16 - 18
dump($isDebug);
}
... lines 21 - 31
}

Here's the big picture: most arguments can be autowired. And when you have one that can't, you can set a bind on the specific service or set a global bind, which is the quickest option.

Next, let's talk about what happens when there are multiple services in the container that implement the same interface. How can we choose which one we want?

Leave a comment!

0
Login or Register to join the conversation
Cat in space

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

This tutorial also works great for Symfony 6!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.3.0 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
        "sensio/framework-extra-bundle": "^6.0", // v6.2.1
        "sentry/sentry-symfony": "^4.0", // 4.0.3
        "symfony/asset": "5.0.*", // v5.0.11
        "symfony/console": "5.0.*", // v5.0.11
        "symfony/debug-bundle": "5.0.*", // v5.0.11
        "symfony/dotenv": "5.0.*", // v5.0.11
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/framework-bundle": "5.0.*", // v5.0.11
        "symfony/monolog-bundle": "^3.0", // v3.6.0
        "symfony/profiler-pack": "*", // v1.0.5
        "symfony/routing": "5.1.*", // v5.1.11
        "symfony/twig-pack": "^1.0", // v1.0.1
        "symfony/var-dumper": "5.0.*", // v5.0.11
        "symfony/webpack-encore-bundle": "^1.7", // v1.8.0
        "symfony/yaml": "5.0.*" // v5.0.11
    },
    "require-dev": {
        "symfony/maker-bundle": "^1.15", // v1.23.0
        "symfony/profiler-pack": "^1.0" // v1.0.5
    }
}
userVoice