Search by

anvilm / iva

AnvilM

CLI framework for PHP 8.4+ with a real command tree, strongly typed input DTOs, GNU/POSIX-style option parsing, and a section/progress-bar/spinner output model.

Package info

github.com/AnvilM/iva

pkg:composer/anvilm/iva

Statistics

Installs: 5

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.1 2026-09-18 23:04 UTC

This package is auto-updated.

Last update: 2026-09-18 23:08:55 UTC


README


logo
Iva

A modern, strongly-typed CLI framework for PHP 8.4+

php-version-shield license-shield

status-shield ai-written-shield

Important

This entire library — every line of source code, every docblock, and this README — was written by an AI. No human has hand-written the implementation. Review the code carefully before relying on it in production, and please open an issue if you spot a bug or a design flaw.

Overview

Iva is a command-line application framework for PHP. It gives you a real, nestable command tree (think git remote add), strongly-typed input DTOs instead of stringly-typed $input->getOption('name') lookups, a GNU/POSIX-compliant option parser, and a rich output model with sections, progress bars, spinners, tables and trees — all without reflection, attributes, or hidden global state.

Two instances of Application can coexist in the same process, commands are built and registered explicitly, and every value you read back in execute() is read through the very same typed Argument/Option object you declared in configure() — so a typo or a type mismatch is a static-analysis error, not a runtime surprise.

Features

  • Real command tree — unlimited nesting (app db migrate up), grouping nodes that just list their children (like git remote), aliases, hidden commands, and automatic "did you mean...?" suggestions for typos.
  • Statically-typed inputArgument::string(), Option::int(), Option::flag(), etc. return a typed handle that you keep as a property and read back with $input->argument($this->name) / $input->option($this->loud) — no magic strings, no mixed.
  • GNU/POSIX-style parsing — long options (--verbose), short flags and their bundling (-vvv), --opt=value / --opt value, negatable flags (--no-foo), variadic arguments, the -- end-of-options marker, and optional option-name abbreviation.
  • Rich output model — colored/styled text (<error>, <comment>, custom styles), verbosity levels (-q, -v, -vv, -vvv), redrawable sections, progress bars (determinate & indeterminate), spinners, tables, and trees, with automatic ANSI/color-support detection.
  • Command lifecycle hooksPreRun / PostRun on a command, and PersistentPreRun / PersistentPostRun on any ancestor group, run like a finally block even when the command throws.
  • Auto-generated help — usage lines, argument/option tables and examples are generated from the same definitions used to parse input, for the whole app, a group, or a single command (--help / -h).
  • First-class testing utilitiesCommandTester and ApplicationTester run a command through the real lexer → parser → binder pipeline against a BufferedOutput, with no real process or exec involved.
  • Persistent (inherited) options — declare an option once on a parent group and every subcommand can read it back through the same typed handle.

Requirements

Requirement Version
PHP ^8.4

Iva depends on the php-standard-library packages (foundation, type, str, io, ansi, async, os, date-time) — installed automatically via Composer.

Installation

composer require anvilm/iva

Quick Start

<?php

declare(strict_types=1);

require __DIR__ . '/vendor/autoload.php';

use src\Application;use src\Command\Command;use src\Input\Argument;use src\Input\Input;use src\Input\Option;use src\Output\Output;

final class GreetCommand extends Command
{
    private Argument $name;
    private Option $loud;

    protected function configure(): void
    {
        $this->setName('greet');
        $this->setDescription('Greets someone by name');

        $this->name = $this->addArgument(Argument::string('name', 'Who to greet'));
        $this->loud = $this->addOption(Option::flag('loud', shortcut: 'l', description: 'Shout the greeting'));

        $this->addExample('greet Ada --loud', 'Greets Ada, loudly');
    }

    public function execute(Input $input, Output $output): int
    {
        $name = $input->argument($this->name);
        $greeting = sprintf('Hello, %s!', $name);

        $output->writeln($input->option($this->loud) ? strtoupper($greeting) : $greeting);

        return 0;
    }
}

