sanmai / console
Ready-made extensible console application using Symfony Console component
Fund package maintenance!
sanmai
Requires
- php: >=8.2
- sanmai/later: ^0.1.7
- sanmai/pipeline: ^6.17
- sanmai/version-info: ^0.2
- symfony/console: ^6.0 || ^7.0
Requires (Dev)
- composer/composer: ^2.2
- dg/bypass-finals: ^1.9
- ergebnis/composer-normalize: ^2.8
- friendsofphp/php-cs-fixer: ^3.17
- infection/infection: >=0.29
- league/pipeline: ^0.3 || ^1.0
- php-coveralls/php-coveralls: ^2.4.1
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2
- phpunit/phpunit: >=9.4 <12
- symfony/process: ^7.3
- vimeo/psalm: ^6.12
README
Zero-configuration console executable that auto-discovers your Symfony Console commands.
Installation
composer require sanmai/console
For Auto-Discovery
If you want commands to be discovered automatically:
composer dump-autoload --optimize
Auto-discovery uses Composer's classmap, which requires an optimized autoloader.
For PSR-4 Projects
If you prefer explicit command registration without optimization, configure a custom provider.
Quick Start
- Create a Symfony Console command:
<?php // src/Commands/HelloCommand.php namespace App\Commands; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; #[AsCommand( name: 'hello', description: 'Says hello' )] class HelloCommand extends Command { // Avoid side effects in constructors - commands are instantiated during discovery. protected function execute(InputInterface $input, OutputInterface $output): int { $output->writeln('Hello, World!'); return Command::SUCCESS; } }
- Update the autoloader:
composer dump-autoload --optimize
- Run your command:
vendor/bin/console hello
That's it! No configuration files, no manual command registration.
How It Works
This library provides a ready-made vendor/bin/console
executable that automatically discovers all Symfony Console commands in your project:
-
Auto-discovery (requires an optimized autoloader):
- Scans Composer's classmap for classes extending
Command
with names ending inCommand
- Finds
CommandProviderInterface
implementations with names ending inCommandProvider
- Filters out vendor files
- Skips classes that throw exceptions during instantiation
- Scans Composer's classmap for classes extending
-
Custom provider (optional, no optimization needed):
- Loads the provider specified in
extra.console.provider
- Provider returns command instances via its iterator
- Works alongside auto-discovery
- Loads the provider specified in
The Problem It Solves
I found myself writing the same console bootstrap script over and over. Even with Symfony's command discovery features, you still had to write a lot of boilerplate code.
With sanmai/console
, you get a ready-made vendor/bin/console
executable installed via Composer. No files to create, no permissions to set - just install the package and vendor/bin/console
is ready to use. It even works in legacy projects that never had Symfony Console commands before.
Configuration
Bootstrap Scripts
Configure a custom bootstrap script in your composer.json
:
{ "extra": { "console": { "bootstrap": "app/bootstrap.php" } } }
Or use Composer commands:
# Configure bootstrap script composer config extra.console.bootstrap app/bootstrap.php # Enable optimized autoloader (required for command discovery) composer config optimize-autoloader true
The bootstrap script runs after Composer's autoloader is initialized. Including vendor/autoload.php
again is safe - the library handles this gracefully.
Example bootstrap script:
<?php // bootstrap.php // Set up error handlers, load environment variables, configure services define('APP_ENV', $_ENV['APP_ENV'] ?? 'production'); // Composer autoloader is already loaded // Safe to include vendor/autoload.php if needed
Custom Provider Configuration
In addition to auto-discovery, you can specify a custom command provider:
{ "extra": { "console": { "provider": "App\\Console\\CommandProvider" } } }
Or using Composer command:
composer config extra.console.provider 'App\Console\CommandProvider'
The custom provider:
- Works alongside auto-discovery (doesn't replace it)
- Doesn't need the
CommandProvider
suffix - Must implement
CommandProviderInterface
- Must have a no-argument constructor
An optimized autoloader is not required to use the custom provider.
Commands with Dependencies
For commands that require constructor dependencies, implement the CommandProviderInterface
:
<?php // src/DatabaseCommandProvider.php namespace App; use ConsoleApp\CommandProviderInterface; use IteratorAggregate; use Symfony\Component\Console\Command\Command; class DatabaseCommandProvider implements CommandProviderInterface, IteratorAggregate { // Provider must have a no-required-argument constructor public function __construct(int $optional = 0) {} public function getIterator(): \Traversable { // Build your services/dependencies $database = new DatabaseConnection(); $cache = new CacheService(); // You can yield commands one by one... yield new DatabaseMigrationCommand($database); yield new CacheClearCommand($cache); // ...or return a variety of iterators, // ...or implement a full iterator } }
CommandProviderInterface
implementations must have no required arguments in their constructor as they are instantiated automatically.
Troubleshooting
Commands not showing up?
- Run
composer dump-autoload --optimize
(add--dev
if your commands are in autoload-dev) - Verify your command class names end with
Command
(e.g.,HelloCommand
,MigrateCommand
) - Check that commands extend
Symfony\Component\Console\Command\Command
- Commands in
vendor/
are ignored by default - Command providers must have class names ending with
CommandProvider
Testing
make -j -k