Buy Access to Course
19.

Controllers are Services Too!

|

Share this awesome video!

|

Keep on Learning!

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

Login Subscribe

Open 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:

45 lines | src/Controller/VinylController.php
// ... 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:

32 lines | config/services.yaml
// ... 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:

51 lines | src/Controller/VinylController.php
// ... lines 1 - 10
class VinylController extends AbstractController
{
public function __construct(
private bool $isDebug,
private MixRepository $mixRepository
)
{}
// ... lines 18 - 36
#[Route('/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:

51 lines | src/Controller/VinylController.php
// ... lines 1 - 10
class VinylController extends AbstractController
{
public function __construct(
private bool $isDebug,
private MixRepository $mixRepository
)
{}
// ... lines 18 - 36
#[Route('/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.