17
How to create a simple CLI app with MiniCLI.
In this post we're covering how to create a simple CLI app with the help of MiniCLI. What we'll be creating is a small CLI tool to ping websites.
The first thing we do is to create a folder for our new tool, which I'll name ping-cli
and require the minicli/minicli
package.
composer require minicli/minicli
Let's also create a .gitignore
to make sure the /vendor
folder doesn't get added to source control.
vendor/
composer.lock
With the requirements done we can continue on to creating our entry-point. This is what'll be called from the command-line. Create a bin
folder and a file named ping-cli
.
mkdir bin && touch bin/ping-cli
Now we can use bash
and the minicli
package to create our new tool. Start by using a shebang to load our new ping-cli
file in a php
environment, and add out opening php
tags.
#!/usr/bin/env php
<?php declare(strict_types = 1);
You can omit declare
if you don't want strict typing.
Before moving on to using minicli
, we must first make sure we're calling this file from the CLI. We do this with the php_sapi_name
functions, which returns the string of our current interface.
if (php_sapi_name() !== 'cli') {
exit;
}
To integrate minicli
we include the composer
autoloader and instantiate a new Minicli
class.
We do this by establishing a new variable $root
to hold the parent directory of the directory bin/ping-cli
. Adding a check to see if we can grab the autoloader using that path. Should that operation be false, we will set the $root
to be 4
levels up from our parent directory.
This is done so that if our tool is required and is symlinked to vendor/bin/ping-cli
, we use the autoloader generated by the outside project correctly.
Lastly we require the autoloader and instantiate a new Minicli\App
.
$root = dirname(__DIR__);
if (! is_file($root . '/vendor/autoload.php')) {
$root = dirname(__DIR__, 4);
}
require $root . '/vendor/autoload.php';
use Minicli\App;
$app = new App();
To get the CLI tool to run, we have to call the runCommand
method on our $app
. Add the following line to the end of our ping-cli
file.
$app->runCommand($argv);
The $argv
variable is a reserved variable provided to us by PHP, you can read more about it in the documentation.
Running our command now will output ./minicli help
to the console, try it yourself by running the command.
./bin/ping-cli
Before embarking on the journey of creating the new command, let us first update our signature. This signature will replace the default ./minicli help
signature ran when running our tool with no input.
We set our new signature using the setSignature
method on the minicli
$app
.
$app->setSignature(<<<EOD
_ ___
____ (_)___ ____ _ _____/ (_)
/ __ \/ / __ \/ __ `/_____/ ___/ / /
/ /_/ / / / / / /_/ /_____/ /__/ / /
/ .___/_/_/ /_/\__, / \___/_/_/
/_/ /____/ \e[0;32m Ver. 0.0.1
EOD);
This will give us the following signature.
With the basic setup out-the-way, let's continue on by registering our ping
command. Since this tool will only have a single command, we will forgo registering a namespace and just register a single command with the registerCommand
method.
To register the command, we pass it the name
and the callable
which will get passed the $input
. For convenience we will also use the $app
in out callable
.
use Minicli\Command\CommandCall;
$app->registerCommand('ping', function (CommandCall $input) use ($app) {
$app->getPrinter()->success('We have lift-off! 🚀');
});
Calling the getPrinter
method access the output handler, which we then use to print a success
message to the console.
Running ./bin/ping-cli ping
will now print back We have lift-off! 🚀 in our console.
With the command registered, let us move swiftly along to implementing our real ping
command.
This command will take a url
parameter, in either of two formats, HTTP or HTTPS. With this parameter, we will send a request to the url
and see if it's accessible.
To grab the parameter we call getParam
on our $input
and specify which parameter we want. Let's store our url
parameter in a $url
variable, or default to null
.
With this we can sent a error
output using getPrinter
and exit early if no url
was provided.
$url = $input->getParam('url') ?? null;
if (! $url) {
$app->getPrinter()->error('Parameter <url> was not provided');
return 1;
}
Now we can continue on and validate the incoming $url
parameter. To do that we will use RegEx and filter_var
.
We first check for a valid URL - since URLs can be valid even without the HTTP protocol, we add this check first. Returning early is the check fails.
Next we use RegEx to check if the $url
provided starts with a valid HTTP(s) protocol. Returning early with a message if that fails.
if (! filter_var($url, FILTER_VALIDATE_URL)) {
$app->getPrinter()->error('Parameter <url> not a valid URL.');
return 1;
}
if (! preg_match('#^https?://#i', $url)) {
$app->getPrinter()->error('Parameter <url> has to include a valid HTTP protocol.');
return 1;
}
While this isn't the end-all be-all of validating the $url
, it'll do for a simple ping command.
Before we try and access the $url
provided, we need to create a stream context for file_get_contents
to use. In this context we will set the HTTP method and some HTTP headers.
To keep things a bit separated, let's create a new function called getStreamContext
where we'll create our new context.
function getStreamContext() {
$headers = implode("\r\n", [
'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36',
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9',
'Accept-Language: en-US,en;q=0.9',
'Upgrade-Insecure-Requests: 1',
'X-Application: PingCLI/0.0.1',
'Sec-GPC: 1',
]);
return stream_context_create([
'http' => [
'method' => 'GET',
'header' => $headers,
]
]);
}
We set some headers so the $url
we're accessing thinks the site is being used from a Chrome browser running on a Mac.
With the context created we can now use file_get_contents
to ping the $url
. Since we won't use the returning content for anything, we can use file_get_contents
directly in our if statement.
if (! @file_get_contents($url, context: getStreamContext())) {
$app->getPrinter()->error('Parameter <url> could not be reached.');
return 1;
}
The reasoning being the silencing (@) is because we can provide a valid URL that file_get_contents
might not be able to access, that will give of warnings unless silenced.
If all those checks are cleared, we can safely assume the site is reachable and up. Let's give the user an indication the site was reached by adding the following code to the end of our callable
.
$app->getPrinter()->success(sprintf('URL <%s> is up.', $url));
We can now test our ping command by running the following.
./bin/ping-cli ping url=https://devdojo.com
This will produce the following result.
Before I leave you, I'd like to add one more feature to our command. That's the estimated ping time, which is an estimate of how long it took to access the website.
We'll do this with the microtime
function and some subtraction. We'll Add the following right above our file_get_contents
check.
$start_time = microtime(as_float: true);
Below our if, and before we output our success message we add the following.
$end_time = microtime(as_float: true);
These two variables will now be used to estimate how long it took to access the site. Since they're places in between file_get_contents
we should get a fairly close estimate.
Let's print that estimate to our user below out success output.
$app->getPrinter()->display(sprintf('Est. ping time: %.02f sec', ($end_time - $start_time)));
This will now add an estimated ping time to the end of our output, as displayed below.
This is how you can create a simple CLI tool with the help of MiniCLI. You can read up more about minicli
on their GitHub page, or their documentation, also follow the creator Erika Heidi on twitter.
That's all for me folks, thank you for reading. 👋
Complete source available on my ping-cli
repository.
If you're having issues running your new CLI tool, make sure the permissions are turned on to use the file as an executable. On Linux system this can be done with chmod
, use the equivalent command for windows.
ping-cli ~ chmod +x bin/ping-cli
17