Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Constructors for your Controller

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

Autowiring works in exactly two places. First it works for controller actions. Arguments can either have the same name as a route wildcard - that's actually not autowiring - just a feature of controllers - or have a type-hint for a service:

... lines 1 - 4
use App\Service\MarkdownHelper;
... lines 6 - 12
class ArticleController extends AbstractController
{
... lines 15 - 22
/**
* @Route("/news/{slug}", name="article_show")
*/
public function show($slug, MarkdownHelper $markdownHelper)
{
... lines 28 - 60
}
... lines 62 - 73
}

Tip

Actually, there are a few other types of arguments you can get in your controller. You'll learn about them as we continue!

The second place autowiring works is the __construct() method of services:

... lines 1 - 4
use Michelf\MarkdownInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
class MarkdownHelper
{
... lines 11 - 15
public function __construct(AdapterInterface $cache, MarkdownInterface $markdown, LoggerInterface $markdownLogger, bool $isDebug)
{
... lines 18 - 21
}
... lines 23 - 42
}

And actually, this is the real place where autowiring is meant to work: Symfony's container - and its autowiring logic - is really good at instantiating objects.

Binding Non-Service Arguments to Controllers

In services.yaml, we added an $isDebug bind and used it inside of MarkdownHelper:

... lines 1 - 5
services:
# default configuration for services in *this* file
_defaults:
... lines 9 - 14
# setup special, global autowiring rules
bind:
... line 17
$isDebug: '%kernel.debug%'
... lines 19 - 34

So... could we also add an $isDebug argument to a controller function? It certainly makes sense, so let's try it!

Add bool $isDebug - the bool part is optional, it doesn't change any behavior. Below, just dump it:

... lines 1 - 12
class ArticleController extends AbstractController
{
... lines 15 - 25
public function show($slug, MarkdownHelper $markdownHelper, $isDebug)
{
dump($isDebug);die;
... lines 29 - 62
}
... lines 64 - 75
}

Try it. Refresh! And... woh! It does not work!

Controller ArticleController::show() requires that you provide a value for the $isDebug argument.

This... probably should work, and there's a good chance that it will work in the future. The bind functionality is relatively new. And it has one edge-case that does not currently work: you cannot use it to bind non-service arguments to controllers.

I know, kinda weird. But, we're working on it - and it might already work by the time you watch this. Yay open source!

Adding a Constructor to your Controller

Remove the $isDebug argument. So... how can we access non-service values - like parameters - from inside our controller? The answer is simple! Remember: our controller is a service, just like MarkdownHelper. And we're now pretty good at working with services, if I do say so myself.

Tip

In Symfony 4.1, the base AbstractController will have a $this->getParameter() shortcut method.

Add public function __construct() with a bool $isDebug argument. Then, dump that variable and die:

... lines 1 - 12
class ArticleController extends AbstractController
{
public function __construct(bool $isDebug)
{
dump($isDebug);die;
}
... lines 19 - 30
public function show($slug, MarkdownHelper $markdownHelper)
{
... lines 33 - 65
}
... lines 67 - 78
}

Immediately when we refresh... it works! I'll press Alt+Enter and select "Initialize fields" to create an $isDebug property and set it. I don't actually need to use this - but let's keep it as an example - I'll add a comment:

... lines 1 - 12
class ArticleController extends AbstractController
{
/**
* Currently unused: just showing a controller with a constructor!
*/
private $isDebug;
public function __construct(bool $isDebug)
{
$this->isDebug = $isDebug;
}
... lines 24 - 83
}

So, it's not as convenient as fetching a value via an argument to your controller action, but it works just the same. And actually, like I mentioned earlier, the container's job is really to instantiate services. And so autowiring should really only work for __construct() functions! In fact, the only reason that it also works for controller actions is for convenience! Yep, one core Symfony dev once said to another:

Hey! Autowiring is great! But since it's so common to need services in a controller function, maybe we should make it work there too!

And then some other core developer said:

Oh man, that sounds great! Virtual high-five!!

This is not really that important. But the point is this: Symfony's container is great and instantiating service objects and using autowiring to pass values to their constructor. But every other function call - um, except for controller actions - will not have this magic. So don't expect it.

Thanks to bind, we can define what values are passed to specific argument names. But, we can go further and control what value should be passed for a specific type hint. That's next.

Leave a comment!

12
Login or Register to join the conversation
Luis M. Avatar
Luis M. Avatar Luis M. | posted 5 years ago

Worked on Symfony 4.1.1!

7 Reply

Hey Luis,

Thanks for confirming that it works!

Cheers!

Reply
Qcho Avatar

I've seen you use `dump(...);die;` a lot. It seems on Symfony 4.1 you can now use `dd(...)` -> dump & die. BTW great screencast!

2 Reply

Hey Qcho,

Good catch! Actually, we know about dd() function, but we're on Symfony 4.0 in this screencast :)
Thanks for sharing it with others BTW!

Cheers!

Reply
Richard Avatar
Richard Avatar Richard | posted 3 years ago

yup, working here. 4.4.2

Reply

Hey Maxii123,

Thanks for confirming it works for you on 4.4.2 :)

Cheers!

Reply

I would like to see that historical proposal talk between those "two devs".
If anyone finds it, please share!

Reply

Probably one of those devs was Ryan!

1 Reply

And the other was Fabien! 😂

Reply

hahaha you bet!

Reply
Petru L. Avatar
Petru L. Avatar Petru L. | posted 5 years ago

SF 4.08 and still not working to bind non service arguments to controllers :D

Reply

Hey Petru L.!

That's correct :). It will work in Symfony 4.1, which comes out in May). We can only add new features in the "minor" releases (e.g. 4.1 or 4.2) - we can't add any features (only bug fixes) in the "patch" releases (e.g. 4.0.7 or 4.0.8). The fix HAS been merged though - so it's definitely coming in 4.1.0! Look for it!

Cheers!

2 Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.1.4
        "symfony/asset": "^4.0", // v4.0.4
        "symfony/console": "^4.0", // v4.0.14
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/framework-bundle": "^4.0", // v4.0.14
        "symfony/lts": "^4@dev", // dev-master
        "symfony/twig-bundle": "^4.0", // v4.0.4
        "symfony/web-server-bundle": "^4.0", // v4.0.4
        "symfony/yaml": "^4.0" // v4.0.14
    },
    "require-dev": {
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.4
        "symfony/debug-bundle": "^3.3|^4.0", // v4.0.4
        "symfony/dotenv": "^4.0", // v4.0.14
        "symfony/maker-bundle": "^1.0", // v1.0.2
        "symfony/monolog-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.0.4
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.0.4
    }
}
userVoice