Controllers are Services Too!
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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOpen up src/Controller/VinylController.php
. It may or may not be obvious, but our controller classes are also services in the container! Yep! They feel special because they're controllers... but they're really just good old, boring services like everything else. Well, except that they have one superpower that nothing else has: the ability to autowire arguments into their action methods. Normally, autowiring only works with the constructor.
Binding Action Arguments
And, the action methods really do work just like the constructors when it comes to autowiring. For example, add a bool $isDebug
argument to the browse()
action... then dump($isDebug)
below:
// ... lines 1 - 10 | |
class VinylController extends AbstractController | |
{ | |
// ... lines 13 - 31 | |
public function browse(MixRepository $mixRepository, bool $isDebug, string $slug = null): Response | |
{ | |
dump($isDebug); | |
// ... lines 35 - 42 | |
} | |
} |
And that... doesn't work! So far, the only two things that we know we are allowed to have as arguments to our "actions" are (A), any wildcards in the route like $slug
and (B) autowireable services, like MixRepository
.
But now, go back to config/services.yaml
and uncomment that global bind
from earlier:
// ... lines 1 - 12 | |
services: | |
# default configuration for services in *this* file | |
_defaults: | |
// ... lines 16 - 17 | |
bind: | |
'bool $isDebug': '%kernel.debug%' | |
// ... lines 20 - 32 |
This time... it works!
Adding a Constructor
Going in the other direction, because controllers are services, you can absolutely have a constructor if you want. Let's move MixRepository
and $isDebug
up to a new constructor. Copy those, remove them... add public function __construct()
, paste... then I'll put them on their own lines. To turn them into properties, add private
in front of each:
// ... lines 1 - 10 | |
class VinylController extends AbstractController | |
{ | |
public function __construct( | |
private bool $isDebug, | |
private MixRepository $mixRepository | |
) | |
{} | |
// ... lines 18 - 36 | |
'/browse/{slug}', name: 'app_browse') | (|
public function browse(string $slug = null): Response | |
{ | |
// ... lines 40 - 48 | |
} | |
} |
Back down below, we just need to make sure we change to dump($this->isDebug)
and add $this->
in front of mixRepository
:
// ... lines 1 - 10 | |
class VinylController extends AbstractController | |
{ | |
public function __construct( | |
private bool $isDebug, | |
private MixRepository $mixRepository | |
) | |
{} | |
// ... lines 18 - 36 | |
'/browse/{slug}', name: 'app_browse') | (|
public function browse(string $slug = null): Response | |
{ | |
dump($this->isDebug); | |
// ... lines 41 - 42 | |
$mixes = $this->mixRepository->findAll(); | |
// ... lines 44 - 48 | |
} | |
} |
Nice! If we try this now... that works just fine!
I don't normally follow this approach... mainly because adding arguments to the action method is just so darn easy. But if you need a service or other value in every action method of your class, you can definitely clean up your argument list by injecting it through the constructor. I'll go remove that dump()
.
Next, let's talk about environment variables and the purpose of the .env
file that we looked at earlier. This stuff will become increasingly important as we make our app more and more realistic.