Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Command: Autowiring & Interactive Questions

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

Last chapter team! Let's do this!

Ok, what if we need a service from inside our command? For example, let's say that we want to use MixRepository to print out a vinyl mix recommendation. How can we do that?

Well, we're inside of a service and we need access to another service, which means we need... the dreaded dependency injection. Kidding - not dreaded, easy with autowiring!

Add public function __construct() with private MixRepository $mixRepository to create and set that property all at once.

... lines 1 - 4
use App\Service\MixRepository;
... lines 6 - 17
class TalkToMeCommand extends Command
{
public function __construct(
private MixRepository $mixRepository
)
{
... line 24
}
... lines 26 - 55
}

Though, if you hover over __construct(), it says:

Missing parent constructor call.

To fix this, call parent::__construct():

... lines 1 - 4
use App\Service\MixRepository;
... lines 6 - 17
class TalkToMeCommand extends Command
{
public function __construct(
private MixRepository $mixRepository
)
{
parent::__construct();
}
... lines 26 - 55
}

This is a super rare situation where the base class has a constructor that we need to call. In fact, this is the only situation I can think of in Symfony like this... so not normally something you need to worry about.

Interactive Questions

Down here, let's output a mix recommendation... but make it even cooler by first asking the user if they want this recommendation.

We can ask interactive questions by leveraging the $io object. I'll say if ($io->confirm('Do you want a mix recommendation?')):

... lines 1 - 17
class TalkToMeCommand extends Command
{
... lines 20 - 34
protected function execute(InputInterface $input, OutputInterface $output): int
{
... lines 37 - 45
$io->success($message);
if ($io->confirm('Do you want a mix recommendation?')) {
... lines 49 - 51
}
... lines 53 - 54
}
}

This will ask that question, and if the user answers "yes", return true. The $io object is full of cool stuff like this, including asking multiple choice questions, and auto-completing answers. Heck, we can even build a progress bar!

Inside the if, get all of the mixes with $mixes = $this->mixRepository->findAll(). Then... we need just a bit of ugly code - $mix = $mixes[array_rand($mixes)] - to get a random mix.

Print the mix with one more $io method $io->note() passing I recommend the mix and then pop in $mix['title']:

... lines 1 - 17
class TalkToMeCommand extends Command
{
... lines 20 - 34
protected function execute(InputInterface $input, OutputInterface $output): int
{
... lines 37 - 45
$io->success($message);
if ($io->confirm('Do you want a mix recommendation?')) {
$mixes = $this->mixRepository->findAll();
$mix = $mixes[array_rand($mixes)];
$io->note('I recommend the mix: ' . $mix['title']);
}
... lines 53 - 54
}
}

And... done! By the way, notice this return Command::SUCCESS? That controls the exit code of your command, so you'll always want to have Command::SUCCESS at the bottom of your command. If there was an error, you could return Command::ERROR.

Tip

Whoops, the correct constant name if the command fails is Command::FAILURE!

Okay, let's try this! Head over to your terminal and run:

php bin/console app:talk-to-me --yell

We get the output... and then we get:

Do you want a mix recommendation?

Why, yes we do! And what an excellent recommendation!

All right, team! We did it! We finished - what I think is - the most important Symfony tutorial of all time! No matter what you need to build in Symfony, the concepts we've just learned will be the foundation of doing it.

For example, if you need to add a custom function or filter to Twig, no problem! You do this by creating a Twig extension class... and you can use MakerBundle to generate this for you or build it by hand. It's very similar to creating a custom console command: in both cases, you're building something to "hook into" part of Symfony.

So, to create a Twig extension, you would create a new PHP class, make it implement whatever interface or base class that Twig extensions need (the documentation will tell you that)... and then you just fill in the logic... which I won't show here.

That's it! Behind the scenes, your Twig extension would automatically be seen as a service, and autoconfiguration would make sure it's integrated into Twig... exactly like the console command.

In the next course, we'll put our new superpowers to work by adding a database to our app so that we can load real, dynamic data. And if you have any real, dynamic questions, we are here for you, as always, down in the comment section.

All right, friends. Thanks so much for coding with me and we'll see you next time.

Leave a comment!

5
Login or Register to join the conversation
Serhii R. Avatar
Serhii R. Avatar Serhii R. | posted 3 months ago

Thanks for this course !) I like your way of teaching

2 Reply
Orlando_Carvalho Avatar
Orlando_Carvalho Avatar Orlando_Carvalho | posted 20 days ago

Tks for the course!! Really helpful and enjoyable!!

Reply

Hey Orlando,

Thanks for your feedback! We're really happy to hear you liked this course and that it was helpful for you :)

Cheers!

Reply

Hey Ryan,

Thanks for these fun courses in Symfony 6 :)

Regarding the commands, is there a bundle that would allow commands to be run only once? I'm looking for behavior that's similar to doctrine migrations, where you put the migrations in the ./migrations directory and they get marked as executed in the database. So I would like to do the same for commands and put them in the ./tasks directory.
Currently I work around this by putting them in a ./src/Commands/Task/Run directory and naming them TaskYYYYMMDDhhmmss.php so that the console can "see" them and execute them (and mark it as executed in the database). I'm not sure on how to configure symfony's console in a way that it can pick up the commands from another (custom) directory.

So my question, is there an existing bundle which has this kind of behavior or where/how should I start if I want to configure this myself?

Regards,

Yves

Reply

Hey Senet,

That's an interesting question. I found this bundle that may fit your needs https://github.com/zenstruck/schedule-bundle/blob/master/doc/run-schedule.md

In case it does not fit your needs. I think you'll have to implement something similar to what Doctrine migrations do. In a database table, you'll have to keep track of all executed commands, and before executing a command, you'll have to check the table. Your naming strategy sounds good to me but keep in mind that you'll need a CRON job to run on your server every minute or so

For registering commands outside of the default directory, you can take advantage of Symfony's auto-configuration feature. You only need to ensure that the new commands directory is not excluded.

Cheers!

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