akmaks/command-chaining-bundle
Install the Package
composer require akmaks/command-chaining-bundle
Configure Chains
Create config/packages/akmaks_command_chaining.yaml with a basic chain definition:
parameters:
chains:
App\Console\MasterCommand:
- App\Console\FirstChainCommand
- App\Console\SecondChainCommand
Implement CommandChainingInterface
For master commands (entry points):
use Akmaks\CommandChainingBundle\Command\CommandChainingInterface;
class MasterCommand extends Command implements CommandChainingInterface {
public function isMasterCommand(): bool { return true; }
}
For chain commands (sub-commands):
class FirstChainCommand extends Command implements CommandChainingInterface {
public function isMasterCommand(): bool { return false; }
}
Run the Master Command
php artisan your:master-command
Chain commands for sequential data processing (e.g., generate-reports, send-emails, cleanup-temp-files). Define them in the config and trigger via a single master command.
Define Chains
Group related commands in config/packages/akmaks_command_chaining.yaml:
parameters:
chains:
App\Console\DeployCommand:
- App\Console\BackupCommand
- App\Console\MigrateCommand
- App\Console\RestartServicesCommand
Master Command Logic Use the master command to handle pre/post-chain logic (e.g., logging, validation):
protected function execute(InputInterface $input, OutputInterface $output): int {
$output->writeln('Starting deployment...');
// Chain execution handled automatically
return Command::SUCCESS;
}
Chain Command Dependencies
Pass shared data between commands via Symfony’s Application or Input:
// In MasterCommand:
$this->getApplication()->find('backup')->run(new ArrayInput(['--path' => '/backup']), $output);
Conditional Chaining Dynamically modify chains based on input:
parameters:
chains:
App\Console\DynamicCommand:
{% if app.environment == 'prod' %}
- App\Console\ProdBackupCommand
{% else %}
- App\Console\DevBackupCommand
{% endif %}
post-command hooks to validate chain results:
$this->getApplication()->get('console')->addListener(ConsoleEvents::COMMAND, function ($event) {
if ($event->getCommandName() === 'your:master-command') {
// Post-chain logic
}
});
CommandChainingInterface in unit tests to verify chain behavior:
$this->expectsOutput('Chain executed successfully');
$this->artisan('your:master-command')->assertExitCode(0);
Circular Dependencies
Avoid defining chains where A depends on B, and B depends on A. The bundle does not detect cycles.
Command Registration Order
Ensure all chain commands are registered in the Symfony kernel before the master command. Use boot() in a bundle or service provider if needed.
Input/Output Isolation
Chain commands share the same Input/Output by default. Use --quiet or custom streams to isolate output:
php artisan your:master-command --quiet
Configuration Overrides The YAML config is loaded once at bootstrap. For dynamic chains, use a service to generate the config array programmatically:
$container->setParameter('chains', $dynamicChainConfig);
php artisan your:master-command --verbose
// In a service provider:
$this->container->get('logger')->info('Chain started', ['command' => $masterCommand]);
Custom Chain Resolvers
Override CommandChainingResolver to implement dynamic chain resolution (e.g., database-backed chains):
class DatabaseChainResolver implements CommandChainingResolverInterface {
public function resolve(string $masterCommand): array {
return $this->fetchFromDatabase($masterCommand);
}
}
Pre/Post-Execution Hooks
Extend the bundle’s CommandChainExecutor to add hooks:
$executor->addPreExecuteHook(function ($command) {
// Pre-execution logic
});
Parallel Execution
Use Symfony’s Process component to run chain commands in parallel (not natively supported):
foreach ($chainCommands as $command) {
$process = new Process(['php', 'bin/console', $command]);
$process->start();
}
try-catch in master commands to halt chains on failure:
try {
$this->getApplication()->find('backup')->run($input, $output);
} catch (Exception $e) {
$output->writeln('<error>Backup failed. Aborting chain.</error>');
return Command::FAILURE;
}
--no-debug to skip verbose output during execution.How can I help you explore Laravel packages today?