Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
September 20, 2019 02:39 pm GMT

Bootstrapping a CLI PHP application in Vanilla PHP

Introduction

PHP is well known for its popularity with web applications and CMSs, but what many people don't know is that PHP is also a great language for building command line applications that don't require a web server. Its ease of use and familiar syntax make it a low-barrier language for complimentary tools and little applications that might communicate with APIs or execute scheduled tasks via Crontab, for instance, but without the need to be exposed to external users.

Certainly, you can build a one-file PHP script to attend your needs and that might work reasonably well for tiny things; but that makes it very hard for maintaining, extending or reusing that code in the future. The same principles of web development can be applied when building command-line applications, except that we are not working with a front end anymore - yay! Additionally, the application is not accessible to outside users, which adds security and creates more room for experimentation.

I'm a bit sick of web applications and the complexity that is built around front-end lately, so toying around with PHP in the command line was very refreshing to me personally. In this post/series, we'll build together a minimalist / dependency-free CLI AppKit (think a tiny framework) - minicli - that can be used as base for your experimental CLI apps in PHP.

PS.: if all you need is a git clone, please go here.

This is part 1 of the series: Building minicli.

Prerequisites

In order to follow this tutorial, you'll need php-cli installed on your local machine or development server, and Composer for generating the autoload files.

1. Setting Up Directory Structure & Entry Point

Let's start by creating the main project directory:

mkdir miniclicd minicli

Next, we'll create the entry point for our CLI application. This is the equivalent of an index.php file on modern PHP web apps, where a single entry point redirect requests to the relevant Controllers. However, since our application is CLI only, we will use a different file name and include some safeguards to not allow execution from a web server.

Open a new file named minicli using your favorite text editor:

vim minicli

You will notice that we didn't include a .php extension here. Because we are running this script on the command line, we can include a special descriptor to tell your shell program that we're using PHP to execute this script.

#!/usr/bin/php<?phpif (php_sapi_name() !== 'cli') {    exit;}echo "Hello World\n";

The first line is the application shebang. It tells the shell that is running this script to use /usr/bin/php as interpreter for that code.

Make the script executable with chmod:

chmod +x minicli

Now you can run the application with:

./minicli

You should see a Hello World as output.

2. Setting Up Source Dirs and Autoload

To facilitate reusing this framework for several applications, we'll create two source directories:

  • app: this namespace will be reserved for Application-specific models and controllers.
  • lib: this namespace will be used by the core framework classes, which can be reused throughout various applications.

Create both directories with:

mkdir appmkdir lib

Now let's create a composer.json file to set up autoload. This will help us better organize our application while using classes and other object oriented resources from PHP.

Create a new composer.json file in your text editor and include the following content:

{  "autoload": {    "psr-4": {      "Minicli\\": "lib/",      "App\\": "app/"    }  }}

After saving and closing the file, run the following command to set up autoload files:

composer dump-autoload

To test that the autoload is working as expected, we'll create our first class. This class will represent the Application object, responsible for handling command execution. We'll keep it simple and name it App.

Create a new App.php file inside your lib folder, using your text editor of choice:

vim lib/App.php

The App class implements a runCommand method replacing the "Hello World" code we had previously set up in our minicli executable.
We will modify this method later so that it can handle several commands. For now, it will output a "Hello $name" text using a parameter passed along when executing the script; if no parameter is passed, it will use world as default value for the $name variable.

Insert the following content in your App.php file, saving and closing the file when you're finished:

<?phpnamespace Minicli;class App{    public function runCommand(array $argv)    {        $name = "World";        if (isset($argv[1])) {            $name = $argv[1];        }        echo "Hello $name!!!\n";    }}

Now go to your minicli script and replace the current content with the following code, which we'll explain in a minute:

#!/usr/bin/php<?phpif (php_sapi_name() !== 'cli') {    exit;}require __DIR__ . '/vendor/autoload.php';use Minicli\App;$app = new App();$app->runCommand($argv);

Here, we are requiring the auto-generated autoload.php file in order to automatically include class files when creating new objects. After creating the App object, we call the runCommand method, passing along the global $argv variable that contains all parameters used when running that script. The $argv variable is an array where the first position (0) is the name of the script, and the subsequent positions are occupied by extra parameters passed to the command call. This is a predefined variable available in PHP scripts executed from the command line.

Now, to test that everything works as expected, run:

./minicli your-name

