Nette Command-Line

Eine leichtgewichtige Bibliothek zum Erstellen von Kommandozeilenanwendungen in PHP. Sie verarbeitet Schalter, Optionen und positionelle Argumente und hilft Ihnen, farbige Terminalausgaben mit ANSI-Unterstützung zu erzeugen.

Installation:

composer require nette/command-line

Sie erfordert PHP in der Version 8.2 und unterstützt PHP bis 8.5.

Verarbeitung von Kommandozeilenargumenten

Jedes Konsolenskript muss Argumente wie --verbose, -o output.txt oder schlichte Dateinamen verarbeiten können. Die Klasse Nette\CommandLine\Parser bietet den schnellsten Einstieg: Sie schreiben einfach den Hilfetext, und der Parser extrahiert die Definitionen der Optionen selbst daraus:

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();

Das war's. Der Parser erkennt, dass --verbose ein Schalter ist, --output einen Wert verlangt und --format einen optionalen Wert mit json als Standardwert hat. Ihr Hilfetext bleibt so mit den tatsächlichen Definitionen der Optionen im Einklang.

Die Methode parse() gibt ein assoziatives Array zurück. Die Schlüssel entsprechen genau den Namen der Optionen, wie Sie sie definiert haben, einschließlich der Bindestriche:

[
	'--help' => true,         // oder null, wenn nicht verwendet
	'--verbose' => null,
	'--output' => 'file.txt', // oder null, wenn nicht verwendet
	'--format' => 'json',     // Standardwert aus (default: json)
	'--include' => ['src', 'lib'],
	'--dry-run' => null,
]

Standardmäßig liest parse() aus $_SERVER['argv']. Sie können ein eigenes Array übergeben, was sich beim Testen anbietet:

$args = $parser->parse(['--verbose', '-o', 'out.txt']);

Syntax des Hilfetexts

Der Parser extrahiert die Definitionen der Optionen nach diesen Regeln aus dem formatierten Hilfetext:

--verbose Schalter (ohne Wert)
-v, --verbose Schalter mit kurzem Alias
--output <file> Option mit erforderlichem Wert
--format [type] Option mit optionalem Wert
(default: json) setzt den Standardwert
<path>... wiederholbare Option

Jede Zeile definiert eine Option. Die Namen der Optionen müssen von der Beschreibung durch mindestens zwei Leerzeichen getrennt sein.

Weitere Konfiguration

Manche Einstellungen lassen sich im Hilfetext nicht ausdrücken. Übergeben Sie sie als Array im zweiten Parameter, wobei der Schlüssel der Name der Option ist:

$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,
	],
]);

Verfügbare Schlüssel:

Parser::Repeatable sammelt mehrere Werte in einem Array
Parser::RealPath prüft, ob die Datei existiert, und wandelt den Pfad in einen absoluten um
Parser::Normalizer Transformationsfunktion fn($value) => ...
Parser::Default Standardwert (dasselbe wie (default: x) im Hilfetext)
Parser::Enum Array erlaubter Werte

Fluent API

Wenn Sie mehr Kontrolle über die Definitionen der Optionen brauchen, verwenden Sie das Fluent API mit den Methoden addSwitch(), addOption() und addArgument(). Dieser Ansatz eröffnet Ihnen alle Möglichkeiten, einschließlich Normalizer, Enums und der genauen Steuerung jedes Parameters:

use Nette\CommandLine\Parser;

$parser = new Parser;
$parser
	->addSwitch('--verbose', '-v')
	->addOption('--output', '-o')
	->addArgument('file');

$args = $parser->parse();

Wie bei addFromHelp() können Sie der Methode parse() zum Testen ein eigenes Array übergeben:

$args = $parser->parse(['--verbose', '-o', 'out.txt', 'input.txt']);

Schalter, Optionen und Argumente

Es gibt drei Arten von Eingaben auf der Kommandozeile:

Schalter sind Flags ohne Wert, etwa --verbose oder -v. Sind sie vorhanden, werden sie als true ausgewertet, sonst als null:

$parser->addSwitch('--verbose', '-v');
// --verbose         → true
// -v                → true
// (nicht verwendet) → null

Optionen nehmen Werte entgegen, etwa --output file.txt. Der Wert lässt sich durch ein Leerzeichen oder durch = trennen:

