mini\CLI namespace

CLI - Command Line Interface

For simple scripts, use $_SERVER['argv'] directly. Use args() when you need structured option parsing or subcommands.

Quick Start

#!/usr/bin/env php
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use function mini\args;

args(args()->withFlag('v', 'verbose')->withRequiredValue('o', 'output'));

if (args()->getUnparsedArgs()) {
    fwrite(STDERR, "Unexpected: " . implode(', ', args()->getUnparsedArgs()) . "\n");
    exit(1);
}

if (args()->getFlag('verbose')) {
    echo "Verbose mode\n";
}
$output = args()->getOption('output');

Declaring Options

args(args()
    ->withFlag('v', 'verbose')                         // -v or --verbose (counting)
    ->withFlag('h', null)                              // -h only
    ->withFlag(null, 'dry-run')                        // --dry-run only
    ->withRequiredValue('c', 'config')                 // -c file or --config=file
    ->withOptionalValue('l', 'log')                    // --log or --log=file
    ->withOptionalValue('o', 'out', '/dev/stdout')    // default when present without value
    ->withSubcommand('run', 'build', 'test')           // declared subcommands
);

Querying Values

Method Returns Description
getFlag('name') int Count of times flag appeared (0 if absent)
getOption('name') string|false|array|null Option value (see below)
hasOption('name') bool True if option was present
getUnparsedArgs() string[] Arguments not matched by declared options/subcommands
getCommand() string|null Current command name
nextCommand() ArgManager|null ArgManager for matched subcommand
getRemainingArgs() string[] Remaining argv for delegation (strips leading --)

getOption() return values:

CLI input getOption() result
(not present) null
--log false (or default if specified)
--log=file "file"
--log=a --log=b ["a", "b"]

Flags can be repeated for counting (short and long forms are combined):

myapp -vvv              # getFlag('verbose') returns 3
myapp -v -v --verbose   # same result

Options with values accept multiple formats:

myapp -cfile.txt        # attached
myapp -c file.txt       # separate
myapp --config=file.txt # long with =
myapp --config file.txt # long separate

Subcommands

#!/usr/bin/env php
<?php
// bin/myapp - Usage: myapp [-v] <command> [options]
use function mini\args;

args(args()
    ->withFlag('v', 'verbose')
    ->withSubcommand('run', 'build')
);

if (args()->getUnparsedArgs()) {
    die("Unknown: " . implode(', ', args()->getUnparsedArgs()));
}

if ($sub = args()->nextCommand()) {
    args($sub);
    match (args()->getCommand()) {
        'run' => require __DIR__ . '/commands/run.php',
        'build' => require __DIR__ . '/commands/build.php',
    };
}

Each subcommand file uses the same pattern:

<?php
// commands/run.php
use function mini\args;

args(args()->withFlag(null, 'fast'));

if (args()->getUnparsedArgs()) {
    die("Unexpected: " . implode(', ', args()->getUnparsedArgs()));
}

echo args()->getFlag('fast') ? "Fast mode\n" : "Normal mode\n";

Double-Dash (--)

Everything after -- goes to getUnparsedArgs():

myapp -v -- --not-an-option file.txt
args()->getUnparsedArgs();  // ['--not-an-option', 'file.txt']

Delegating to External Commands

args(args()->withSubcommand('proxy'));

if ($sub = args()->nextCommand()) {
    args($sub);
    $remaining = args()->getRemainingArgs();  // strips leading -- automatically
    passthru('external-tool ' . implode(' ', array_map('escapeshellarg', $remaining)));
}

Unit Testing

For tests, create ArgManager directly instead of using the global args():

$_SERVER['argv'] = ['myapp', '-v', '--config=test.cfg'];
$args = (new ArgManager())->withFlag('v', 'verbose')->withRequiredValue('c', 'config');
$this->assertSame(1, $args->getFlag('verbose'));

Classes (3)

ArgManager

Command-line argument parser with subcommand support

ReadlineManager

Wrapper around PHP's readline callback interface with proper signal handling

TTY

Terminal output helper