My friend Jon and I are taking the week to explore creating a proper Deployment Pipeline for a PHP project. PHP projects are often deployed by checking out all the appropriate source code to prescribed directories on the target server, running shell scripts that determine environments and perform search/replaces in source files, soft-linking environmental config in, copying files around, compressing and moving resources around, running unit and integration tests. At first blush it seems you could automate this by stringing together some small programs that run each of these steps and then hands off control for the next step but that would only be scripting. This is an important distinction. Automation buys a concept and relieves cognitive load, Scripting does not. Scripting would be the equivalent of, “…move the shifter into 1st, release the clutch until you feel the pressure point, give a little gas and let the clutch out all the way, give more throttle to accelerate, let off the gas and put in the clutch, move the shifter to 2nd…” where as Automation would be, “Put the car in Drive”. There’s certainly nothing wrong with scripting and I personally prefer to drive stick but if you run a company where shifting a transmission is incidental to your core business you should automate it.
One of the first issues with the above scenario is that we have a mutable build. The build is either transformed at every step or rebuilt in every environment. The artifact we start out with that passes our unit tests is not the same thing that gets delivered to production later. This lowers our confidence in the build which manifests as unknown issues with the deploy. Maybe you just try again. Maybe you try again and copy `config.php` from `sample/` to `prod/` because someone told you last week but it slipped your mind. Hopefully that will work. Depending on your background this may be a little foreign. In Java, for example, you would never copy your compiled classes between servers because it’s easier to package it as a `jar` or `war` or then copy that (not that I haven’t seen `.class` files copied individually to servers). The tooling makes that easy. In PHP-land this concept is very much in it’s infancy which makes it non-obvious and harder to do than just copying source files.
The first thing we did was define a deliverable. This was a little harder than we thought as the current definition of a project is often “Source code files copied to a certain directory on a server.” It’s easy enough to define the deliverable as a directory or tarball or zip file but we ended up going with PHP’s Webphar. Phar is PHP’s answer to Java’s JAR and Webphar is similar to WAR. The immediate benefit is that you have an artifact you can drop into your web server that will Just Work but the deeper benefit is that this gets people away from thinking of deploys as “copying files” and creates a clear distinction between source code and deliverable. This distinction comes for free with compiled languages but it still exists in interpreted ones, it’s just somewhat hidden; the source file you edit, although the same as the one you deliver, is still different. One is source code and one is a deliverable.
The Nitty Gritty
Our project name is `lobs` for myriad reasons so try not to worry about that. This is our `create_phar.php` file:
<?php ini_set("phar.readonly", 0); $phar = new Phar('lobs.phar', 0, 'lobs.phar'); $phar->buildFromDirectory(__DIR__ . '/src'); $phar->setStub('<?php Phar::webPhar(); __HALT_COMPILER();'); $phar->setSignatureAlgorithm(Phar::SHA256);
There's a little bit of ceremony involved but there are probably tools like `phing` which can automate some of this. To run this and create the PHAR:
php -dphar.readonly=0 create_phar.php
We’re using Apache as our web server and, provided it’s already setup to understand PHP, you need only tell it about the `.phar` extension.
AddType application/x-httpd-php .php .phar
And voila: http://localhost/lobs.phar/index.php
We were pretty pumped about getting this far. I had dabbled with Phar files before and though they had “worked” there was a lot of trial and error and I wasn’t 100% sure _why_ it worked which was very unsatisfying. I had also never gotten it working with Apache. There is just not much documentation out there on using and interacting with Phars. Working together we were able to overcome hurdles pretty quickly and it was very exciting the first time we copied our Phar to our deploy directory and were viewing files in the browser.
Join us next time when we try to rope in Dependency Management, Pipelines, CI, Build Tools, Static Code Analysis and maybe break some dishes.