phpdot / console
CLI application framework wrapping symfony/console. DI integration, command discovery, output helpers.
v1.0.0
2026-04-03 21:43 UTC
Requires
- php: >=8.3
- psr/container: ^2.0
- symfony/console: ^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.94
- phpstan/phpstan: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^11.0
README
CLI application framework wrapping symfony/console. DI integration, command discovery, output helpers. Standalone.
Install
composer require phpdot/console
Architecture
graph TD
APP[Application] -->|wraps| SA[Symfony Application]
APP -->|discover| CD[CommandDiscovery]
APP -->|read/write| CC[CommandCache]
APP -->|wires| CCL[ContainerCommandLoader]
CD -->|PhpToken + Reflection| FILES[".php files + #[AsCommand]"]
CD -->|produces| MAP["Command Map<br/>name → class"]
CC -->|caches| MAP
CCL -->|implements| CLI[CommandLoaderInterface]
CCL -->|resolves via| PSR[PSR-11 Container]
CCL -->|uses| MAP
SA -->|runs| CMD[Command instances]
CMD -->|extends| BASE[PHPdot Command]
BASE -->|extends| SC[Symfony Command]
style APP fill:#2d3748,color:#fff
style BASE fill:#2d3748,color:#fff
style CCL fill:#4a5568,color:#fff
style CD fill:#4a5568,color:#fff
style CC fill:#4a5568,color:#fff
style SA fill:#718096,color:#fff
style PSR fill:#718096,color:#fff
style SC fill:#718096,color:#fff
style MAP fill:#718096,color:#fff
style FILES fill:#718096,color:#fff
style CLI fill:#718096,color:#fff
style CMD fill:#718096,color:#fff
Loading
Usage
Standalone
use PHPdot\Console\Application; $app = new Application(name: 'MyApp', version: '1.0.0'); $app->add(new GreetCommand()); $app->run();
With discovery
use PHPdot\Console\Application; use PHPdot\Console\Cache\CommandCache; $app = new Application( name: 'MyApp', version: '1.0.0', cache: new CommandCache(__DIR__ . '/var/cache/commands.php'), ); $app->discover([__DIR__ . '/src/Command']); $app->run();
With DI container
use PHPdot\Console\Application; use PHPdot\Console\Cache\CommandCache; $container = /* any PSR-11 container */; $app = new Application( name: 'MyApp', version: '1.0.0', container: $container, cache: new CommandCache(__DIR__ . '/var/cache/commands.php'), ); $app->discover([__DIR__ . '/src/Command']); $app->run();
Defining commands
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use PHPdot\Console\Command; #[AsCommand(name: 'users:import', description: 'Import users from CSV')] final class ImportUsersCommand extends Command { public function __construct( private readonly UserRepository $users, ) { parent::__construct(); } protected function configure(): void { $this->addArgument('file', InputArgument::REQUIRED, 'CSV file path'); } protected function execute(InputInterface $input, OutputInterface $output): int { $file = $input->getArgument('file'); $rows = $this->parser->parse($file); $this->info($output, "Importing from {$file}..."); $this->withProgress($output, $rows, function (array $row) { $this->users->create($row); }); $this->success($output, 'Import complete.'); return self::SUCCESS; } }
Output helpers
$this->info($output, 'Processing...'); $this->error($output, 'Something failed'); $this->success($output, 'Done'); $this->warning($output, 'Careful'); $this->comment($output, 'Note');
Table
$this->table($output, [ ['name' => 'Alice', 'email' => 'alice@example.com'], ['name' => 'Bob', 'email' => 'bob@example.com'], ]); // Headers auto-detected from first row keys
Progress
$this->withProgress($output, $items, function (mixed $item) { // process each item });
Interactive input
$name = $this->ask($input, $output, 'What is your name?'); $confirmed = $this->confirm($input, $output, 'Continue?', true); $color = $this->choice($input, $output, 'Pick a color', ['red', 'blue', 'green']); $password = $this->secret($input, $output, 'Enter password');
Programmatic execution
$exitCode = $app->call('cache:clear'); $exitCode = $app->call('migrate', ['--force' => true]);
Discovery
CommandDiscovery scans directories for classes with #[AsCommand] that extend Symfony\Component\Console\Command\Command. Uses PhpToken::tokenize() for class extraction and ReflectionClass for attribute reading.
Skips: interfaces, traits, enums, abstract classes, anonymous classes, classes without #[AsCommand].
Cache format
// var/cache/commands.php return [ 'cache:clear' => 'App\\Command\\CacheClearCommand', 'users:import' => 'App\\Command\\ImportUsersCommand', ];
Requirements
- PHP >= 8.3
- symfony/console ^7.0
- psr/container ^2.0
License
MIT