Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

MakerBundle & Autoconfiguration

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

Congrats, team! We are done with the heavy stuff in this tutorial! So it's time for a victory lap. Let's install one of my favorite Symfony bundles: MakerBundle. Find your terminal and run:

composer require maker --dev

In this case, I'm using the --dev flag because this is a code generation utility that we only need locally, not on production.

This bundle, of course, provides services. But these services aren't really meant for us to use directly. Instead, all of the services from this bundle power a bunch of new bin/console commands. Run

php bin/console

and look for the make section. Ooh. There's a ton of stuff here for setting up security, generating doctrine entities for the database (which we'll do in the next tutorial), making a CRUD, and much more.

Generating a new Command Class

Let's try one: how about we try to build our own new custom console command that will appear in this list. To do that, run:

php bin/console make:command

This will interactively ask you for the name of the command. Let's say app:talk-to-me. You don't have to, but it's pretty common to prefix your custom commands with app:. And... done!

That created exactly one new file: src/Command/TalkToMeCommand.php. Let's go open that up:

<?php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'app:talk-to-me',
description: 'Add a short description for your command',
)]
class TalkToMeCommand extends Command
{
protected function configure(): void
{
$this
->addArgument('arg1', InputArgument::OPTIONAL, 'Argument description')
->addOption('option1', null, InputOption::VALUE_NONE, 'Option description')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$arg1 = $input->getArgument('arg1');
if ($arg1) {
$io->note(sprintf('You passed an argument: %s', $arg1));
}
if ($input->getOption('option1')) {
// ...
}
$io->success('You have a new command! Now make it your own! Pass --help to see your options.');
return Command::SUCCESS;
}
}

Cool! On top, you can see that the name and description of the command are done in a PHP attribute! Then, down in this configure() method, which we'll talk about more in a minute, we can configure arguments and options that can be passed from the command line.

When we run the command, execute() will be called... where we can print things out to the screen or read options and arguments.

Perhaps the best thing about this class is that... it already works. Check it out! Back at your terminal, run;

php bin/console app:talk-to-me

And... it's alive! It doesn't do much, but this output is coming from down here. Woo!

Autoconfiguration: Auto Discovering "Plugins"

But wait... how did Symfony instantly see our new Command class and know to start using it? Is it because it lives in the src/Command/ directory... and Symfony scans for classes that live here? Nope! We could rename this directory to ThereAreDefinitelyNoCommandsInHere... and Symfony would still see the command.

The way this works is much cooler. Open up config/services.yaml and look at the _defaults section:

... lines 1 - 12
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 18 - 32

We talked about what autowire: true means, but I didn't explain the purpose of autoconfigure: true. Because this is below _defaults, autoconfiguration is active on all of our services, including our new TalkToMeCommand service. When autoconfiguration is enabled, it basically tells Symfony:

Hey, please look at the base class or interface of each service, and if it looks like a class should be a console command... or an event subscriber... or any other class that hooks into a part of Symfony, please automatically integrate the service into that system. Okay, thanks. Bye!

Yep! Symfony sees that our class extends Command and thinks:

Hmm, I may not be a self-aware AI... but I bet this is a command. I better notify the console system about it!

I love autoconfiguration. It means that we can create a PHP class, extend whatever base class or implement whatever interface needed for the "thing" that we're building, and... it will just work.

Internally, if you want all the nerdy details, autoconfiguration adds a tag to your service, like console.command, which is what ultimately helps it get noticed by the console system.

All right, now that our command is working, let's have some fun and customize it next.

Leave a comment!

3
Login or Register to join the conversation

Hi, it would be interesting to understand how to manage a custom bundle with symfony 6.x for medium and large sized projects it is convenient for organizing the code, because many things seem to have changed, thanks

Reply

Hey @pasquale_pellicani!

I've added it to our screencast request list! Indeed, we have a tutorial about this, but it's showing its age.

Cheers!

1 Reply
Rufnex Avatar

Great one, thanks ;o)

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