$parser->addOption('--output', '-o');
// --output file.txt    → 'file.txt'
// --output=file.txt    → 'file.txt'
// -o file.txt          → 'file.txt'
// --output             → wirft eine Exception (Wert erforderlich)
// (nicht verwendet)    → null

Beachten Sie, dass die Option selbst immer optional ist – wird sie nicht verwendet, kommt null zurück. Wird sie aber verwendet, ist der Wert standardmäßig erforderlich. Mit optionalValue: true erlauben Sie die Option auch ohne Wert (sie wird dann als true ausgewertet):

$parser->addOption('--format', '-f', optionalValue: true);
// --format json        → 'json'
// --format             → true
// (nicht verwendet)    → null

Wird dieselbe Option ohne repeatable: true mehrfach verwendet, gewinnt der letzte Wert:

$parser->addOption('--output', '-o');
// -o first.txt -o second.txt  → 'second.txt'

Argumente sind positionelle Werte ohne Bindestriche. Standardmäßig sind sie erforderlich. Mit optional: true machen Sie sie optional:

$parser->addArgument('input');
// script.php file.txt  → 'file.txt'
// (nicht verwendet)    → wirft eine Exception

$parser->addArgument('output', optional: true);
// (nicht verwendet)    → null

$parser->addArgument('output', optional: true, fallback: 'out.txt');
// (nicht verwendet)    → 'out.txt'

Mit fallback legen Sie den Wert fest, der verwendet wird, wenn eine optionale Option oder ein optionales Argument nicht angegeben wird. Bei Optionen mit optionalValue: true gilt: Die Verwendung der Option ohne Wert wird weiterhin als true ausgewertet, während der Standardwert nur dann greift, wenn die Option überhaupt nicht vorhanden ist:

$parser->addOption('--format', '-f', optionalValue: true, fallback: 'json');
// --format xml       → 'xml'
// --format           → true (Option ohne Wert verwendet)
// (nicht verwendet)  → 'json' (Standardwert)

Argumente können auf der Kommandozeile an beliebiger Stelle stehen – sie müssen nicht erst hinter den Optionen kommen:

// alle diese Schreibweisen sind gleichwertig:
// script.php --verbose input.txt
// script.php input.txt --verbose

Werte mit einem Enum einschränken

Beschränken Sie die akzeptierten Werte auf eine bestimmte Menge:

$parser->addOption('--format', '-f', enum: ['json', 'xml', 'csv']);
// --format yaml  → wirft "Value of option --format must be json, or xml, or csv."

Wiederholbare Optionen

Mit repeatable: true sammeln Sie mehrere Werte in einem Array:

$parser->addOption('--include', '-I', repeatable: true);
// -I src -I lib      → ['src', 'lib']
// (nicht verwendet)  → []

$parser->addArgument('files', optional: true, repeatable: true);
// a.txt b.txt        → ['a.txt', 'b.txt']

Werte transformieren

Mit normalizer transformieren Sie den verarbeiteten Wert:

$parser->addOption('--count', normalizer: fn($v) => (int) $v);
// --count 42  → 42 (Integer)

Zum Prüfen eines Dateipfads verwenden Sie das eingebaute normalizeRealPath:

$parser->addOption('--config', normalizer: Parser::normalizeRealPath(...));
// --config app.ini     → '/full/path/to/app.ini'
// --config missing.ini → wirft "File path 'missing.ini' not found."

Kombination beider Ansätze

Wenn Sie Normalizer nur für einige Optionen brauchen, können Sie addFromHelp() mit den Fluent-Methoden kombinieren:

$parser
	->addFromHelp('
		-v, --verbose  Enable verbose mode
		-q, --quiet    Suppress output
	')
	->addOption('--config', '-c', normalizer: Parser::normalizeRealPath(...))
	->addArgument('input');

Fehlerbehandlung

Der Parser wirft bei ungültiger Eingabe \Exception:

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);
}

Häufige Fehlermeldungen:

Option --output requires argument. Option ohne den erforderlichen Wert verwendet
Unknown option --foo. unbekannte Option
Missing required argument <file>. erforderliches Argument nicht angegeben
Unexpected parameter foo. überzähliges positionelles Argument
Value of option --format must be json, or xml. Wert nicht im Enum