And you should see the following output:

Hello your-name!!!

Now, if you don't pass any additional parameters to the script, it should print:

Hello World!!!

3. Creating an Output Helper

Because the command line interface is text-only, sometimes it can be hard to identify errors or alert messages from an application, or to format data in a way that is more human-readable. We'll outsource some of these tasks to a helper class that will handle output to the terminal.

Create a new class inside the lib folder using your text editor of choice:

vim lib/CliPrinter.php

The following class defines three public methods: a basic out method to output a message; a newline method to print a new line; and a display method that combines those two in order to give emphasis to a text, wrapping it with new lines. We'll expand this class later to include more formatting options.

<?phpnamespace Minicli;class CliPrinter{    public function out($message)    {        echo $message;    }    public function newline()    {        $this->out("\n");    }    public function display($message)    {        $this->newline();        $this->out($message);        $this->newline();        $this->newline();    }}

Now let's update the App class to use the CliPrinter helper class. We will create a property named $printer that will reference a CliPrinter object. The object is created in the App constructor method. We'll then create a getPrinter method and use it in the runCommand method to display our message, instead of using echo directly:

<?phpnamespace Minicli;class App{    protected $printer;    public function __construct()    {        $this->printer = new CliPrinter();    }    public function getPrinter()    {        return $this->printer;    }    public function runCommand($argv)    {        $name = "World";        if (isset($argv[1])) {            $name = $argv[1];        }        $this->getPrinter()->display("Hello $name!!!");    }}

Now run the application again with:

./minicli your_name

You should get output like this (with newlines surrounding the message):

Hello your_name!!!

In the next step, we'll move the command logic outside the App class, making it easier to include new commands whenever you need.

4. Creating a Command Registry

We'll now refactor the App class to handle multiple commands through a generic runCommand method and a Command Registry. New commands will be registered much like routes are typically defined in some popular PHP web frameworks.

The updated App class will now include a new property, an array named command_registry. The method registerCommand will use this variable to store the application commands as anonymous functions identified by a name.

The runCommand method now checks if $argv[1] is set to a registered command name. If no command is set, it will try to execute a help command by default. If no valid command is found, it will print an error message.

This is how the updated App.php class looks like after these changes. Replace the current content of your App.php file with the following code:

<?phpnamespace Minicli;class App{    protected $printer;    protected $registry = [];    public function __construct()    {        $this->printer = new CliPrinter();    }    public function getPrinter()    {        return $this->printer;    }    public function registerCommand($name, $callable)    {        $this->registry[$name] = $callable;    }    public function getCommand($command)    {        return isset($this->registry[$command]) ? $this->registry[$command] : null;    }    public function runCommand(array $argv = [])    {        $command_name = "help";        if (isset($argv[1])) {            $command_name = $argv[1];        }        $command = $this->getCommand($command_name);        if ($command === null) {            $this->getPrinter()->display("ERROR: Command \"$command_name\" not found.");            exit;        }        call_user_func($command, $argv);    }}

Next, we'll update our minicli script and register two commands: hello and help. These will be registered as anonymous functions within our App object, using the newly created registerCommand method.

Copy the updated minicli script and update your file:

#!/usr/bin/php<?phpif (php_sapi_name() !== 'cli') {    exit;}require __DIR__ . '/vendor/autoload.php';use Minicli\App;$app = new App();$app->registerCommand('hello', function (array $argv) use ($app) {    $name = isset ($argv[2]) ? $argv[2] : "World";    $app->getPrinter()->display("Hello $name!!!");});$app->registerCommand('help', function (array $argv) use ($app) {    $app->getPrinter()->display("usage: minicli hello [ your-name ]");});$app->runCommand($argv);

Now your application has two working commands: help and hello. To test it out, run:

./minicli help

This will print:

usage: minicli hello [ your-name ]

Now test the hello command with:

./minicli hello your_name
Hello your_name!!!

You have now a working CLI app using a minimalist structure that will serve as base to implement more commands and features.

This is how your directory structure will look like at this point:

. app lib  App.php  CliPrinter.php vendor  composer  autoload.php composer.json minicli

In the next part of this series, we'll refactor minicli to use Command Controllers, moving the command logic to dedicated classes inside the application-specific namespace. See you next time!


Original Link: https://dev.to/erikaheidi/bootstrapping-a-cli-php-application-in-vanilla-php-4ee

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To