PHP
Article

Boxing up your Apps as Phars Quickly and Easily with Box

By Bruno Skvorc

In this tutorial, we’ll use Box to package a PHP application into a Phar, in order to make it easily distributable and globally installable via Composer.

Box

On what?

We need a project for this, and packaging up yet another “Hello World” script would be pointless. We’ll use Webiny’s FolderBuilder, a tool which you can use to plan out the folder structure for your project, interactively drag and drop files and folders, and even export and share these structures with others.

Webiny FolderBuilder Screenshot

But that’s almost entirely a JavaScript tool, why would we be using Phars for it? Yes, that GUI is JS, but if you look at the repo you’ll notice a PHP script inside which you can run on any folder and get the required JSON generated. This means you can easily generate FolderBuilder compatible structures from existing projects, too, which is super handy for planning a project’s restructuring.

While it’s very simple to just download and run the script from any folder you’d like to parse into JSON, why not make the whole project Phar-distributable and allow for something like:

composer global require webiny/folderbuilder
folderbuilder . > structure.json

Boxstrapping

Let’s bootstrap a Box project. First, we need to install Box if it’s not already present on our system. I am, of course, using Homestead Improved as usual to keep things isolated from my host OS.

composer global require kherge/box

We could download the phar of Box and use that, but we’re a Composer crowd here, we don’t use those outdated platform-specific methods ;)

Now that’s done, the box command is accessible from anywhere on our machine:

Box 01 Gif

To package an app with Box, one needs to include a box.json file in the project’s folder. The simplest of these can be seen in the example application:

{
    "files": ["src/Put.php"],
    "main": "bin/main",
    "output": "example.phar",
    "stub": true
}

The project we’ll be packaging has a lot of files, but only one we actually need for the Phar to be useful, so to keep it light, we’ll only package that one (and another, but more on that later). In that regard, our json file won’t be all that different (though if you’d like to see a more complex one, see the from Box itself here). Let’s begin!

Box.json

Note for Vagrant users: when using Vagrant boxes such as Homestead Improved the Phar extension won’t be able to write files for some reason. The solution is to either change the “output” property to a folder outside the shared one, or to do everything outside a shared folder altogether. We’ll take the latter approach in this case and just do everything inside the Vagrant user’s “home” folder.

The first thing we do is, of course, clone the repo:

cd ~
git clone https://github.com/webiny/folderbuilder
cd folderbuilder
git checkout 74b234fa33bd69690a2c26df38ef7d188c4e69eb

The last command is necessary so that you end up in a state of the project before I applied the fixes outlined in this post.

Then, we create the file box.json and populate it with:

{
	"files": ["structure.php"],
	"output": "bin/wfb.phar",
	"stub": true,
	"main": "structure.php"
}

So what does this mean?

The “files” property lists all the files we want to include in the Phar. “output” is the product of our build, and “main” indicates the entry file. “stub” is required when using CLI apps. The help file says:

“The stub (string, boolean) setting is used to specify the location of a stub file, or if one should be generated. If a path is provided, the stub file will be used as is inside the Phar. If true is provided, a new stub will be generated. If false (or nothing) is provided, the default stub used by the Phar class will be used.” and further explanation can be found here.

We make a bin folder because it’s a common place to put built/compiled resources.

Then, we run:

box build -v

Box will automatically detect the box.json file in the folder and produce output not unlike this one:

? Output path: /home/vagrant/folderbuilder/bin/wfb.phar
? Adding files...
  + /home/vagrant/folderbuilder/structure.php
? Adding main file: /home/vagrant/folderbuilder/structure.php
? Generating new stub...
* Done.

If we now execute the command:

php bin/wfb.phar

The current directory’s structure will be printed on screen in JSON format, just like if we ran php structure.php directly.

Executable

But how can we make it run without needing to specify either php or .phar? If you look inside the contents of wfb.phar, the first two lines will read:

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

This means “When this file is executed, use the PHP environment to chew it through”. To be able to do this, though, we need to make the file executable, and we do this by adding a “chmod” flag to our box.json file. While we’re at it, we can also make sure the files we include in the phar are compacted by using the two default compressors included with Box:

{
	"files": ["structure.php", "bin/stub.php"],
	"output": "bin/wfb.phar",
	"stub": true,
	"main": "bin/stub.php",
	"chmod": "0755",
	"compactors": [
        "Herrera\\Box\\Compactor\\Json",
        "Herrera\\Box\\Compactor\\Php"
    ]
}

Now, rerun box build and after it’s done, try running:

bin/wfb.phar

It should work. We still have to specify the extension though, and just removing it from box.json will cause Box to throw errors at us. What if we just removed .phar from the filename and hoped for the best?

mv bin/wfb.phar bin/wfb
bin/wfb

Lo and behold, it works! Okay, now how do we distribute this?

Distribution

If your project already has a composer.json file, then all you need to do is add a bin field for vendor binaries:

"bin": ["bin/wfb"]

In the case of FolderBuilder, there was no composer.json file at all, so I created one from scratch with composer init. The final version is here.

The project also needed to be put onto Packagist. Following the instructions in this post, that’s a 5-minute endeavor.

Trying it out

With everything done, let’s try it out. I’m going to fire up a new Homestead Improved instance for that, just so I’m 100% sure I’m starting with a fresh environment, and so that I don’t have to uninstall anything from the environment we’ve built this tutorial on.

Starting a new HI instance for testing is literally 5 lines of shell commands:

git clone https://github.com/swader/homestead_improved hi_fbtest
cd hi_fbtest
sed -i '' "s@map\: \.@map\: $PWD@g” Homestead.yaml
vagrant up
vagrant ssh

Now inside my new VM, I run:

composer global require webiny/folderbuilder

That should be enough. Let’s see if it works. I want to map out the Code folder (i.e. the root of the Homestead Improved project, should produce the scripts folder, Homestead.yaml, and other files and folders).

wfb ~/Code > out.json

And indeed, the file is there! Pasting its contents into FolderBuilder, we get exactly what we asked for.

FolderBuilder Displays Desired Output

Conclusion

In this tutorial, we looked at boxing up PHP code into Phars with the Box project. We discussed making them executable and we explained how to distribute these Phars during Composer installation, so that they become immediately accessible from everywhere once globally required. We could talk about signing the Phars or building their auto-update scripts, but that was already done better by someone else – check that post out if you’d like to learn more advanced aspects.

As for Webiny’s folder builder, it’s open source and welcoming contributions. Have ideas on how to improve it further? Maybe add tree creation into the mix, reading a JSON file and creating what was developed in the GUI? Go for it, submit a PR!

Did you see any missteps in our process? Do you follow the same workflow or do you package your Phars differently? Let us know in the comments!

No Reader comments

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.