Common Use Cases
Deployment Scripts
Run a sequence of commands to deploy your application:
Batch::create($application, $output)
->add('cache:clear')
->addShell('composer install --no-dev --classmap-authoritative')
->addShell('npm install')
->addShell('npm run build')
->add('database:migrations:migrate --no-interaction')
->add('assets:install')
->stopOnFailure()
->run();
Database Migration Chaining
Run multiple migration-related commands in sequence:
Batch::create($application, $output)
->add('doctrine:database:create --if-not-exists')
->add('doctrine:migrations:migrate --no-interaction')
->add('doctrine:fixtures:load --no-interaction')
->add('cache:clear')
->run();
Log Aggregation
Send command output to multiple log files based on environment:
$outputs = [
$consoleOutput,
new NativeFileOutput('/var/log/commands.log', NativeFileOutput::MODE_APPEND),
];
if ($debug) {
$outputs[] = new NativeFileOutput('/var/log/debug.log', NativeFileOutput::MODE_APPEND);
}
$output = new MultiplexedOutput($outputs);
// Now run your batch with this output
Batch::create($application, $output)
->add('my:command')
->run();
CI/CD Command Sequencing
In your CI pipeline, run a predictable sequence of commands:
// In your CI command
Batch::create($application, $output)
->add('tests:phpunit')
->add('tests:phpstan')
->add('tests:psalm')
->add('code:style:fix')
->silenceErrors(false) // Fail on first error
->run();
return $batch->allCommandsSucceeded() ? Command::SUCCESS : Command::FAILURE;
Multi-Destination Logging
Send output to console, local file, and cloud storage simultaneously:
use League\Flysystem\AwsS3V3\AwsS3V3Adapter;
use League\Flysystem\Filesystem;
// Set up S3 filesystem
$adapter = new AwsS3V3Adapter($s3Client, $bucketName);
$s3Filesystem = new Filesystem($adapter);
$output = new MultiplexedOutput([
$consoleOutput,
new NativeFileOutput('local.log'),
new FlysystemFileOutput($s3Filesystem, 's3-logs/command-'.date('Y-m-d-His').'.log'),
]);
Recipes
Dry Run Mode
Show what would be executed without actually running it:
class DryRunCommand extends Command {
public function execute(InputInterface $input, OutputInterface $output) {
$batch = Batch::create($this->getApplication(), $output);
// Build your batch
$batch->add('cache:clear');
$batch->add('migrations:migrate');
if ($input->getOption('dry-run')) {
$output->writeln('<comment>Dry run mode. The following would be executed:</comment>');
$output->writeln('');
$output->writeln((string) $batch);
return Command::SUCCESS;
}
$batch->run();
return Command::SUCCESS;
}
}
Retry with Backoff
Implement retry logic for flaky commands:
use EFrane\ConsoleAdditions\Command\Batch;
class RetryBatchCommand extends Command {
public function execute(InputInterface $input, OutputInterface $output) {
$maxRetries = 3;
$retryDelay = 5; // seconds
for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
try {
$batch = Batch::create($this->getApplication(), $output)
->add('flaky:command')
->silenceErrors(false);
$batch->run();
return Command::SUCCESS;
} catch (\Exception $e) {
if ($attempt < $maxRetries) {
$output->writeln(sprintf(
'<warning>Attempt %d/%d failed, retrying in %d seconds...</warning>',
$attempt, $maxRetries, $retryDelay
));
sleep($retryDelay);
continue;
}
throw $e;
}
}
}
}
Conditional Command Execution
Run commands conditionally based on previous results:
$batch = Batch::create($application, $output);
$batch->add('check:precondition');
// Store result for later
$batch->run();
$returnCodes = $batch->getReturnCodes();
if ($returnCodes[0] === 0) {
// Precondition passed, run main commands
$batch = Batch::create($application, $output)
->add('main:command')
->run();
}
Parallel-like Execution with Groups
While true parallel execution requires async, you can group related commands:
// Database operations
$batch->addMessage('=== Database Operations ===');
$batch->add('database:create');
$batch->add('database:migrate');
$batch->add('database:fixtures');
// Asset operations
$batch->addMessage('=== Asset Operations ===');
$batch->add('assets:install');
$batch->addShell('npm run build');
// Cache operations
$batch->addMessage('=== Cache Operations ===');
$batch->add('cache:clear');
$batch->add('cache:warmup');
Interactive Batch with Confirmation
Confirm before executing a batch:
use Symfony\Component\Console\Style\SymfonyStyle;
class ConfirmedBatchCommand extends Command {
public function execute(InputInterface $input, OutputInterface $output) {
$io = new SymfonyStyle($input, $output);
$batch = Batch::create($this->getApplication(), $output)
->add('cache:clear')
->add('database:migrate');
$io->text('The following commands will be executed:');
$io->listing(explode(' && ', (string) $batch));
if (!$io->confirm('Continue?')) {
return Command::SUCCESS;
}
$batch->run();
return Command::SUCCESS;
}
}
Progress Tracking
Track progress through a long-running batch:
$commands = [
'step1:prepare',
'step2:process',
'step3:finalize',
];
$batch = Batch::create($application, $output);
$total = count($commands);
foreach ($commands as $i => $command) {
$batch->addMessage(sprintf('Step %d/%d: %s', $i + 1, $total, $command));
$batch->add($command);
}
$batch->run();
Cleanup on Failure
Register a cleanup action that runs when the batch fails:
$batch = Batch::create($application, $output);
try {
$batch
->add('deploy:start')
->add('deploy:execute')
->stopOnFailure()
->run();
} catch (\Exception $e) {
$output->writeln('<error>Deployment failed, running cleanup...</error>');
// Run cleanup batch
$cleanup = Batch::create($application, $output);
$cleanup
->add('deploy:rollback')
->addShell('git checkout main')
->run();
throw $e;
}
Tips & Best Practices
- Use
stopOnFailure()for critical deployments where you don’t want to continue after a failure - Use
silenceErrors()for non-critical batches where you want to see all results - Add MessageAction between commands to make output more readable
- Log the batch configuration using
(string) $batchfor debugging - Check return codes with
allCommandsSucceeded()orgetReturnCodes()for conditional logic