Creating Phar files with Box

Creating Phar files with Box

After a long time, I am finally writing another blog post about PHP.
It is about the box tool, which I have just integrated into n98-magerun2.
The tool helps with handling phar files.
I primarily use it to create the n98-magerun2.phar file.
Previously, I used a phing task for this. However, it had to be adjusted to
handle the somewhat larger (> 6 MB) phar files.
I had tried box before in the past. Back then, box could not handle the large files either. That was several years ago, though (in the meantime, there was a version 2 and then a fork of the tool).
By now, creation works reliably.

Phar (PHP Archive) is a PHP extension that makes it possible to process programs or files from a compressed archive file, similar to Java Archive. Various compression methods are available for compressing the data itself, such as bzip2, gzip or ZIP.
https://en.wikipedia.org/wiki/PHAR_(file_format)

Installation

The easiest way is to download the box.phar file (yes, it also comes as an executable phar file itself).

You can do this as follows, for example:

curl -L -O https://github.com/box-project/box/releases/download/3.14.0/box.phar
chmod +x ./box.phar

That is all. You can now simply run the ./box.phar file and should see what the tool has to offer.

If everything works, you should see the following output in your console.

CLI output of box.phar

Phar "compile"

The main task of box is creating phar archives.
The compile command handles this. Phar files can vary quite a bit. For example, the standard supports different compression methods to keep the file small.
Phar files can be signed using various algorithms. And there are several other features too ...
All settings can conveniently be stored in a .box.json.dist file.

In the case of n98-magerun2, this looks like this, for example:

{
    "compression": "GZ",
    "algorithm": "SHA512",
    "datetime": "release-date",
    "files": [
        "config.yaml",
        "src/bootstrap.php"
    ],
    "force-autodiscovery": true,
    "directories": [
        "src",
        "res",
        "vendor/twig/twig/src"
    ],
    "git-commit-short": "git_commit_short",
    "stub": "build/phar/_cli_stub.php",
    "output": "n98-magerun2.phar"
}

As you can see, I compress my files with Gzip. When the phar file is created, all files in the phar are automatically compressed. The phar file can then only be executed in PHP versions that include the zlib library. That should be the case with any standard PHP installation, though. Box issues a warning during "compile" in this case.
For signing, we chose the SHA-512 algorithm for n98-magerun2.
Box also does several things automatically unless you disable them. For example, the composer.json found in almost every PHP project is automatically used as the basis for determining the files your application needs. An optimized autoloader with a classmap is then generated, and all dev dependencies are removed to keep the file as small as possible. In addition, Composer is given the --classmap-authoritative option. This ensures that only the classmap is used for autoloading.
That is also best practice for most applications. In n98-magerun2, however, we support adding commands through external modules, which then extend the autoloader at runtime. Since this option cannot be disabled in box, I had to disable it at runtime.
The Magerun code now contains the following line:

Config.php

$autoloader->setClassMapAuthoritative(false);

Additional files that are not loaded by the Composer autoloader can also be specified in the configuration.
In my case, these are the config.yaml file and the res directory.

Isolation

Box can also use the php-scoper tool to give the namespaces of dependent packages in the Phar file a scope prefix. All sorts of well-known PHP projects, such as phstan or PHPUnit, do this. The advantage is that conflicts are less likely.
I spent a long time testing this myself because my tool uses Symfony components while also bootstrapping Magento 2. Conflicts can easily arise when the same Symfony component is used twice in different versions.
The scoper works very well as long as a PSR-4 autoloader is involved. Autoloading via "file", on the other hand, causes problems.
In my case, I had to remove the scoper again, as otherwise all third-party modules would have become incompatible at once because they would also have needed to use the namespace prefix.
If you do not configure it, this prefix is even generated using a random value.
All php-scoper settings can then be made in a separate configuration file, scoper.inc.php.

What is inside the phar file?

Anyone wanting to look inside the generated Phar file will love the extract command.
It lets you quickly unpack any Phar file.

Here is an example that unpacks the n98-magerun2 Phar file into the extract1 directory.

./box.phar extract ./n98-magerun2.phar extract1

Comparing two phar files is also possible. There is a handy diff command for this.

./box.phar verify ./n98-magerun2.phar ./latest.phar

Timestamps

Whenever a Phar file is created, a timestamp with the current time is automatically written into it.
That is useful in some cases. However, we like every code version (GIT tag) to always produce the same Phar file with the same checksum.
The Composer project wants this too and has provided a small library to modify the timestamp afterwards.
We use this library too.
If you want to know more, you can take a closer look at build.sh.
Since the command manipulates the binary part of the Phar file, you should verify the Phar file again. If something goes wrong, the signature would be incorrect and the Phar file would not be executable. PHP would then issue an error message stating that the signature does not match.

Box provides the verify command for this.

./box.phar verify ./n98-magerun2.phar

If everything is correct, you will get the message The PHAR passed verification. followed by the SHA-512 signature).

What else is there?

That is far from everything Box offers. Files can also be manipulated by so-called compactors before being stored in the Phar. The PHP-Scoper described above is one such compactor.
It is also possible to register your own compactors.

Automatic replacement of placeholders is handy too. In my case, the GIT commit hash of the code version being used is automatically written into the code.

The n98-magerun2 version command then produces output like this, for example:

n98-magerun2 4.10.0-dev (commit: 61ce922) by netz98 GmbH

This tells you right away which commit was the basis of the Phar file.
That is particularly useful for the "develop version".

Box can also generate Docker containers. I have not tried that feature myself yet, though.

I hope my blog post has given you a little insight into the tool and that you can use it for your own projects.