Mit der Methode isEmpty() stellen Sie fest, ob überhaupt keine Argumente auf der Kommandozeile angegeben wurden (der Benutzer also nur script.php ohne irgendetwas dahinter aufgerufen hat):

if ($parser->isEmpty()) {
	$parser->help();
	exit;
}

Umgang mit –help und –version

Wenn Ihr Skript erforderliche Argumente hat, würde der Aufruf script.php --help normalerweise fehlschlagen, weil das erforderliche Argument fehlt. Verwenden Sie parseOnly(), um die informativen Optionen zuerst zu prüfen:

$parser = new Parser;
$parser
	->addSwitch('--help', '-h')
	->addSwitch('--version', '-V')
	->addArgument('input');  // erforderlich

// Zuerst prüfen wir die informativen Optionen (ohne Validierung, ohne Exceptions)
$info = $parser->parseOnly(['--help', '--version']);

if ($info['--help']) {
	$parser->help();
	exit;
}

if ($info['--version']) {
	echo "1.0.0\n";
	exit;
}

// Erst jetzt die vollständige Verarbeitung mit Validierung
$args = $parser->parse();

Die Methode parseOnly():

  • verarbeitet nur die angegebenen Optionen und ignoriert alles Übrige,
  • respektiert Aliase (-h--help),
  • wirft niemals eine Exception,
  • gibt null für Optionen zurück, die nicht verwendet wurden.

Farbige Ausgabe

Die Klasse Nette\CommandLine\Console umhüllt den Text mit ANSI-Farbcodes, sodass Ihre Ausgabe im Terminal hervorsticht:

use Nette\CommandLine\Console;

$console = new Console;
echo $console->color('red', 'Error!') . "\n";
echo $console->color('white/blue', 'White text on blue background') . "\n";

Die Farbe wird als 'Vordergrund' oder 'Vordergrund/Hintergrund' angegeben. Verfügbare Farben sind: black, gray, silver, white, navy, blue, green, lime, teal, aqua, maroon, red, purple, fuchsia, olive und yellow.

Farben werden nur dann automatisch eingeschaltet, wenn die Ausgabe sie unterstützt. Die Methode color() gibt bei abgeschalteten Farben einen einfachen String zurück, ihr Aufruf ist also immer sicher. Sie können das Verhalten auch von Hand festlegen:

$console->useColors(false); // schaltet die Farben ab
$console->useColors(true);  // erzwingt die Farben

Erkennung des Terminals

Zwei statische Methoden helfen Ihnen bei der Entscheidung, ob Sie Funktionen nutzen, die nur im Terminal Sinn ergeben. detectColors() gibt false zurück, wenn die Umgebungsvariable NO_COLOR gesetzt ist oder wenn die Ausgabe kein Konsolenterminal ist; die Variable FORCE_COLOR überstimmt die Prüfung des Terminals:

if (Console::detectColors()) {
	// das Terminal unterstützt ANSI-Farben
}

detectTerminal() sagt Ihnen, ob die Ausgabe ein interaktives Terminal (ein TTY) ist. Das eignet sich zum automatischen Abschalten von Funktionen, die nur in einem Terminal Sinn ergeben, etwa Fortschrittsanzeigen, das Überschreiben von Zeilen oder interaktive Rückfragen:

if (Console::detectTerminal()) {
	// die Ausgabe geht an ein interaktives Terminal, nicht in eine Datei oder Pipe
}

Vollständiges Beispiel

Hier ist ein Skript zur Dateikonvertierung aus der Praxis, das Parser und Console kombiniert:

#!/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(...));

// --help noch vor der Validierung behandeln (vermeidet den Fehler "missing argument")
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;
}

// ... hier folgt die Logik der Konvertierung ...

echo "Done!\n";

Das Skript akzeptiert Befehle wie:

  • convert input.txt – Konvertierung mit den Standardeinstellungen
  • convert -v --format xml input.txt – ausführliche Ausgabe, Format XML
  • convert -o result.txt input.txt – Angabe der Ausgabedatei
  • convert --help – Anzeige der Hilfe (funktioniert auch ohne Eingabedatei)
Version: 1.x