phpdot/console

CLI application framework wrapping symfony/console. DI integration, command discovery, output helpers.

Maintainers

Package info

github.com/phpdot/console

pkg:composer/phpdot/console

Statistics

Installs: 1

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-04-03 21:43 UTC

This package is auto-updated.

Last update: 2026-04-03 21:45:37 UTC


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