Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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

By 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 --active

Styled 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-report

This 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.

Thanks for reading!

Donate to the site — we work hard for you.