$app = new Application('greet-app', '1.0.0');
$app->command(new GreetCommand());

exit($app->run($argv));
$ php greet.php Ada --loud
HELLO, ADA!

Core Concepts

Commands

Every command extends Command, declares itself in configure(), and receives an already-parsed, already-typed Input in execute():

final class DeployCommand extends Command
{
    protected function configure(): void
    {
        $this->setName('deploy');
        $this->setDescription('Deploys the application');
        $this->setAliases(['d']);
    }

    public function execute(Input $input, Output $output): int
    {
        // ...
        return ExitCode::Ok->value;
    }
}

Nested commands & groups

Build a subcommand tree with CommandBuilder::subcommand(). A CommandGroup has no behaviour of its own — running it just lists its children, like git remote:

final class DbGroup extends CommandGroup
{
    protected function configure(): void
    {
        $this->setName('db');
        $this->setDescription('Database commands');
    }
}

$app->command(new DbGroup())
    ->subcommand(new MigrateCommand())
    ->subcommand(new SeedCommand());
$ php app.php db migrate --help

Arguments & Options

Arguments and options are built through named constructors and kept as typed handles:

protected function configure(): void
{
    $this->setName('copy');

    $this->source = $this->addArgument(Argument::string('source'));
    $this->targets = $this->addArgument(Argument::string('targets', variadic: true));

    $this->force = $this->addOption(Option::flag('force', shortcut: 'f', negatable: true));
    $this->retries = $this->addOption(Option::int('retries', default: 3));
    $this->tags = $this->addOption(Option::strings('tag'));
}

Persistent options declared on a parent group are inherited by every subcommand and can be read back through the same shared handle — see the docblock on Command::addOption() for the pattern.

Output

public function execute(Input $input, Output $output): int
{
    $output->writeln('<comment>Starting...</comment>');

    $bar = $output->progressBar(100);
    $bar->start();
    for ($i = 0; $i < 100; $i++) {
        $bar->advance();
    }
    $bar->finish();

    $output->table()
        ->headers(['Name', 'Status'])
        ->rows([['web', 'running'], ['worker', 'stopped']])
        ->render();

    $spinner = $output->spinner('Connecting...');
    $spinner->start();
    // ...
    $spinner->finish();

    return ExitCode::Ok->value;
}

Verbosity is controlled by the built-in -q, -v, -vv, -vvv global flags, and $output->isVerbose() / $output->isDebug() let a command adapt its own logging.

Lifecycle hooks

final class DbGroup extends CommandGroup implements PersistentPreRun, PersistentPostRun
{
    public function persistentPreRun(Output $output): void { /* open a connection */ }
    public function persistentPostRun(Output $output, int $exitCode): void { /* always close it */ }
}

PersistentPostRun runs like a finally block: it fires even if the subcommand throws.

Testing

use src\Testing\CommandTester;

$tester = new CommandTester(new GreetCommand());
$tester->execute(['Ada', '--loud']);

$tester->assertSuccessful();
$tester->assertOutputContains('HELLO, ADA!');

ApplicationTester does the same at the whole-application level, resolving the command path exactly as run() would.

Supported input styles

Style Example
Long option --verbose
Long option with value --timeout=30 / --timeout 30
Short flag -v
Bundled short flags -vvv
Negatable flag --no-force
Variadic argument cmd file1 file2 file3
End-of-options marker cmd -- --not-an-option

Project Structure

src/
├── Application.php        # Entry point: parses argv, resolves & runs a command
├── ExitCode.php            # Standard sysexits-style exit codes
├── Command/                # Command tree: Command, CommandGroup, CommandBuilder, lifecycle hooks
├── Input/                  # Lexer, Parser, Definition, Argument, Option, typed InputType system
├── Output/                 # Output, ConsoleOutput, sections, ProgressBar, Spinner, Table, Tree, formatter
├── Help/                   # Auto-generated help & usage lines
└── Testing/                # CommandTester, ApplicationTester

License

The project is distributed under the MIT License. For details, refer to the LICENSE file.