DEV Community

Cover image for Introducing minicli: a microframework for CLI-centric PHP applications
Erika Heidi
Erika Heidi

Posted on

Introducing minicli: a microframework for CLI-centric PHP applications

In the previous posts of the Building Minicli series, we demonstrated the steps to bootstrap a dependency-free microframework for CLI-only applications in PHP.

Minicli was created as an educational experiment and also as a lightweight base that I could reuse for my personal projects. I like to think of it as the most basic unit I was able to put together, on top of which I could build my toys.

It's been months since I shared my latest post on this series, and I was reluctant to share what I've been working on because it always feels like an incomplete work. But will it ever be complete (or feel like so)? Probably not. Minicli is open source since day 0, and although I never intended to turn it into a mainstream project, I also think it can help people who are interested in building simple things in the command line without the overkill of dozens of external requirements.

So I'd like to officially introduce you to Minicli, a highly experimental dependency-free microframework for CLI-centric PHP apps.

While I don't advocate for reinventing all the wheels in an application, I believe there should be a starting point that doesn't require 10+ different libraries for basic parsing and routing of commands. From there, you should be able to consciously choose what you'll be depending on, in terms of external libraries. Minicli is what I came up with in order to solve this situation.

What I've built with Minicli so far:

Dolphin, a command-line tool for managing DigitalOcean droplets from the command line.
Dolphin screenshot

My website, which is a static-content CMS pulling from my DEV posts. I'm open sourcing this as a separate project called Librarian (WIP).

Screenshot of eheidi.dev

In this post, you will learn how to create a simple CLI application in PHP using Minicli.

Creating a Project

You'll need php-cli and Composer to get started.

Create a new project with:

composer create-project --prefer-dist minicli/application myapp
Enter fullscreen mode Exit fullscreen mode

Once the installation is finished, you can run minicli with:

cd myapp
./minicli
Enter fullscreen mode Exit fullscreen mode

This will show you the default app signature.

The help command that comes with minicli, defined in app/Command/Help/DefaultController.php, auto-generates a tree of available commands:

./minicli help
Enter fullscreen mode Exit fullscreen mode
Available Commands

help
└──test

Enter fullscreen mode Exit fullscreen mode

The help test command, defined in app/Command/Help/TestController.php, shows an echo test of parameters:

./minicli help test user=erika name=value
Enter fullscreen mode Exit fullscreen mode
Hello, erika!

Array
(
    [user] => erika
    [name] => value
)
Enter fullscreen mode Exit fullscreen mode

Creating your First Command

The simplest way to create a command is to edit the minicli script and define a new command as an anonymous function within the Application via registerCommand:

#!/usr/bin/php
<?php

if (php_sapi_name() !== 'cli') {
    exit;
}

require __DIR__ . '/vendor/autoload.php';

use Minicli\App;
use Minicli\Command\CommandCall;

$app = new App();
$app->setSignature('./minicli mycommand');

$app->registerCommand('mycommand', function(CommandCall $input) {
    echo "My Command!";

    var_dump($input);
});

$app->runCommand($argv);
Enter fullscreen mode Exit fullscreen mode

You could then execute the new command with:

./minicli mycommand
Enter fullscreen mode Exit fullscreen mode

Using Command Controllers

To organize your commands into controllers, you'll need to use Command Namespaces.

Let's say you want to create a command named hello. You should start by creating a new directory under the app/Commands folder:

mkdir app/Commands/Hello
Enter fullscreen mode Exit fullscreen mode

Now Hello is your Command Namespace. Inside that directory, you'll need to create at least one Command Controller. You can start with the DefaultController, which will be called by default when no subcommand is provided.

This is how this DefaultController class could look like:

<?php

namespace App\Command\Hello;

use Minicli\Command\CommandController;

class DefaultController extends CommandController
{
    public function handle()
    {       
        $this->getPrinter()->display("Hello World!");
    }
}
Enter fullscreen mode Exit fullscreen mode

This command would be available as:

./minicli hello
Enter fullscreen mode Exit fullscreen mode

Becase a subcommand was not provided, it is inferred that you want to execute the default command. This command can also be invoked as:

./minicli hello default
Enter fullscreen mode Exit fullscreen mode

Any other Command Controller placed inside the Hello namespace will be available in a similar way. For instance, let's say you want to create a new subcommand like hello caps.

You would then create a new Command Controller named CapsController:

<?php

namespace App\Command\Hello;

use Minicli\Command\CommandController;

class CapsController extends CommandController
{
    public function handle()
    {       
        $this->getPrinter()->display("HELLO WORLD!");
    }
}
Enter fullscreen mode Exit fullscreen mode

And this new command would be available as:

./minicli hello caps
Enter fullscreen mode Exit fullscreen mode

Working with Parameters

Minicli uses a few conventions for command call arguments:

  • Args / Arguments: Parsed arguments - anything that comes from $argv that is not a key=value and not a --flag.
  • Params / Parameters: Key-value pairs such as user=erika
  • Flags: single arguments prefixed with -- such as --update

The parent CommandController class exposes a few handy methods to work with the command call parameters.
For instance, let's say you want to update the previous hello command to use an optional parameter to tell the name of the person that will be greeted.

