Console commands
Data imports, mailings and cleaning up old records are started from the terminal or on a schedule. You write your own bin/console commands for such jobs. A command is a service too: repositories, mail and everything else are available to it.
Creating
php bin/console make:command app:send-report// src/Command/SendReportCommand.php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'app:send-report', description: 'Sends the weekly report')]
class SendReportCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->success('The report was sent.');
return Command::SUCCESS;
}
}php bin/console app:send-reportBy convention the names of your own commands start with app:.
Arguments and options
They are described in the configure() method:
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
protected function configure(): void
{
$this
->addArgument('format', InputArgument::REQUIRED, 'File format')
->addOption('limit', null, InputOption::VALUE_REQUIRED, 'How many records', 100)
->addOption('active', null, InputOption::VALUE_NONE, 'Active only');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$format = $input->getArgument('format');
$limit = (int) $input->getOption('limit');
$onlyActive = $input->getOption('active');
return Command::SUCCESS;
}php bin/console app:export-users csv --limit=500 --activeStyled output
The SymfonyStyle class gives ready-made elements:
$io = new SymfonyStyle($input, $output);
$io->title('Exporting users');
$io->text('A plain line');
$io->warning('Some records were skipped');
$io->error('File not found');
$io->table(['Name', 'Email'], [['Anna', 'anna@example.com']]);Questions
$name = $io->ask('What should the file be called?', 'users.csv');
if (!$io->confirm('Delete the old records?', false)) {
return Command::SUCCESS;
}A progress bar
foreach ($io->progressIterate($users) as $user) {
$this->export($user);
}Services in a command
Dependencies are passed through the constructor:
public function __construct(
private UserRepository $users,
private MailerInterface $mailer,
) {
parent::__construct();
}Running on a schedule
On a server the command is added to the cron scheduler:
0 9 * * 1 php /var/www/blog/bin/console app:send-reportThis line runs the report every Monday at 9:00.
Common mistake
A command's own constructor must call parent::__construct(). Without it the command gets no name, and the console exits with an error. And remember to return Command::FAILURE on a failure: cron and deployment systems read the exit code to learn that the operation did not work.