afrihost/base-command-bundle
Symfony bundle providing an opinionated base class for console commands to reduce boilerplate. Adds built-in initialization for common needs like logging and locking, with global defaults via config and per-command or runtime overrides for flexible behavior.
Installation Add the package via Composer:
composer require afrihost/base-command-bundle
Register the bundle in config/bundles.php:
return [
// ...
Afrihost\BaseCommandBundle\AfrihostBaseCommandBundle::class => ['all' => true],
];
First Command
Extend BaseCommand in your custom command:
use Afrihost\BaseCommandBundle\Command\BaseCommand;
namespace App\Command;
class MyCommand extends BaseCommand
{
protected static $defaultName = 'app:my-command';
protected function configure()
{
$this->setDescription('A minimal command using BaseCommandBundle');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->logInfo('Command executed!'); // Built-in logging
return Command::SUCCESS;
}
}
Key Features Out-of-the-Box
$this->logInfo(), $this->logError(), etc. (inherited from BaseCommand).setLockEnabled(true) in configure() or runtime.config/packages/afrihost_base_command.yaml:
afrihost_base_command:
log_to_console: true
default_log_level: 'info'
Leverage built-in hooks for consistent behavior:
protected function initialize(InputInterface $input, OutputInterface $output)
{
$this->logDebug('Initializing command...');
parent::initialize($input, $output);
}
protected function interact(InputInterface $input, OutputInterface $output)
{
$this->askAndValidate($input, $output, 'confirm', 'Proceed?', function ($answer) {
return $answer === 'yes';
});
}
Override global defaults per-command:
protected function configure()
{
$this->setLockEnabled(true) // Force locking for this command
->setLogLevel('debug'); // Override log level
}
Use setLockEnabled() and setLockTTL() (in seconds) to prevent concurrent executions:
protected function configure()
{
$this->setLockEnabled(true)
->setLockTTL(3600); // Lock for 1 hour
}
Reuse validation helpers:
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->validateRequiredOption($input, 'option-name', 'Option is required!');
// ...
}
Access Laravel services via Symfony’s container:
protected function execute(InputInterface $input, OutputInterface $output)
{
$userRepository = $this->getContainer()->get('App\Repository\UserRepository');
// ...
}
For commands processing multiple items (e.g., records), use processItems():
protected function execute(InputInterface $input, OutputInterface $output)
{
$items = $this->getItemsToProcess();
$this->processItems($items, function ($item) use ($output) {
$this->logInfo(sprintf('Processing %s', $item->id));
// Process logic here
});
}
Locking Conflicts
setLockTTL() to auto-release locks.var/log/ for lock-related errors if commands hang.Logging Duplication
log_to_console: true is set globally, logs may appear twice (console + file). Override per-command:
$this->setLogToConsole(false);
Container Access in Tests
$command = new MyCommand();
$command->setContainer($this->createMock(ContainerInterface::class));
Deprecated Symfony Features
# config/packages/afrihost_base_command.yaml
afrihost_base_command:
default_log_level: 'debug'
php bin/console debug:locks (if available) or inspect var/lock/ manually.Custom Log Handlers
Override getLogger() to inject custom handlers:
protected function getLogger()
{
$logger = parent::getLogger();
$logger->pushHandler(new CustomHandler());
return $logger;
}
Add Command Metadata
Extend BaseCommand to add custom metadata (e.g., tags, categories):
protected function getMetadata()
{
return [
'tags' => ['system', 'admin'],
'category' => 'Maintenance',
];
}
Runtime Configuration Dynamically adjust settings via input arguments:
protected function configure()
{
$this->addArgument('log-level', InputArgument::OPTIONAL, 'Override log level', 'info');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setLogLevel($input->getArgument('log-level'));
}
processItems() with chunking for large datasets to avoid memory issues:
$this->processItems($items, function ($item) { /* ... */ }, 100); // Process 100 items at a time
How can I help you explore Laravel packages today?