<?php

namespace App\Command\Hello;

use Minicli\Command\CommandController;
use Minicli\Input;

class HelloController extends CommandController
{
    public function handle()
    {       
        $name = $this->hasParam('user') ? $this->getParam('user') : 'World';
        $this->getPrinter()->display(sprintf("Hello, %s!", $name));
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, to use the custom version of the command, you'll need to run:

./minicli hello user=erika 
Enter fullscreen mode Exit fullscreen mode

And you'll get the output:

Hello, erika!
Enter fullscreen mode Exit fullscreen mode

CommandCall Class Methods

  • hasParam(string $key) : bool - Returns true if a parameter exists.
  • getParam(string $key) : string - Returns a parameter, or null if its non existent.
  • hasFlag(string $key) : bool - Returns whether or not a flag was passed along in the command call.

Printing Output

The CliPrinter class has shortcut methods to print messages with various colors and styles.
It comes with two bundled themes: regular and unicorn. This is set up within the App bootstrap config array, and by default it's configured to use the regular theme.

    public function handle()
    {       
        $this->getPrinter()->info("Starting Minicli...");
        if (!$this->hasParam('message')) {
            $this->getPrinter()->error("Error: you must provide a message.");
            exit;
        }

        $this->getPrinter()->success($this->getParam('message'));
    }
Enter fullscreen mode Exit fullscreen mode

CliPrinter Class Methods

  • display(string $message) : void - Displays a message wrapped in new lines.
  • error(string $message) : void - Displays an error message wrapped in new lines, using the current theme colors.
  • success(string $message) : void - Displays a success message wrapped in new lines, using the current theme colors.
  • info(string $message) : void - Displays an info message wrapped in new lines, using the current theme colors.
  • newline() : void - Prints a new line.
  • format(string $message, string $style="default") : string - Returns a formatted string with the desired style.
  • out(string $message) : void - Prints a message.

Wrapping Up

Minicli is a work in progress, but you can already use it as a minimalist base on top of which you can build fun toy projects and/or helpful command line tools, like Dolphin.

Here's a few ideas I'd like to build with Minicli but haven't had the time so far (and I definitely wouldn't mind if anyone build these):

  • a text-based rpg game
  • a Twitter bot
  • a tool for finding your Twitter mutuals
  • a cli-based quizz game

If you'd like to give Minicli a try, check the documentation for more details and don't hesitate to leave a comment if you have any questions :)

Top comments (4)

Collapse
 
syntaxseed profile image
SyntaxSeed (Sherri W)

Tip:
The sub-commands can extend the DefaultController for the command, over-writing the handle() method, but inheriting the other methods from the DefaultController, in case for example, you want a method in the DefaultController which handles flags, but don't want to re-define that in all the sub-commands.

Collapse
 
erikaheidi profile image
Erika Heidi

That's a great idea Sherri, thanks for sharing! Updates coming soon 😊

Collapse
 
miss_my_pals profile image
Gbenga

While using the framework, I run into this problem. Have checked everything and it is in the correct order

Fatal error: Uncaught Error: Class '\CreateController' not found in C:\Users\use
r pc\Desktop\myapp\vendor\minicli\minicli\lib\Command\CommandNamespace.php:71
Stack trace:

0 C:\Users\user pc\Desktop\myapp\vendor\minicli\minicli\lib\Command\CommandName

space.php(36): Minicli\Command\CommandNamespace->loadCommandMap('C:\Users\user
p...')

1 C:\Users\user pc\Desktop\myapp\vendor\minicli\minicli\lib\Command\CommandRegi

stry.php(51): Minicli\Command\CommandNamespace->loadControllers('C:\Users\user
p...')

2 C:\Users\user pc\Desktop\myapp\vendor\minicli\minicli\lib\Command\CommandRegi

stry.php(40): Minicli\Command\CommandRegistry->registerNamespace('Controller')

3 C:\Users\user pc\Desktop\myapp\vendor\minicli\minicli\lib\Command\CommandRegi

stry.php(31): Minicli\Command\CommandRegistry->autoloadNamespaces()

4 C:\Users\user pc\Desktop\myapp\vendor\minicli\minicli\lib\App.php(66): Minicl

i\Command\CommandRegistry->load(Object(Minicli\App))

5 C:\Users\user pc\Desktop\myapp\vendor\minicli\minicli\lib\App.php(46): Minicl

i\App->loa in C:\Users\user pc\Desktop\myapp\vendor\minicli\minicli\lib\Command\
CommandNamespace.php on line 71

Collapse
 
jordonr profile image
Jordon Replogle

Nicely done. Reminded me of the simple framework (if you can even call it that) I made for running cli tasks created in PHP.

GitHub logo jordonr / php-tc

PHP Task Command

php-tc

PHP Task Command

Basic layout or framework to make php tasks that are to be run from the command-line.

I have included a few packages that were used for my needs so make sure to edit the composer.json as needed and run composer.

Wrote this out of a random need to check and update a SOAP API every 5 minutes. I tried Mono, Ruby, Perl and Python but only PHP generated the correct SOAP calls for this specific API.