How To Create a Composer Bin Command with Minicli

When creating and distributing PHP software with Composer, the PHP dependency manager, there are mainly two types of projects you can set up: a library and an application.

If your code is intended to be included in other applications so that classes and other components are available at code level, then you are most likely building a library. If your code is self-contained as something to be installed and executed as a standalone piece, then you're most likely building an application.

Sometimes, however, the line between these two types of projects is not very clear. You may want to provide executable code in addition to your library components. There is also the case when you build a tool that needs access to the application context, so that it can't be installed as a separate project and thus needs to be included in the bigger application with a composer require.

With Composer Bin Commands, you can include executable scripts within your project and they will be installed (via a symbolic link) on vendor/bin. Examples of projects using this feature include PestPHP and Laravel Sail, both using Composer bin commands to distribute executable scripts as application dependencies.

In this tutorial, we'll build a bin Composer command with Minicli, using the Advice Slip API. The code for this project is available as minicli/advice-slip on GitHub and on Packagist. The benefit of using Minicli for something like this is that you won't be bringing a bunch of new dependencies to the projects that will install your tool, the only dependency added with Minicli is minicli/minicli.

Requirements

You'll need a PHP development environment with:

Step 1: Setting Up a Minicli application

Start by requiring minicli/minicli in your existing application. In case you don't have an existing application and you would like to create a standalone tool with Minicli, create a new directory for your application and run Composer from there, so it will generate a new composer.json file for you.

composer require minicli/minicli

Next, create a new bin directory in the root of your application folder.

mkdir bin

Create a new file called minicli in this folder:

touch bin/minicli

This script will be the entry point of your CLI application that will be made available as Composer bin command.

Note About Composer autoload

When including the Composer autoload script, we'll need to first check if the application is being called from vendor/bin (as a required dependency) or if it's being called from the application folder itself (as a standalone project).

To do that, we'll verify the existence of a vendor/autoload.php file. If that file doesn't exist, it's likely that the application is being called as a dependency in the vendor folder, which make the autoload script available at a different location.

Step 2: Creating a bin/minicli Script

Open theminicli script file inside the bin folder, which is currently empty. The following code will implement the autoload verification and bootstrap a new Minicli application using the provided app_path as command location, similar to the basic demo from official docs.

#!/usr/bin/php
<?php

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

$root_app = dirname(__DIR__);

if (!is_file($root_app . '/vendor/autoload.php')) {
    $root_app = dirname(__DIR__, 4);
}

require $root_app . '/vendor/autoload.php';

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

$app = new App();
$input = new CommandCall($argv);

$app->registerCommand('help', function() use ($app) {
    $app->getPrinter()->info("minicli help");
});

$app->runCommand($input->getRawArgs());

Copy these contents to your minicli script and don't forget to save the file. Then, run the registered help command with:

php bin/minicli help

Once you confirm the script is working, you can create a new command to fetch contents from the API.

Step 3: Creating a Command To Obtain Advice Slips

The structure is ready and you can now go ahead and implement your command (s).

Here we'll build a command to fetch an advice slip from the free Advice Slip API.

This is a free API that doesn't require authentication, but if you want to build something more fun, check out the Giphy API (it requires registering an application and providing API keys). I won't cover it here for simplicity.

Our command will obtain a random advice, and will also accept a search=term parameter to search for specific advice. Replace the current content of your minicli script with the following code, which implements the new "advice" command:

#!/usr/bin/php
<?php

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

$root_app = dirname(__DIR__);

if (!is_file($root_app . '/vendor/autoload.php')) {
    $root_app = dirname(__DIR__, 4);
}

require $root_app . '/vendor/autoload.php';

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

$app = new App();
$app->setSignature('./bin/minicli advice');
$input = new CommandCall($argv);

$app->registerCommand('help', function() use ($app) {
    $app->getPrinter()->info("./bin/minicli advice");
});

$app->registerCommand('advice', function() use ($app, $input) {
    $query_url = "https://api.adviceslip.com/advice";

    if ($input->hasParam('search')) {
        $query_url = "https://api.adviceslip.com/advice/search/" . $input->getParam('search');
    }

    $result = file_get_contents($query_url);

    if ($result) {
        $content = json_decode($result, true);

        if (isset($content['total_results'])) {
            $search_results = $content['slips'];
            $advice = $search_results[array_rand($search_results)]['advice'];
        } else {
            if (isset($content['slip'])) {
                $advice = $content['slip']['advice'];
            }
        }

        $app->getPrinter()->newline();
        $app->getPrinter()->info($advice ?? "No results found.");
        $app->getPrinter()->newline();
    }

    return 0;
});

$app->runCommand($input->getRawArgs());

You can now try the command with:

php bin/minicli advice

And also:

php bin/minicli advice search=spiders

Step 4: Updating composer.json

Once your command is functional, you want to update your composer.json to include the Bin definition.

If this is a new application, you may also want to update the name and other information, since you'll need to submit your code to packagist.org in order to make it available to use via composer require.

This is how my composer.json file looks after the update (I set this package name to minicli/advice-slip, you should use a different name). Notice the bin entry including the location of the minicli script. You could include other scripts here if you wanted to.

{
  "name": "minicli/advice-slip",
  "description": "Demo - Advice Slip",
  "license": "MIT",
  "homepage": "https://github.com/minicli/advice-slip",
  "keywords": ["cli","command-line", "demo"],
  "require": {
    "minicli/minicli": "^2.0",
    "ext-json": "*"
  },
  "bin": [
    "bin/minicli"
  ]
}

If your project is already on Packagist, then you'll need to commit and push your changes to the remote repository so that they are picked up by Packagist. You may need to create a new release of your project in order to deliver the changes to existing users.

If your project is not yet published, follow the next step to set it up with Packagist.

Step 5: Submitting the Package to Packagist

To make the command available to anyone who would like to use it through Composer, your package / application must be submitted on Packagist.org. To do that, first you need to push your application to a public repository on GitHub. Once you have the repository URL, you can log in on Packagist and submit your new package:

Ideally, you should create at least one release in your GitHub repository (such as 0.1.0). If you don't have a release, Composer will complain about the minimum stability whenever a user requires this package on their own projects.

Once everything is set up, including the release, you can include you Minicli application in other projects with a regular composer require org/project:

composer require minicli/advice-slip
Using version ^0.1.0 for minicli/advice-slip
./composer.json has been updated
Running composer update minicli/advice-slip
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
  - Locking minicli/advice-slip (0.1.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Downloading minicli/advice-slip (0.1.0)
  - Installing minicli/advice-slip (0.1.0): Extracting archive
Generating autoload files
1 package you are using is looking for funding.
Use the `composer fund` command to find out more!

The command we built will then be available as:

./vendor/bin/minicli advice

Or:

php vendor/bin/minicli advice

Conclusion

In this tutorial, we've seen how to create a single command application with Minicli and how to turn it into a Composer Bin Command.

You can have access to this project's codebase at https://github.com/minicli/advice-slip .

29