- Why?
- Live Demo
- Quck Start - Build your first CLI App
- Built-in Functionality
- Progress Bar
- Helper Functions
- Easy logging
- Multi processing
- Tips & Tricks
- Contributing
- Useful projects and links
- License
- See Also
The library greatly extends the functionality of CLI App and helps make creating new console utilities in PHP quicker and easier. Here's a summary of why this library is essential:
-
Enhanced Functionality
- The library supercharges Symfony/Console, facilitating a more streamlined development of console utilities.
-
Progress Bar Improvements
- Developers gain a refined progress bar suited for loop actions and enhanced with debugging information. This makes tracking task progress and diagnosing issues a breeze. See DemoProgressBar.php and see Live Demo.
$this->_($messages, $level, $context)
as part of CliCommand instead of Symfony/Console$output->writeln()
.cli($messages, $level, $context)
as alias for different classes.$this->progressBar(iterable|int $listOrMax, \Closure $callback, string $title = '')
as part of CliCommand instead of Symfony/Console ProgressBar.
-
Strict Type Conversion
- One notable feature allows for the strict conversion of option values, ensuring data integrity and reducing runtime errors. See DemoOptionsStrictTypes.php.
- Built-in validations for list of values. See Sanitize input variables.
-
Styling and Output Customization
- With built-in styles and color schemes, developers can make their console outputs more readable and visually appealing. See DemoStyles.php.
-
Message Aliases
- The library introduces powerful aliases for message outputs, allowing for concise and consistent command calls. This is especially helpful in maintaining clean code.
-
Advanced Options
- Features such as profiling for performance, timestamping, error muting, and specialized output modes (like cron and logstash modes) empower developers to refine their console outputs and diagnostics according to their specific needs.
- Display timing and memory usage information with
--profile
option. - Show timestamp at the beginning of each message with
--timestamp
option. - Mute any sort of errors. So exit code will be always
0
(if it's possible) with--mute-errors
. - None-zero exit code on any StdErr message with
--non-zero-on-error
option. - For any errors messages application will use StdOut instead of StdErr
--stdout-only
option (It's on your own risk!). - Disable progress bar animation for logs with
--no-progress
option.
-
Versatile Output Modes
- The library provides different output formats catering to various use cases. Whether you're focusing on user-friendly text, logs, or integration with tools like ELK Stack, there's an output mode tailored for you.
--output-mode=text
. By default, text output format. Userfriendly and easy to read.--output-mode=cron
. It's basically focused on logs output. It's combination of--timestamp --profile --stdout-only --no-progress -vv --no-ansi
.--output-mode=logstash
. It's basically focused on Logstash format for ELK Stack. Also, it means--stdout-only --no-progress -vv
.
-
Bonuses
- There is a multiprocess mode (please don't confuse it with multithreading) to speed up work with a monotonous dataset.
- Helper functions for user input in interactive mode.
composer require jbzoo/cli
The simplest CLI application has the following file structure. See the Demo App for more details.
/path/to/app/
my-app # Binrary file (See below)
composer.json # Composer file
/Commands/ # Commands directory
Simple.php # One of the commands (See below)
/vendor/
autoload.php # Composer autoload
See Details
{
"name" : "vendor/my-app",
"type" : "project",
"description" : "Example of CLI App based on JBZoo/CLI",
"license" : "MIT",
"keywords" : ["cli", "application", "example"],
"require" : {
"php" : ">=8.1",
"jbzoo/cli" : "^7.1.0"
},
"autoload" : {
"psr-4" : {"DemoApp\\" : ""}
},
"bin" : ["my-app"]
}
Binary file: demo/my-app
See Details
#!/usr/bin/env php
<?php declare(strict_types=1);
namespace DemoApp;
use JBZoo\Cli\CliApplication;
// Init composer autoloader
require_once __DIR__ . '/vendor/autoload.php';
// Optional. Set your application name and version.
$application = new CliApplication('My Console Application', 'v1.0.0');
// Optional. Looks at the online generator of ASCII logos
// https://patorjk.com/software/taag/#p=testall&f=Epic&t=My%20Console%20App
$application->setLogo(
<<<'EOF'
__ __ _____ _
| \/ | / ____| | | /\
| \ / |_ _ | | ___ _ __ ___ ___ | | ___ / \ _ __ _ __
| |\/| | | | | | | / _ \| '_ \/ __|/ _ \| |/ _ \ / /\ \ | '_ \| '_ \
| | | | |_| | | |___| (_) | | | \__ \ (_) | | __/ / ____ \| |_) | |_) |
|_| |_|\__, | \_____\___/|_| |_|___/\___/|_|\___| /_/ \_\ .__/| .__/
__/ | | | | |
|___/ |_| |_|
EOF,
);
// Scan directory to find commands.
// * It doesn't work recursively!
// * They must be inherited from the class \JBZoo\Cli\CliCommand
$application->registerCommandsByPath(__DIR__ . '/Commands', __NAMESPACE__);
// Optional. Action name by default (if there is no arguments)
$application->setDefaultCommand('list');
// Run application
$application->run();
The simplest CLI action: ./demo/Commands/DemoSimple.php
See Details
<?php declare(strict_types=1);
namespace DemoApp\Commands;
use JBZoo\Cli\CliCommand;
use JBZoo\Cli\Codes;
class Simple extends CliCommand
{
protected function configure(): void
{
// Action name. It will be used in command line.
// Example: `./my-app simple`
$this->setName('simple');
// Defined inhereted CLI options. See ./src/CliCommand.php for details.
parent::configure();
}
protected function executeAction(): int
{
// Your code here
$this->_('Hello world!');
// Exit code. 0 - success, 1 - error.
return self::SUCCESS;
}
}
As live-demo take a look at demo application - ./demo/Commands/DemoOptionsStrictTypes.php.
Try to launch ./my-app options-strict-types
.
// If the option has `InputOption::VALUE_NONE` it returns true/false.
// --option-name
$value = $this->getOpt('option-name'); // `$value === true`
// --option-name=" 123.6 "
$value = $this->getOpt('option-name'); // Returns the value AS-IS. `$value === " 123.6 "`
// --option-name=" 123.6 "
$value = $this->getOptBool('option-name'); // Converts an input variable to boolean. `$value === true`
// --option-name=" 123.6 "
$value = $this->getOptInt('option-name'); // Converts an input variable to integer. `$value === 123`
$value = $this->getOptInt('option-name', 42, [1, 2, 42]); // Strict comparing with allowed values
// --option-name=" 123.6 "
$value = $this->getOptFloat('option-name'); // Converts an input variable to float. `$value === 123.6`
$value = $this->getOptFloat('option-name', 1.0, [1.0, 2.0, 3.0]); // Strict comparing with allowed values
// --option-name=" 123.6 "
$value = $this->getOptString('option-name'); // Converts an input variable to trimmed string. `$value === "123.6"`
$value = $this->getOptString('option-name', 'default', ['default', 'mini', 'full']); // Strict comparing with allowed values
// --option-name=123.6
$value = $this->getOptArray('option-name'); // Converts an input variable to trimmed string. `$value === ["123.6"]`
// --option-name="15 July 2021 13:48:00"
$value = $this->getOptDatetime('option-name'); // Converts an input variable to \DateTimeImmutable object.
// Use standard input as input variable.
// Example. `echo " Qwerty 123 " | php ./my-app agruments`
$value = self::getStdIn(); // Reads StdIn as string value. `$value === " Qwerty 123 \n"`
There are list of predefined colors
<black> Text in Black color </black>
<red> Text in Red Color </red>
<green> Text in Green Color </green>
<yellow> Text in Yellow Color </yellow>
<blue> Text in Blue Color </blue>
<magenta>Text in Magenta Color</magenta>
<cyan> Text in Cyan Color </cyan>
<white> Text in White Color </white>
<!-- Usually default color is white. It depends on terminal settings. -->
<!-- You should use it only to overwrite nested tags. -->
<default>Text in Default Color</default>
There are list of predefined styles
<bl>Blinked Text</bl>
<b>Bold Text</b>
<u>Underlined Text</u>
<r>Reverse Color/Backgroud</r>
<bg>Change Background Only</bg>
Also, you can combine colors ans styles.
<magenta-bl>Blinked text in magenta color</magenta-bl>
<magenta-b>Bold text in magenta color</magenta-b>
<magenta-u>Underlined text in magenta color</magenta-u>
<magenta-r>Reverse text in magenta color</magenta-r>
<magenta-bg>Reverse only background of text in magenta color</magenta-bg>
And predefined shortcuts for standard styles of Symfony Console
<i> alias for <info>
<c> alias for <commnet>
<q> alias for <question>
<e> alias for <error>
Console commands have different verbosity levels, which determine the messages displayed in their output.
As live-demo take a look at demo application - ./demo/Commands/ExamplesOutput.php. You can see Demo video.
Example of usage of verbosity levels
// There two strictly(!) recommended output ways:
/**
* Prints a message to the output in the command class which inherits from the class \JBZoo\Cli\CliCommand
*
* @param string|string[] $messages Output message(s). Can be an array of strings or a string. Array of strings will be imploded with new line.
* @param string $verboseLevel is one of value form the class \JBZoo\Cli\OutLvl::*
* @param string $context is array of extra info. Will be serialized to JSON and displayed in the end of the message.
*/
$this->_($messages, $verboseLevel, $context);
/**
* This is global alias function of `$this->_(...)`.
* It's nice to have it if you want to display a text from not CliCommand class.
*/
JBZoo\Cli\cli($messages, $verboseLevel, $context);
# Do not output any message
./my-app output -q
./my-app output --quiet
# Normal behavior, no option required. Only the most useful messages.
./my-app output
# Increase verbosity of messages
./my-app output -v
# Display also the informative non essential messages
./my-app output -vv
# Display all messages (useful to debug errors)
./my-app output -vvv
As live-demo take a look at demo application - ./demo/Commands/DemoProfile.php.
Try to launch ./my-app profile --profile
.
As live-demo take a look at demo application - ./demo/Commands/DemoProgressBar.php and Live Demo.
You can consider this as a substitute for the long cycles you want to profile.
Keep in mind that there is an additional overhead for memory and runtime to calculate all the extra debugging information in --verbose
mode.
$this->progressBar(5, function (): void {
// Some code in loop
});
$this->progressBar($arrayOfSomething, function ($value, $key, $step) {
// Some code in loop
if ($step === 3) {
throw new ExceptionBreak("Something went wrong with \$value={$value}. Stop the loop!");
}
return "<c>Callback Args</c> \$value=<i>{$value}</i>, \$key=<i>{$key}</i>, \$step=<i>{$step}</i>";
}, 'Custom messages based on callback arguments', $throwBatchException);
As live-demo take a look at demo application - ./demo/Commands/DemoHelpers.php.
Try to launch ./my-app helpers
.
JBZoo/Cli uses Symfony Question Helper as base for aliases.
Ask any custom question and wait for a user's input. There is an option to set a default value.
$yourName = $this->ask("What's your name?", 'Default Noname');
$this->_("Your name is \"{$yourName}\"");
Ask a question and hide the response. This is particularly convenient for passwords. There is an option to set a random value as default value.
$yourSecret = $this->askPassword("New password?", true);
$this->_("Your secret is \"{$yourSecret}\"");
If you have a predefined set of answers the user can choose from, you could use a method askOption
which makes sure
that the user can only enter a valid string from a predefined list.
There is an option to set a default option (index or string).
$selectedColor = $this->askOption("What's your favorite color?", ['Red', 'Blue', 'Yellow'], 'Blue');
$this->_("Selected color is {$selectedColor}");
Suppose you want to confirm an action before actually executing it. Add the following to your command.
$isConfirmed = $this->confirmation('Are you ready to execute the script?');
$this->_("Is confirmed: " . ($isConfirmed ? 'Yes' : 'No'));
If you need to show an aligned list, use the following code.
use JBZoo\Cli\CliRender;
$this->_(CliRender::list([
"It's like a title",
'Option Name' => 'Option Value',
'Key' => 'Value',
'Another Key #2' => 'Qwerty',
], '*')); // It's bullet character
* It's like a title
* Option Name : Option Value
* Key : Value
* Another Key #2: Qwerty
./my-app output --timestamp >> /path/to/crontab/logs/$(date +%Y-%m-%d).log 2>&1
Just add the --output-mode=cron
flag and save the output to a file. Especially, this is very handy for saving logs for Crontab.
./my-app output --output-mode=cron >> /path/to/crontab/logs/$(date +%Y-%m-%d).log 2>&1
Just add the --output-mode=logstash
flag and save the output to a file. Especially, this is very handy for saving logs for ELK Stack.
./my-app output --output-mode=logstash >> /path/to/logstash/logs/$(date +%Y-%m-%d).log 2>&1
There is a multiprocess mode (please don't confuse it with multithreading) to speed up work with a monotonous dataset. Basically, JBZoo\Cli
will start a separate child process (not a thread!) for each dataset and wait for all of them to execute (like a Promise). This is how you get acceleration, which will depend on the power of your server and the data processing algorithm.
You will see a simple progress bar, but you won't be able to profile and log nicely, as it works for normal mode.
You can find examples here
- ./tests/TestApp/Commands/TestSleepMulti.php - Parent command
- ./tests/TestApp/Commands/TestSleep.php - Child command
Notes:
- Pay attention on the method
executeOneProcess()
andgetListOfChildIds()
which are used to manage child processes. They are inherited fromCliCommandMultiProc
class. - Optimal number of child processes is
Number of CPU cores - 1
. You can override this value by setting cli options. See them here ./src/CliCommandMultiProc.php. - Be really careful with concurrency. It's not easy to debug. Try to use
-vvv
option to see all errors and warnings.
- Use class
\JBZoo\Cli\Codes
to get all available exit codes. - You can add extra context to any message. It will be serialized to JSON and displayed in the end of the message. Just use
CliHelper::getInstance()->appendExtraContext(['section' => ['var' => 'value']]);
- You can define constant
\JBZOO_CLI_TIMESTAMP_REAL=true
to addtimestamp_real
as exta context. Sometimes it's useful for logstash if default value@timestamp
doesn't work for you.
# Fork the repo and build project
make update
# Make your local changes
# Run all tests and check code style
make test-all
# Create your pull request and check all tests on GithubActions page
- Symfony/Console Docs
- kevinlebrun/colors.php - New colors for the terminal
- php-school/cli-menu - Interactive menu with nested items
- nunomaduro/collision - Beautiful error reporting
- splitbrain/php-cli - Lightweight and no dependencies CLI framework
- thephpleague/climate - Allows you to easily output colored text, special formats
- Exit Codes With Special Meanings
- How to redirect standard (stderr) error in bash
MIT
- CI-Report-Converter - Converting different error reports for deep compatibility with popular CI systems.
- Composer-Diff - See what packages have changed after
composer update
. - Composer-Graph - Dependency graph visualization of composer.json based on mermaid-js.
- Mermaid-PHP - Generate diagrams and flowcharts with the help of the mermaid script language.
- Utils - Collection of useful PHP functions, mini-classes, and snippets for every day.
- Image - Package provides object-oriented way to manipulate with images as simple as possible.
- Data - Extended implementation of ArrayObject. Use files as config/array.
- Retry - Tiny PHP library providing retry/backoff functionality with multiple backoff strategies and jitter support.
- SimpleTypes - Converting any values and measures - money, weight, exchange rates, length, ...