zetacomponents/console-tools
A PHP library from Apache Zeta Components for building command-line applications, with argument parsing, console input/output, progress bars, tables, menus, dialogs, and reusable components for interactive terminal tools.
What it is. zetacomponents/console-tools is the console component of Zeta Components (the old eZ Components library). It is a PHP 5-era toolbox, not a framework — you get six independent widgets:
ezcConsoleInput — option/argument parsing (short + long options, types, validators, dependencies)ezcConsoleOutput — tagged, colored, verbosity-aware output (<error>…</error>)ezcConsoleProgressbar, ezcConsoleProgressMonitor, ezcConsoleStatusbar — progress feedbackezcConsoleTable — column-aligned tablesezcConsoleQuestionDialog, ezcConsoleMenuDialog — interactive promptsThere is no facade, no service provider, no config file. You new a class and use it.
Install.
composer require zetacomponents/console-tools
This pulls zetacomponents/base, whose ezc_bootstrap.php registers the ezc class autoloader. That autoloader is not PSR-4, so if new ezcConsoleOutput() reports "class not found", the base package (or the bootstrap include) is missing — not the console package. Non-Composer installs must require Base/src/ezc_bootstrap.php themselves.
Where to look first. src/input.php and src/output.php are the two files you will use in every script. After those, read the per-widget files (progressbar.php, table.php, question_dialog.php); each class has a plain-English docblock example at the top, and the docs/ folder ships a tutorial per component. Options are separate classes in src/options/ — that is where you look when you need to change a default.
First use case. A one-file CLI script: two options, colored output, and a progress bar.
<?php
require __DIR__ . '/vendor/autoload.php';
$output = new ezcConsoleOutput();
$output->formats->info->color = 'green';
$output->formats->warn->color = 'yellow';
$input = new ezcConsoleInput();
$help = new ezcConsoleOption('h', 'help', ezcConsoleInput::TYPE_NONE, false, 'Show help');
$input->registerOption($help);
$file = new ezcConsoleOption('f', 'file', ezcConsoleInput::TYPE_STRING, 'data.csv', 'File to import');
$input->registerOption($file);
try {
$input->process(); // parses $_SERVER['argv']
} catch (ezcConsoleInputException $e) {
$output->outputLine('<error>' . $e->getMessage() . '</error>');
exit(1);
}
if ($help->value) {
echo $input->getHelp(); // or render usage from $input->options
exit(0);
}
$output->outputLine("<info>Importing {$file->value}</info>");
$bar = new ezcConsoleProgressbar($output, 100);
$bar->start();
foreach (range(1, 100) as $i) {
$bar->advance();
}
$bar->finish();
That skeleton — options → validate → output → widget — is the shape of every script built on this package.
One bootstrap per command, options declared declaratively. Register every option up front in a single method, then let process() do the parsing. Do not hand-roll $argv parsing alongside it; ezcConsoleInput already handles --opt=value, --opt value, -abc bundling and type coercion.
$verbose = new ezcConsoleOption('v', 'verbose', ezcConsoleInput::TYPE_NONE);
$verbose->multiple = true; // repeatable: -vvv
$input->registerOption($verbose);
$output->options->verbosityLevel = max(1, (int) count($verbose->value));
Cross-option rules belong in the option, not in if soup. Options support dependencies and value validators, both enforced by process():
$format = new ezcConsoleOption('F', 'format', ezcConsoleInput::TYPE_STRING);
$format->addValidator(function ($value) {
return in_array($value, ['csv', 'json'], true);
});
$input->registerOption($format);
$json = new ezcConsoleOption('', 'json', ezcConsoleInput::TYPE_NONE);
$json->dependencies[] = new ezcConsoleOptionRule($format, ['json']);
$input->registerOption($json);
Invalid combinations then throw ezcConsoleInputException with a usable message instead of producing silently wrong behaviour.
Define semantic output formats once, then use tags everywhere. Pick names like error, warn, info, debug in a configureOutput() helper, and write user-facing strings as tagged text. This gives you one place to disable colorization (piped output, CI) and one place to add verbosity gating:
$output->formats->debug->color = 'gray';
// only actually printed when verbosity allows it
$output->outputLine('<debug>cache hit for ' . $key . '</debug>');
Use the right progress widget. ezcConsoleProgressbar for one loop with a known total, ezcConsoleProgressMonitor for a short list of named tasks with per-task status, ezcConsoleStatusbar for many tiny items. In all three cases wrap the loop in try/finally and call finish() — otherwise a thrown exception leaves the cursor mid-bar and the next line of output is mangled.
Tables: build rows by array access, print once.
$table = new ezcConsoleTable($output, 100);
$table[] = ['ID', 'Name', 'Status'];
foreach ($rows as $row) {
$table[] = [(string) $row->id, $row->name, '<info>ok</info>'];
}
$table->outputTable();
Set $table->options->colWidth and the wrap behaviour explicitly if your data can contain long unbroken strings — the auto layout is easy to blow past the terminal width.
Interactive prompts. Use ezcConsoleQuestionDialog for free-form/yes-no answers and ezcConsoleMenuDialog (associative array of key => label) for menus. ezcConsoleQuestionDialogMapping is the convenient way to map y/n/yes/no onto booleans. Always give the dialog a default and always give the script a --yes/--no-interaction escape hatch.
Testing. process() accepts an array of arguments, so option parsing is unit-testable without shelling out:
$input = $this->buildInput();
$input->process(['script.php', '--file', 'x.csv', '-vv']);
$this->assertSame('x.csv', $input->getOption('file')->value);
Widgets that write to stdout can be wrapped in output buffering; interactive dialogs generally cannot be tested this way, so keep them behind a thin method you can stub.
In a Laravel codebase. Treat this as a library for standalone scripts (bin/, deploy scripts, importers) rather than as an Artisan replacement. Inside a command, Laravel already gives you $this->output (Symfony Console) with formatting, tables and progress bars; the two output systems do not share a stream, and you cannot hand a Symfony OutputInterface to an ezc widget. If you must mix them, isolate the ezc code in one method and flush/detach before returning to Laravel output. Given zero dependents and low activity, do not adopt it for new application code — it is a reasonable choice only when you are already in a Zeta/eZ codebase or want a dependency-free set of widgets pinned to a legacy PHP version.
NOASSERTION. The source headers carry Apache-2.0, but confirm that from the files themselves before shipping — do not rely on the repo badge.each(), ${} interpolation, bare-word constants, and dynamic-property deprecations to bite on modern runtimes. Test on your actual PHP version before committing, and be prepared to pin an older PHP or vendor a patched fork. This is the single biggest reason to prefer Symfony Console for greenfield work.ezc autoloader is invisible to tooling. No PSR-4 means IDEs, PHPStan and Laravel's package discovery will not resolve these classes until vendor/autoload.php has pulled in ezc_bootstrap.php. Static analysis will be mostly useless; rely on the docblocks in src/.process() throws ezcConsoleInputException for bad types, failed validators and unmet dependencies. Always wrap it, print the message, and exit non-zero — a stack trace is not a user-facing error message. Also check a registered help option's value yourself rather than assuming it exits for you.<error> in a filename, a database value or an exception message will be interpreted as a format tag. Sanitize untrusted strings before outputLine()/outputText() — strip or neutralize </>. This is the most common silent-data-loss bug with this package.$output->options->colorization controls it; leaving it on pollutes logs, CI output and anything piped to a file with escape sequences. Drive it from your own --no-color flag or from stream_isatty(STDOUT).\r; any outputLine() or var_dump() inside the loop destroys the line. Finish the bar first. Also note the redraw frequency is time-based (roughly a few per second), so a very fast loop appears to jump from 0% to 100% — lower it if you need deterministic output in tests or logs.-i will block indefinitely. Guard on TTY detection or require an explicit --yes.TYPE_INT does not check ranges or signs. Anything domain-specific needs an explicit validator — which is also the cleanest extension point in the whole package, since validators are plain callables.ezcConsoleOutputFormat objects for new styles, register callables as option validators, subclass the widgets if you need custom rendering. There is no plugin system; don't go looking for one.->value along with $input->arguments for positional parameters. Nine times out of ten a "broken" script is an option whose type was left at TYPE_NONE while the user passed a value.stty, degrading on Windows). For tables and progress bars, set widths explicitly in code rather than trusting detection, especially in containers where COLUMNS is unset.How can I help you explore Laravel packages today?