Symfony Console
02 / 02

Composing Commands & Machine Output

Composing Commands & Machine Output

Calling a Command from Another

<?php
protected function execute(InputInterface $input, OutputInterface $output): int
{
    $syncCommand = $this->getApplication()->find('app:sync');

    $syncInput = new ArrayInput(['--force' => true]);
    $returnCode = $syncCommand->run($syncInput, $output);  // runs in-process, no subprocess

    if ($returnCode !== Command::SUCCESS) {
        return $returnCode;
    }

    // ...continue with this command's own work
    return Command::SUCCESS;
}

Interactive Prompts

<?php
use Symfony\Component\Console\Question\ChoiceQuestion;

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $io = new SymfonyStyle($input, $output);

    $env = $io->choice('Which environment?', ['dev', 'staging', 'production'], 'dev');

    if ($env === 'production' && !$io->confirm('This targets PRODUCTION. Continue?', false)) {
        return Command::FAILURE;
    }

    // ...
    return Command::SUCCESS;
}

Machine-Readable Output

<?php
protected function configure(): void
{
    $this->addOption('format', null, InputOption::VALUE_REQUIRED, 'text or json', 'text');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $results = $this->fetchResults();

    if ($input->getOption('format') === 'json') {
        $output->writeln(json_encode($results));  // pipeable into jq, other tools
    } else {
        (new SymfonyStyle($input, $output))->table(['ID', 'Status'], $results);
    }

    return Command::SUCCESS;
}

// php bin/console app:report --format=json | jq '.[0]'

Graceful Shutdown

<?php
use Symfony\Component\Console\Command\SignalableCommandInterface;

class LongRunningCommand extends Command implements SignalableCommandInterface
{
    private bool $shouldStop = false;

    public function getSubscribedSignals(): array
    {
        return [SIGTERM, SIGINT];
    }

    public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
    {
        $this->shouldStop = true;  // finish current unit of work, then exit cleanly
        return false;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        while (!$this->shouldStop && $this->hasMoreWork()) {
            $this->processNext();
        }
        return Command::SUCCESS;
    }
}

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free