Nette Command-Line
A lightweight library for building command-line applications in PHP. It parses switches, options, and positional arguments, and helps you produce colorful terminal output with ANSI support.
Installation:
composer require nette/command-line
It requires PHP version 8.2 and supports PHP up to 8.5.
Parsing Command-Line Arguments
Every CLI script needs to handle arguments like --verbose, -o output.txt, or plain file names. The Nette\CommandLine\Parser class offers the
fastest way to get started: just write your help text and let the parser extract option definitions from it:
use Nette\CommandLine\Parser;
$parser = new Parser;
$parser->addFromHelp('
-h, --help Show this help
-v, --verbose Enable verbose mode
-o, --output <file> Output file
-f, --format [type] Output format (default: json)
-I, --include <path>... Include paths
--dry-run Show what would be done
');
$args = $parser->parse();
That's it. The parser understands that --verbose is a switch, --output requires a value, and
--format has an optional value with json as its fallback. Your help text stays in sync with the actual
option definitions.
The parse() method returns an associative array. Keys match option names exactly as defined, including the
dashes:
[
'--help' => true, // or null if not used
'--verbose' => null,
'--output' => 'file.txt', // or null if not used
'--format' => 'json', // fallback from (default: json)
'--include' => ['src', 'lib'],
'--dry-run' => null,
]
By default, parse() reads from $_SERVER['argv']. You can pass a custom array, which is handy for
testing:
$args = $parser->parse(['--verbose', '-o', 'out.txt']);
Help Text Syntax
The parser extracts option definitions from formatted help text according to these rules:
--verbose |
Switch (no value) |
-v, --verbose |
Switch with short alias |
--output <file> |
Option with required value |
--format [type] |
Option with optional value |
(default: json) |
Sets the fallback value |
<path>... |
Repeatable option |
Each line defines one option. Option names must be separated from their descriptions by at least two spaces.
Additional Configuration
Some settings can't be expressed in the help text. Pass an array as the second parameter, keyed by option name:
$parser->addFromHelp('
-c, --config <file> Configuration file
-I, --include <path> Include path
-n, --count <num> Number of iterations
', [
'--config' => [
Parser::RealPath => true,
],
'--include' => [
Parser::Repeatable => true,
],
'--count' => [
Parser::Normalizer => fn($v) => (int) $v,
],
]);
Available keys:
Parser::Repeatable |
Collect multiple values into an array |
Parser::RealPath |
Validate that the file exists and resolve it to an absolute path |
Parser::Normalizer |
Transform function fn($value) => ... |
Parser::Default |
Fallback value (same as (default: x) in the help text) |
Parser::Enum |
Array of allowed values |
Fluent API
When you need more control over the option definitions, use the fluent API with the addSwitch(),
addOption(), and addArgument() methods. This approach gives you access to all features, including
normalizers, enums, and precise control over each parameter:
use Nette\CommandLine\Parser;
$parser = new Parser;
$parser
->addSwitch('--verbose', '-v')
->addOption('--output', '-o')
->addArgument('file');
$args = $parser->parse();
As with addFromHelp(), you can pass a custom array to parse() for testing:
$args = $parser->parse(['--verbose', '-o', 'out.txt', 'input.txt']);
Switches, Options, and Arguments
There are three types of command-line inputs:
Switches are flags without values, like --verbose or -v. They parse as true when
present, null when absent:
$parser->addSwitch('--verbose', '-v');
// --verbose → true
// -v → true
// (not used) → null
Options accept values, like --output file.txt. The value can be separated by a space or by
=:
$parser->addOption('--output', '-o');
// --output file.txt → 'file.txt'
// --output=file.txt → 'file.txt'
// -o file.txt → 'file.txt'
// --output → throws an exception (value required)
// (not used) → null
Note that the option itself is always optional – not using it returns null. However, when it is used, the value
is required by default. Set optionalValue: true to allow the option without a value (it then parses as
true):
$parser->addOption('--format', '-f', optionalValue: true);
// --format json → 'json'
// --format → true
// (not used) → null
When the same option is used multiple times without repeatable: true, the last value wins:
$parser->addOption('--output', '-o');
// -o first.txt -o second.txt → 'second.txt'
Arguments are positional values without dashes. By default they are required. Set optional: true to make
them optional:
$parser->addArgument('input');
// script.php file.txt → 'file.txt'
// (not used) → throws an exception
$parser->addArgument('output', optional: true);
// (not used) → null
$parser->addArgument('output', optional: true, fallback: 'out.txt');
// (not used) → 'out.txt'
Use fallback to specify the value used when an optional option or argument is not provided. For options with
optionalValue: true, note that using the option without a value still parses as true, while the fallback
is used only when the option is not present at all:
$parser->addOption('--format', '-f', optionalValue: true, fallback: 'json');
// --format xml → 'xml'
// --format → true (option used without a value)
// (not used) → 'json' (fallback)
Arguments can appear anywhere on the command line – they don't have to come after the options:
// all of these are equivalent:
// script.php --verbose input.txt
// script.php input.txt --verbose
Restricting Values with Enum
Limit the accepted values to a specific set:
$parser->addOption('--format', '-f', enum: ['json', 'xml', 'csv']);
// --format yaml → throws "Value of option --format must be json, or xml, or csv."
Repeatable Options
Set repeatable: true to collect multiple values into an array:
$parser->addOption('--include', '-I', repeatable: true);
// -I src -I lib → ['src', 'lib']
// (not used) → []
$parser->addArgument('files', optional: true, repeatable: true);
// a.txt b.txt → ['a.txt', 'b.txt']
Transforming Values
Use a normalizer to transform the parsed value:
$parser->addOption('--count', normalizer: fn($v) => (int) $v);
// --count 42 → 42 (integer)
For file path validation, use the built-in normalizeRealPath:
$parser->addOption('--config', normalizer: Parser::normalizeRealPath(...));
// --config app.ini → '/full/path/to/app.ini'
// --config missing.ini → throws "File path 'missing.ini' not found."
Mixing Both Approaches
You can combine addFromHelp() with the fluent methods when you need normalizers for only some of the options:
$parser
->addFromHelp('
-v, --verbose Enable verbose mode
-q, --quiet Suppress output
')
->addOption('--config', '-c', normalizer: Parser::normalizeRealPath(...))
->addArgument('input');
Error Handling
The parser throws \Exception for invalid input:
use Nette\CommandLine\Parser;
$parser = new Parser;
$parser
->addOption('--output', '-o')
->addArgument('file');
try {
$args = $parser->parse();
} catch (\Exception $e) {
fwrite(STDERR, "Error: {$e->getMessage()}\n");
exit(1);
}
Common error messages:
Option --output requires argument. |
Option used without its required value |
Unknown option --foo. |
Unrecognized option |
Missing required argument <file>. |
Required argument not provided |
Unexpected parameter foo. |
Extra positional argument |
Value of option --format must be json, or xml. |
Value not in the enum |
Use isEmpty() to check whether no command-line arguments were provided at all (i.e. the user ran just
script.php with nothing after it):
if ($parser->isEmpty()) {
$parser->help();
exit;
}
Handling –help and –version
When your script has required arguments, running script.php --help would normally fail because the required
argument is missing. Use parseOnly() to check for info options first:
$parser = new Parser;
$parser
->addSwitch('--help', '-h')
->addSwitch('--version', '-V')
->addArgument('input'); // required
// First, check the info options (no validation, no exceptions)
$info = $parser->parseOnly(['--help', '--version']);
if ($info['--help']) {
$parser->help();
exit;
}
if ($info['--version']) {
echo "1.0.0\n";
exit;
}
// Now do the full parsing with validation
$args = $parser->parse();
The parseOnly() method:
- parses only the specified options, ignoring everything else,
- respects aliases (
-h→--help), - never throws exceptions,
- returns
nullfor options that weren't used.
Colorful Output
The Nette\CommandLine\Console class wraps text in ANSI color codes so your output stands out in the terminal:
use Nette\CommandLine\Console;
$console = new Console;
echo $console->color('red', 'Error!') . "\n";
echo $console->color('white/blue', 'White text on blue background') . "\n";
The color is given as 'foreground' or 'foreground/background'. Available colors are:
black, gray, silver, white, navy, blue,
green, lime, teal, aqua, maroon, red,
purple, fuchsia, olive, and yellow.
Colors are enabled automatically only when the output supports them. The color() method returns a plain string
when colors are disabled, so it's always safe to call. You can force the behavior manually:
$console->useColors(false); // disable colors
$console->useColors(true); // force colors on
Detecting the Terminal
Two static methods help you decide whether to use terminal-only features. detectColors() returns
false when the NO_COLOR environment variable is set, or when the output isn't a
CLI terminal; the FORCE_COLOR variable overrides the terminal check:
if (Console::detectColors()) {
// the terminal supports ANSI colors
}
detectTerminal() tells you whether the output is an interactive terminal (a TTY). This is useful for
auto-disabling features that only make sense in a terminal, such as progress indicators, line-rewriting output, or interactive
prompts:
if (Console::detectTerminal()) {
// output goes to an interactive terminal, not a file or pipe
}
Complete Example
Here's a real-world file converter script combining Parser and Console:
#!/usr/bin/env php
<?php
use Nette\CommandLine\Parser;
require __DIR__ . '/vendor/autoload.php';
$parser = new Parser;
$parser
->addFromHelp('
-h, --help Show this help
-v, --verbose Show detailed output
-n, --dry-run Show what would be done
-f, --format [type] Output format (default: json)
-o, --output <file> Output file
', [
'--format' => [
Parser::Enum => ['json', 'xml', 'csv'],
],
])
->addArgument('input', normalizer: Parser::normalizeRealPath(...));
// Handle --help before validation (avoids the "missing argument" error)
if ($parser->isEmpty() || $parser->parseOnly(['--help'])['--help']) {
echo "Usage: convert [options] <input>\n\n";
$parser->help();
exit;
}
try {
$args = $parser->parse();
} catch (\Exception $e) {
fwrite(STDERR, "Error: {$e->getMessage()}\n");
exit(1);
}
if ($args['--verbose']) {
echo "Converting {$args['input']} to {$args['--format']}...\n";
}
if ($args['--dry-run']) {
echo "Dry run: no changes made.\n";
exit;
}
// ... conversion logic here ...
echo "Done!\n";
The script accepts commands like:
convert input.txt– convert with the defaultsconvert -v --format xml input.txt– verbose, XML formatconvert -o result.txt input.txt– specify the output fileconvert --help– show the help (works even without the input file)