My Symfony command template
Published on 2019-11-24 • Modified on 2019-11-24
You can generate a command with the maker bundle (php bin/console make:command
). I use a slightly different template where the help text is dynamically generated with constants. These are also used as parameters to set the various options, so you don't have duplicated code. Remove the constructor parameter as it is specific to this blog (It's a parameter bound in services.yaml
). To run the command, I will use a CommandTester
this time instead of using the Process
component.
<?php
declare(strict_types=1);
// src/Command/ShowVersionCommand.php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Helper\TableSeparator;
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;
/**
* Displays the current application version. This is also the template I copy/paste
* when having to write a new command.
*/
#[AsCommand(
name: self::NAMESPACE.':'.self::CMD,
description: self::DESCRIPTION)
]
final class ShowVersionCommand extends BaseCommand
{
public const string CMD = 'version';
public const string DESCRIPTION = 'Shows the current application version number.';
// BaseCommand is an abstract class extending Symfony\Component\Console\Command\Command
// and contains the project main namespace
private string $appVersion;
public function __construct(string $appVersion)
{
$this->appVersion = $appVersion;
parent::__construct();
}
protected function configure(): void
{
[$desc, $class] = [self::DESCRIPTION, self::class];
$this
// InputArgument::REQUIRED / OPTIONAL / IS_ARRAY (3)
->addArgument('arg1', InputArgument::OPTIONAL, 'Argument description')
// InputOption::VALUE_NONE / VALUE_REQUIRED / VALUE_OPTIONAL / VALUE_IS_ARRAY / VALUE_NEGATABLE (5)
->addOption('option1', null, InputOption::VALUE_NONE, 'Option description')
->setHelp(
<<<EOT
$desc
COMMAND:
<comment>$class</comment>
DEV:
<info>%command.full_name% -vv</info>
PROD:
<info>%command.full_name% --env=prod --no-debug</info>
EOT
);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title(self::DESCRIPTION);
if ($output->isVerbose()) {
$io->note('This note will only be displayed when using at least the verbose mode option for the command "-v"');
// if you want to test on a given level only, use:
// if ($output->getVerbosity() === OutputInterface::VERBOSITY_VERBOSE)
}
if ($output->isVerbose()) {
$io->note('This note will only be displayed when using at least the verbose mode option for the command "-v"');
}
$output->writeln('This line will only be displayed when using at least the verbose mode option for the command "-v"', OutputInterface::VERBOSITY_VERBOSE);
// snippet L62+9 used in templates/snippet/code/_164.html.twig
$io->section('Version');
$io->note(BaseCommand::NAMESPACE.' '.$this->appVersion);
$io->newLine();
$io->section('Let test the SymfonyStyle for CLI now, this is a section');
$io->title('This is a title');
$io->block('This is a block');
$io->newLine(2);
$io->text('This is a text (two new lines before)');
$io->comment('This is a comment');
$io->caution('This is a caution');
$io->error('This is an error');
$io->info('This is an info');
$io->note('This is a note');
$io->listing(['this', 'is', 'a', 'listing']);
$io->table(
['This', 'is', 'a', 'table'],
[
['Cell 1-1', 'Cell 1-2', 'Cell 1-3', 'Cell 1-4'],
['Cell 2-1', 'Cell 2-2', 'Cell 2-3', 'Cell 2-4'],
['Cell 3-1', 'Cell 3-2', 'Cell 2-3', 'Cell 3-4'],
]
);
$io->horizontalTable(
['This is an', 'horizontal Table'],
[
['Cell 1-1', 'Cell 1-2'],
['Cell 2-1', 'Cell 2-2'],
['Cell 3-1', 'Cell 3-2'],
]
);
$io->definitionList(
'This is a definition list',
['foo1' => 'bar1'],
['foo2' => 'bar2'],
['foo3' => 'bar3'],
new TableSeparator(),
'with keys => values',
['foo4' => 'bar4']
);
$io->success('This is a success');
$io->section("That's all folks!");
return self::SUCCESS;
}
}
Bonus, the snippet to run this code: 🎉<?php
declare(strict_types=1);
namespace App\Controller\Snippet;
use App\Command\ShowVersionCommand;
use SensioLabs\AnsiConverter\AnsiToHtmlConverter;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Process\Process;
/**
* I am using a PHP trait to isolate each snippet in a file.
* This code should be called from a Symfony controller extending AbstractController (as of Symfony 4.2)
* or Symfony\Bundle\FrameworkBundle\Controller\Controller (Symfony <= 4.1).
* Services are injected in the main controller constructor.
*
* @property KernelInterface $kernel
* @property ShowVersionCommand $showVersionCommand
*/
trait Snippet58Trait
{
public function snippet58(): void
{
$process = new Process([
'php',
$this->kernel->getProjectDir().'/bin/console',
'strangebuzz:version',
'--ansi',
]);
$process->run();
echo (new AnsiToHtmlConverter())->convert($process->getOutput()); // That's it! 😁
}
}
Run this snippet ≪ this.showUnitTest ? this.trans.hide_unit_test : this.trans.show_unit_test ≫ More on Stackoverflow Read the doc Random snippet
<?php
declare(strict_types=1);
namespace App\Tests\Integration\Controller\Snippets;
use App\Command\SendSlackNotificationCommand;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\CommandTester;
/**
* @see Snippet58Trait
*/
final class Snippet58Test extends KernelTestCase
{
/**
* @see Snippet58Trait::snippet58
*/
public function testSnippet58(): void
{
$app = new Application(self::createKernel());
$command = $app->find(SendSlackNotificationCommand::NAME);
$commandTester = new CommandTester($command);
$message = 'Hello World!';
$commandTester->execute([
'command' => $command->getName(),
'message' => $message,
]);
self::assertStringContainsStringIgnoringCase($message, $commandTester->getDisplay());
}
}