Building minicli: Autoloading Command Namespaces
Tags: showdev, php, cli, beginners
Published: 2019-12-10 13:15:44

---
title: Building minicli: Autoloading Command Namespaces
published: true
description: Part 3 part of "Building minicli"
series: building-minicli
tags: showdev, php, cli, beginners
cover_image: https://thepracticaldev.s3.amazonaws.com/i/taltpgc7848dj5mi8axv.jpg
---

## Introduction

In the [previous episode](https://dev.to/erikaheidi/php-in-the-command-line-implementing-command-controllers-13lh) of the *Building Minicli* series, we have refactored the initial version of `minicli` to support commands defined in classes, with an architecture that uses Command Controllers.

In this new guide, we are going to implement **Command Namespaces** to organize Controllers and create a standard directory structure and naming conventions that can be leveraged for autoloading commands during application boot. This is a common approach used in web PHP frameworks to facilitate app development and reduce the amount of code necessary when bootstrapping a new application.

Our refactoring will go over the following steps:

1. Implement the new `CommandNamespace` class and refactor `CommandRegistry` accordingly.
2. Outsource command parsing to a new `CommandCall` class.
3. Update the `App` class to support the changes.
4. Update the abstract `CommandController` class and the concrete controllers in order to support the rest of the work.
5. Update and run the `minicli` script.

This is **Part 3** of the [Building Minicli](https://dev.to/erikaheidi/bootstrapping-a-cli-php-application-in-vanilla-php-4ee) series.

### Before Getting Started

You'll need `php-cli` and [Composer](https://getcomposer.org/) to follow this tutorial. You are strongly encouraged to start with the [first tutorial in this series](https://dev.to/erikaheidi/bootstrapping-a-cli-php-application-in-vanilla-php-4ee) and then move through the [second part](https://dev.to/erikaheidi/php-in-the-command-line-implementing-command-controllers-13lh) before following this guide.

In case you want a clean base copy of `minicli` to follow this tutorial, download version `0.1.2` of [erikaheidi/minicli](https://github.com/erikaheidi/minicli) to bootstrap your setup:

```
wget https://github.com/erikaheidi/minicli/archive/0.1.2.zip
unzip 0.1.2.zip
cd minicli
```

Then, run Composer to set up autoload. This won't install any package, because `minicli` has no dependencies.

```
composer dump-autoload
```

Run the application with:

```
php minicli
```

or

```
chmod +x minicli
./minicli
```

## 1. Implementing Command Namespaces


In the current application design, each command is an individual Controller. This is a nice way to keep commands organized and under a "contract", instead of having multiple commands all mixed together in a single Controller. This is how the demo controller `HelloController` looks like:

```php
<?php
namespace App\Command;
use Minicli\CommandController;
class HelloController extends CommandController
{
    public function run($argv)
    {
        $name = isset ($argv[2]) ? $argv[2] : "World";
        $this->getApp()->getPrinter()->display("Hello $name!!!");
    }
}
```

Our current `CommandRegistry` keeps a record of Command Controllers that are manually registered when bootstrapping the application. The `getCallable` method is responsible for figuring out what callable needs to be executed by the application:

```php
    public function getCallable($command_name)
    {
        $controller = $this->getController($command_name);
        if ($controller instanceof CommandController) {
            return [ $controller, 'run' ];
        }
        $command = $this->getCommand($command_name);
        if ($command === null) {
            throw new \Exception("Command \"$command_name\" not found.");
        }
        return $command;
    }
```


The problem with this approach is that it can get quite messy and confusing for users if you have many commands that are related to each other but with completely different names.

We want to implement common command entry points to keep related commands organized. Take the example of `docker`:

```
docker image [ import | build | history | ls | pull | prune ... ]
docker container [ build | info | kill | pause | rename | rm ... ]
```

The `image` command serves as a **common namespace** for all commands that deal with Docker images. The same is valid for `container` and other Docker commands.

We'll create a new `CommandNamespace` class that will keep a registry of application Controllers under a common name. We'll then modify the `CommandRegistry` class to work directly with Command Namespaces, and leave the work of registering and loading Controllers to these new entities. To expand the new design even further while simplifying application bootstrap, we will implement a standard directory structure that will facilitate autoloading Command Namespaces and Controllers into the application.

This is how our new architecture will look like:


```
app/Command
└── Command1
    ├── DefaultController.php
    ├── OtherController.php
    └── AnyController.php
└── Command2
    └── AnotherController.php
└── Command3
    └── RandomController.php
...
```
This is an expressive way of organizing commands while also facilitating automatic loading, which reduces the amount of code you have to write in order to include new commands into the application. Each Controller is a new subcommand under the designated Namespace. The name of each subcommand is obtained from the Controller class name, and the *DefaultController* is automatically used when no subcommand is provided in the command call. A directory structure like that would yield the following command "map":

```
./minicli command1 [ other | any ]
./minicli command2 another
./minicli command3 random
```

Let's start by creating the new `CommandNamespace` class.

### The `CommandNamespace` Class

Open a new file at `minicli/lib/CommandNamespace` using your code editor of choice.

```
lib/CommandNamespace.php
```

The `CommandNamespace` class will have a **name** and an array containing Controllers mapped into subcommands.

The `loadControllers` method will leverage the standard directory structure and naming conventions we defined to create a map of all Controllers under that namespace.

Copy the following code to your `CommandNamespace` class:

```php
<?php
namespace Minicli;

class CommandNamespace
{
    protected $name;

    protected $controllers = [];

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function loadControllers($commands_path)
    {
        foreach (glob($commands_path . '/' . $this->getName() . '/*Controller.php') as $controller_file) {
            $this->loadCommandMap($controller_file);
        }

        return $this->getControllers();
    }

    public function getControllers()
    {
        return $this->controllers;
    }

    public function getController($command_name)
    {
        return isset($this->controllers[$command_name]) ? $this->controllers[$command_name] : null;
    }

    protected function loadCommandMap($controller_file)
    {
        $filename = basename($controller_file);

        $controller_class = str_replace('.php', '', $filename);
        $command_name = strtolower(str_replace('Controller', '', $controller_class));
        $full_class_name = sprintf("App\\Command\\%s\\%s", $this->getName(), $controller_class);

        /** @var CommandController $controller */
        $controller = new $full_class_name();
        $this->controllers[$command_name] = $controller;
    }
}
```


Save the file when you're done.


### The `CommandRegistry` class

Open the existing `CommandRegistry` class on your editor:

```
lib/CommandRegistry.php
```

The `CommandRegistry` class will now outsource to Command Namespaces the work of registering and locating Controllers. Because the application implements a standard directory structure and naming conventions, we can  locate all Command Namespaces currently defined - this is done in the `autoloadNamespaces` method.

To keep compatibility with single commands registered via anonymous functions, which can be very handy and facilitate single-command apps, we will keep a `default_registry` array to register commands that way, too. Another important change is that we now have a `getCallableController` in addition to `getCallable`. The Application will decide which one to use, and when.

This is how the updated `CommandRegistry` class looks like:

```php
<?php

namespace Minicli;

class CommandRegistry
{
    protected $commands_path;

    protected $namespaces = [];

    protected $default_registry = [];

    public function __construct($commands_path)
    {
        $this->commands_path = $commands_path;
        $this->autoloadNamespaces();
    }

    public function autoloadNamespaces()
    {
        foreach (glob($this->getCommandsPath() . '/*', GLOB_ONLYDIR) as $namespace_path) {
            $this->registerNamespace(basename($namespace_path));
        }
    }

    public function registerNamespace($command_namespace)
    {
        $namespace = new CommandNamespace($command_namespace);
        $namespace->loadControllers($this->getCommandsPath());
        $this->namespaces[strtolower($command_namespace)] = $namespace;
    }

    public function getNamespace($command)
    {
        return isset($this->namespaces[$command]) ? $this->namespaces[$command] : null;
    }

    public function getCommandsPath()
    {
        return $this->commands_path;
    }

    public function registerCommand($name, $callable)
    {
        $this->default_registry[$name] = $callable;
    }

    public function getCommand($command)
    {
        return isset($this->default_registry[$command]) ? $this->default_registry[$command] : null;
    }

    public function getCallableController($command, $subcommand = null)
    {
        $namespace = $this->getNamespace($command);

        if ($namespace !== null) {
            return $namespace->getController($subcommand);
        }

        return null;
    }

    public function getCallable($command)
    {
        $single_command = $this->getCommand($command);
        if ($single_command === null) {
            throw new \Exception(sprintf("Command \"%s\" not found.", $command));
        }

        return $single_command;
    }
}
```

Save the file when you're done updating its content.


## 2. Outsourcing Command Parsing to the `CommandCall` Class

To facilitate parsing commands, subcommands and other parameters, we'll  create a **new** class named `CommandCall`.

Open a new file:

```
lib/CommandCall.php
```

The `CommandCall` class works as a simple abstraction to the command call and provides a way to parse named parameters, such as `user=name`.
It is handy because it keeps these values in a typed object that gives us more control over what is forwarded to the commands controllers. It can be expanded in the future for more complex parsing.

### The `CommandCall` class

Copy the following contents to your new `CommandCall` class:

```php
<?php

namespace Minicli;


class CommandCall
{
    public $command;

    public $subcommand;

    public $args = [];

    public $params = [];

    public function __construct(array $argv)
    {
        $this->args = $argv;
        $this->command = isset($argv[1]) ? $argv[1] : null;
        $this->subcommand = isset($argv[2]) ? $argv[2] : 'default';

        $this->loadParams($argv);
    }

    protected function loadParams(array $args)
    {
        foreach ($args as $arg) {
            $pair = explode('=', $arg);
            if (count($pair) == 2) {
                $this->params[$pair[0]] = $pair[1];
            }
        }
    }

    public function hasParam($param)
    {
        return isset($this->params[$param]);
    }


    public function getParam($param)
    {
        return $this->hasParam($param) ? $this->params[$param] : null;
    }
}
```

Save the file when you're done.


## 3. Updating the `App` Class

To accommodate the changes in the `CommandRegistry`, we'll need to also update the `App` class. Open the file with:

```
lib/App.php
```

The `runCommand` method now will first call the `getCallableController` method in the `CommandRegistry` class; if a controller is found, it will execute three distinct methods in this order: `boot`, `run`, and `teardown`. If a controller can't be found, it probably means that namespace doesn't exist, and it's actually a single command. We'll try to find a single command and run its respective callable, otherwise the app will exit with an error.

There's also a new `app_signature` property that lets us customize a one-liner to tell people how to use the app.

###  The `App` Class

Following, the contents of the updated `App` class:

```php
<?php

namespace Minicli;

class App
{
    protected $printer;

    protected $command_registry;

    protected $app_signature;

    public function __construct()
    {
        $this->printer = new CliPrinter();
        $this->command_registry = new CommandRegistry(__DIR__ . '/../app/Command');
    }

    public function getPrinter()
    {
        return $this->printer;
    }

    public function getSignature()
    {
        return $this->app_signature;
    }

    public function printSignature()
    {
        $this->getPrinter()->display(sprintf("usage: %s", $this->getSignature()));
    }

    public function setSignature($app_signature)
    {
        $this->app_signature = $app_signature;
    }


    public function registerCommand($name, $callable)
    {
        $this->command_registry->registerCommand($name, $callable);
    }

    public function runCommand(array $argv = [])
    {
        $input = new CommandCall($argv);

        if (count($input->args) < 2) {
            $this->printSignature();
            exit;
        }

        $controller = $this->command_registry->getCallableController($input->command, $input->subcommand);

        if ($controller instanceof CommandController) {
            $controller->boot($this);
            $controller->run($input);
            $controller->teardown();
            exit;
        }

        $this->runSingle($input);
    }

    protected function runSingle(CommandCall $input)
    {
        try {
            $callable = $this->command_registry->getCallable($input->command);
            call_user_func($callable, $input);
        } catch (\Exception $e) {
            $this->getPrinter()->display("ERROR: " . $e->getMessage());
            $this->printSignature();
            exit;
        }
    }

}
```

Save the file when you're done updating its content.


## 4. Refactoring Abstract and Concrete Command Controllers

Now it's time to update the abstract class that is inherited by our Controllers, in order to include a few handy methods to retrieve parameters and to work as shortcut for accessing Application components such as the Printer.

Open the `CommandController` class:

```
lib/CommandController.php
```

Under the new "contract" , Controllers will have to implement a method named `handle`. Externally, nothing will change: `run` is still the public method that will be executed from the `App` class. The change is to enable intercepting the `CommandCall` data and make it available for all protected controller methods.

The `teardown` method is optional and for that reason is empty, so it can be overwritten in children controllers.

### The `CommandController` Class

Following, the contents of the updated `CommandController` abstract class:

```php
<?php

namespace Minicli;

abstract class CommandController
{
    protected $app;

    protected $input;

    abstract public function handle();

    public function boot(App $app)
    {
        $this->app = $app;
    }

    public function run(CommandCall $input)
    {
        $this->input = $input;
        $this->handle();
    }

    public function teardown()
    {
        //
    }

    protected function getArgs()
    {
        return $this->input->args;
    }

    protected function getParams()
    {
        return $this->input->params;
    }

    protected function hasParam($param)
    {
        return $this->input->hasParam($param);
    }

    protected function getParam($param)
    {
        return $this->input->getParam($param);
    }

    protected function getApp()
    {
        return $this->app;
    }

    protected function getPrinter()
    {
        return $this->getApp()->getPrinter();
    }
}
```

Save the file when you're done updating its content.

We'll need to move our current `hello` command to follow the designated directory structure:

```
cd minicli
mkdir app/Command/Hello
```

Because we now use a `command subcommand` nomenclature, we'll have to create a subcommand inside the `hello` namespace. To create a subcommand named `name`, you should use `NameController` as class name.

Let's copy the `HelloController` to the `hello` namespace and rename it to `NameController.php`.

```
mv app/Command/HelloController.php app/Command/Hello/NameController.php
```

Now we need to update this file to rename the class and implement the `handle` method, removing the old `run` implementation. Open file with:

```
app/Hello/NameController.php
```


### The `NameController` Class

Folowing, the contents of the updated `NameController` class, former `HelloController`.

```php
<?php

namespace App\Command\Hello;

use Minicli\CommandController;

class NameController extends CommandController
{
    public function handle()
    {
        $name = $this->hasParam('user') ? $this->getParam('user') : 'World';

        $this->getPrinter()->display(sprintf("Hello, %s!", $name));
    }
}
```

Save the file when you're done updating its content.

## 5. Updating and running `minicli`

The last thing we need to do is update the `minicli` script to reflect all the changes. We'll set a signature and register a single `help` command to test out our named parameters feature.

Open the file with:

```
cd minicli
nano minicli
```

### The `minicli` Script

Replace the current contents of your `minicli` script with the following code:

```php
#!/usr/bin/php
<?php

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

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

use Minicli\App;
use Minicli\CommandCall;

$app = new App();
$app->setSignature("minicli hello name [ user=name ]");

$app->registerCommand("help", function(CommandCall $call) use ($app) {
    $app->printSignature();
    print_r($call->params);
});

$app->runCommand($argv);

```

Save the file when you're done.

## 8. Testing the Changes

Now you can execute the `hello name` command with:

```
./minicli hello name
```

or

```
./minicli hello name user=erika
```

To test named parameters, run:

```
./minicli help name=value name2=value2
```

You'll get output like this:

```
usage: minicli hello name [ user=name ]

Array
(
    [name] => value
    [name2] => value2
)
```

## Conclusion

In this guide, we refactored our `minicli` micro framework to support a better organizational command structure and to enable autoloading command controllers.

You can find the full refactored code in the **0.1.3** release of `minicli`: https://github.com/erikaheidi/minicli/releases/tag/0.1.3.

In the next and final part of this series, we'll wrap up everything to release `minicli 1.0`.