Monday, February 22, 2021

Slim PHP from the command line

Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs.
https://www.slimframework.com/

Slim can also be used to run scripts or commands from the command line, also known as the cli.

While libraries such as symfony/console or even slim/console can be used, they can be rather cumbersome ie heavy when all you want to do is run a command and have access to your existing code and services in Slim.

The basic flow of a Slim API call could be envisioned as:
    uri -> route -> action -> use query params

From the command line, there is no uri.
But maybe we could take the cli arguments and map that to a uri, and thus a route.

Perhaps such as
> php cli.php /cli/dostuff

Hmm, /cli/dostuff sure looks like a uri, right?
That can be mapped a Slim route nicely.

How about
> php cli.php /cli/dostuff?verbose=1&dryrun=1

Calling 
$request->getQueryParams() 
should return
verbose = 1
dryrun = 1

But what if we use arguments in a more common cli fashion?
> php cli.php /cli/dostuff verbose=1 dryrun=1

Hmm, verbose and dryrun still look like query string params
Would be nice if calling 
$request->getQueryParams() 
would return
verbose = 1
dryrun = 1


But what if we use arguments in a more typical cli fashion?
> php cli.php /cli/dostuff -verbose -dryrun

Hmm, verbose and dryrun do not look like query string params, but what if they were mapped to a key, such as argv?
Would be nice if calling 
$request->getQueryParams() 
would return
argv = [-verbose, -dryrun]

Well, for those changes and more, create a new file, named something such as runrundorun.php or maybe just cli.php. Place the new file below public, in the project root, at the same level as public, src, vendor, etc.

In cli.php, we will map the command line arguments to a uri, so Slim can test against routes, and then bootstrap Slim as normal.

cli.php
<?php

if (PHP_SAPI != 'cli') {
    exit("CLI only");
}

if (empty($argv) || count($argv) < 2) {
    exit("Missing route for CLI");
}

// remove calling script
array_shift($argv);

// get route + params from 1st argument
$uri = array_shift($argv);

// group routes by /cli/
if (strpos($uri, '/cli/') !== 0) {
    // handle os shell quirks
    // windows git bash
    if (strpos($uri, 'C:/Program Files/Git/cli/') === 0) {
        $uri = str_replace('C:/Program Files/Git/cli/', '/cli/', $uri);
    } else {
        echo "uri: " . $uri . PHP_EOL;
        exit("CLI Route must start with /cli/");
    }
}

// get any more arguments
if (!empty($argv)) {
    $additional = '';
    foreach ($argv as $arg) {
        if (strpos($arg, '=') !== false) {
            // r=1 d=10
            $additional .= '&' . $arg;
        } else {
            // normal args -r 10, store as argv
            $additional .= '&argv[]=' . $arg;
        }
    }
    if (strpos($uri, '?') === false) {
        $uri .= '?';
    }
    $uri .= $additional;
}
// set uri based on cli arguments
$_SERVER['REQUEST_URI'] = $uri;

// normal Slim app, routes_cli_*, ActionCli
(require __DIR__ . '/cfg/bootstrap.php');
GitHub gist


So now just add a route for /cli/dostuff and call your code!

> php [/app/]cli.php /uri/mapped/to/route?param1=v1 argv1=v1 argv0 

Extra:
Now, if you are running cli commands, you probably do not want errors returned as html.
You can add a customer error handler and return just text or maybe something with a little structure, such as json.  See the example in the official Slim docs to add a ErrorHandler to return json.
https://www.slimframework.com/docs/v4/objects/application.html

-End of Document-
Thanks for reading