How to pack a command line php application for distribution
Recently I wanted to create a small php command line application for managing my BitBucket repositories.
Making a cli executable with one file is simple.
But usually you need more than that. You want to use composer packages, to provide a good interface for user, to be able to parse arguments and options and to print a help messages.
That means you will have many files - your source code plus packages or complete framework.
To have the whole project build into a single executable file (phar - PHP Archive) you need to pack it somehow and the solution is phar-composer.
Instructions
Phar-composer (which itself is a phar) can pack any composer managed project. So you need to have a composer.json file in your root directory.
Make a new directory cli-app
, initialize composer project with composer init
and fill in the questions to generate new composer.json
.
mkdir cli-app
cd cli-app
composer init
After completing the process you should have a composer.json
with content like:
{
"name": "dam1r89/cli-app",
"authors": [
{
"name": "Damir Miladinov",
"email": "damir89@gmail.com"
}
],
"require": {}
}
Run a composer install
Even if you are not using additional packages you need to run
composer install
or phar-composer build will complain
Before packing a project we need to specify what will be our main php file. What Phar-composer is doing is reading composer's binary property bin
defined in the project's composer.json
file. In simple words the bin
key is used as a pointer to the main file for starting whole application. So we need to add something like this:
{
"name": "dam1r89/cli-app",
"authors": [
{
"name": "Damir Miladinov",
"email": "damir89@gmail.com"
}
],
"require": {},
+ "bin": ["bin/main"]
}
For this example main file will be (even it doesn't have extension) a php file.
Make the bin directory
mkdir bin
And create bin/main
file.
<?php
// To use composer's autoload
require_once '../vendor/autoload.php';
// $argv contains command line arguments
print "From packed application: " . $argv[1] . "\n";
Phar-composer will set file permission based on the permissions of this file. So we want to make it executable:
chmod +x bin/main
Download phar-composer in to the project directory. Make a new directory dist
(distributable) which will hold the project compressed into a phar executable.
mkdir dist
Finally run the build with
php phar-composer.phar build . dist
The dot means build the project from current directory (needs to have composer.json
), and dist is where file will be saved. In dist
folder you will have a phar file with name of your project.
To test it run php dist/cli-app.phar "Woohoo"
. You should see From packed application: Woohoo
.
Now you can distribute cli-app.phar
as a single file executable application.
System wide access
If you want to make application globally available you can do something like
cp dist/cli-app.phar /usr/local/bin/cli-app
And then use application like cli-app "Hello"
.
This previous step can be done automatically with phar-composer install
command.
php phar-composer.phar install . cli-app
Last parameter can be used to specify the command name.
More complex application
When building a real world application you will want to have a separate directory for your source code and only initialize commands through the main file. For example you would have app
directory and autoload it with composer:
Example
{
"authors": [
{
"email": "damir89@gmail.com",
"name": "Damir Miladinov"
}
],
"autoload": {
"psr-4": {
"App\\": "app/"
}
},
"bin": [
"bin/main"
],
"name": "dam1r89/cli-app",
"require": {}
}
app/Application.php
<?php
namespace App;
class Application{
public function say($something){
print "From packed application: $something\n";
}
}
bin/main
<?php
require_once '../vendor/autoload.php';
$app = new App\Application();
$app->say($argv[1]);
Libraries for interacting with user
Previous part explained how to pack the application. What is next is to parse arguments and options and print output. All that can be heavy task unless using a library.
My suggestion is
- Symfony Console or
- Laravel Console which is basically a Symfony Console wrapper that provides fluent command definitions like this:
command {arg1} {arg2} {--option=}
.