Turning a PHP CLI app into a Phar archive

You’ve probably come across command line tools distributed as Phar archives before. The obvious example is Composer. Phar archives allow users to neatly use PHP apps without going through the pains of setting up someone else’s project.

We’re going to use the example Symfony Console component entry point from my previous post about making CLI apps with Slim Framework.

Here’s that file again:

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

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

use Symfony\Component\Console\Application;

$application = new Application();

$service = $container->get(MyService::class);
$application->add(new GenerateAdminCommand($service));

$application->run();

I’ve called this file my-app, and it’s in a bin folder off the root directory.
The overall file structure for this app is as follows:
.
├── bin
│   └── my-app
├── build
├── create-phar.php
├── logs
├── src
├── tests
└── vendor

The create-phar.php file is what we’ll be using to package the app as a Phar, it looks like this:

<?php
$buildRoot = __DIR__;

$phar = new Phar($buildRoot . '/build/my-app.phar', 0, 'my-app.phar');

$include = '/^(?=(.*src|.*bin|.*vendor))(.*)$/i';

$phar->buildFromDirectory($buildRoot, $include);
$phar->setStub("#!/usr/bin/env php\n" . $phar->createDefaultStub("bin/davework"));

Lets walk through what this is doing:

$phar = new Phar($buildRoot . '/build/my-app.phar', 0, 'my-app.phar');

This line instantiates a new Phar object. The first parameter is the filename, it must contain the .phar extension. The second parameter are flags for the RecursiveDirectoryIterator that the Phar class extends. The third parameter is an alias for the Phar, use the filename for your Phar without the directory.

$phar->buildFromDirectory($buildRoot, $include);

This line tells the Phar class what to put in the archive. We can only choose one directory here, and most modern code will have at least src/ and vendor/ folders. If we don’t want to add the entire project to the archive (and we don’t want to include tests and logs among other things) then we need to pass a regex to buildFromDirectory() telling the method which folders and files to include. I’ve included a regex to select certain folders:

$include = '/^(?=(.*src|.*bin|.*vendor))(.*)$/i';

Finally, we need to include a stub which handles the default activity for the archive. We use the console entry point for this.

Run create-file.php and we’ll have a new archive file in the build/ directory.

Leave a Reply

Your email address will not be published. Required fields are marked *