fidry/console
Lightweight, robust wrapper around symfony/console. Uses a single IO object (SymfonyStyle-like, with access to Input/Output) plus typed, validated args/options. Prefer explicit interfaces over inheritance; works with Symfony or standalone CLI apps.
Installation:
composer require theofidry/console
Ensure Fidry\Console\FidryConsoleBundle is enabled in config/bundles.php.
First Command:
Create a class implementing Fidry\Console\Command\Command:
// src/Command/MyFirstCommand.php
namespace App\Command;
use Fidry\Console\{Command\Command, Command\Configuration, ExitCode, IO};
final class MyFirstCommand implements Command {
public function getConfiguration(): Configuration {
return new Configuration('app:hello', 'Says hello');
}
public function execute(IO $io): int {
$io->writeln('Hello, World!');
return ExitCode::SUCCESS;
}
}
Run It:
php bin/console app:hello
doc/command.md for core concepts.Fidry\Console\IO for input/output handling—it’s the core abstraction.Define Command:
Implement Command interface with getConfiguration() and execute().
public function getConfiguration(): Configuration {
return new Configuration(
'app:my-command',
'Description',
'Help text',
[/* arguments */],
[/* options */]
);
}
Typed Input Handling:
Use IO methods for type-safe input:
$username = $io->getTypedArgument('username')->asStringNonEmpty();
$age = $io->getTypedOption('age')->asNullablePositiveInteger();
Output Patterns:
$io->writeln('Message')$section = $io->section(); $section->writeln('...')Symfony\Component\Console\Helper\ProgressBar via $io->getOutput().Service Integration: Inject services via constructor (Laravel’s DI works seamlessly):
public function __construct(private MyService $service) {}
Lifecycle Hooks:
Implement InitializableCommand for pre-execution logic:
public function initialize(IO $io): void {
$this->validateEnvironment();
}
Laravel-Specific:
Artisan::command() for Laravel-specific commands:
Artisan::command('app:custom', function () {
// Command logic
});
Console/Kernel.php for grouping commands under php artisan.Testing:
Use Fidry\Console\Test\CommandTestCase for mocking IO:
public function testExecute(): void {
$io = $this->createMock(IO::class);
$command = new MyCommand();
$this->assertSame(ExitCode::SUCCESS, $command->execute($io));
}
Lazy Loading:
Implement LazyCommand for deferred command registration (see doc/lazy-command.md).
Input Validation:
asPositiveInteger()) throw exceptions on failure. Handle them:
try {
$age = $io->getTypedArgument('age')->asPositiveInteger();
} catch (InvalidArgumentException $e) {
$io->error('Age must be a positive integer.');
return ExitCode::FAILURE;
}
Output Sections:
ConsoleOutputInterface. Check before use:
if (!$io->getOutput() instanceof ConsoleOutputInterface) {
throw new LogicException('Sections require ConsoleOutputInterface.');
}
Service Container:
fidry.console_command for auto-registration. Laravel’s autowiring handles this if using Artisan::command().Missing Features:
IO Mocking:
For tests, mock IO and verify interactions:
$io = $this->createMock(IO::class);
$io->expects($this->once())
->method('writeln')
->with('Hello!');
Exit Codes:
Always return ExitCode::SUCCESS/ExitCode::FAILURE for consistency. Custom codes require const definitions.
Configuration:
Configuration::fromArray() for dynamic command setup:
$config = Configuration::fromArray([
'name' => 'app:dynamic',
'description' => 'Dynamic command',
]);
Custom IO:
Extend Fidry\Console\IO to add domain-specific methods:
class CustomIO extends IO {
public function logUserAction(string $action): void {
$this->writeln(sprintf('<info>[USER] %s</info>', $action));
}
}
Command Decorators: Wrap commands to add cross-cutting logic (e.g., logging):
class LoggingCommandDecorator implements Command {
public function __construct(private Command $command) {}
public function execute(IO $io): int {
$io->writeln('<comment>Starting command...</comment>');
return $this->command->execute($io);
}
}
Event Listeners:
Use Symfony’s event system (via EventDispatcher) to hook into command lifecycle:
$dispatcher->addListener(CommandEvents::EXECUTE, function (CommandEvent $event) {
$event->getCommand()->initialize($event->getInput());
});
Artisan Integration:
Artisan::command() bypass fidry.console_command tagging. Ensure manual registration if needed.Artisan::call() to execute commands programmatically:
$exitCode = Artisan::call('app:my-command', ['username' => 'john']);
Configuration:
Configuration via config/console.php:
'commands' => [
'app:my-command' => [
'description' => 'Custom description',
],
],
How can I help you explore Laravel packages today?