Idiot-Proof PHP Deployment with Phing

DZone 's Guide to

Idiot-Proof PHP Deployment with Phing

· Web Dev Zone ·
Free Resource
Disclaimer: I am not underestimating the universe's ability to produce idiots, the point I'm trying to make is that I haven't managed to make any deploy mistakes using this approach. Yet.

Once upon a time, a long time ago, I went onto a conference stage for the very first time and said that I thought I might be the world's ditsiest PHP developer. I actually still think that is pretty true, and if you work with me then you will know that I mostly break and fix things in approximately equal measure. With this in mind, when I launched my own product recently ( BiteStats, a thing to automatically email you a summary of your analytics stats every month), I knew that I would need a really robust way of deploying code. I've been doing a few different things for a few years, and I've often implemented these tools with or for other organisations, but I don't have much code in production in my own right, weirdly. I decided Phing was the way to go, got it installed, and worked out what to do next.


I created a file called build.xml in the root of my project directory, and initially it contained only the following:
<?xml version="1.0" encoding="UTF-8"?>
<project name="bitestats" basedir="." default="deploy">
    <property name="builddir" value="./build" />


That default attribute says which task to run if no task is specified, and the property tag initialises a variable I'll need in a couple of places later on. In fact it's the build directory, a directory that will be used to hold all the artefacts of the build.


Tasks are defined in the build file, inside the project tag. I have two really simple ones which I think most projects have: build and clean. These create the build directory ready for me to use when I run other tasks, and remove it again. So with those included, my file now looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<project name="bitestats" basedir="." default="deploy">
    <property name="builddir" value="./build" />

    <target name="clean">
            <echo msg="Clean..." />
            <delete dir="${builddir}" />

    <target name="prepare">
            <echo msg="Prepare..." />
            <mkdir dir="${builddir}" />


To run the tasks, you just do phing prepare or phing clean respectively.

Real Deploy Tasks

To run the deployment itself, I have the default task that you saw declared in the first snippet, deploy. The task definition looks like this:
<target name="deploy">
      <echo msg="Next Tag ..." />
      <exec command="echo $(($(hg tags | egrep -o '^deploy\-([0-9]*) ' | egrep -o '[0-9]* ' | sort -nr | head -n 1) + 1))" outputProperty="nextTag" />
      <echo msg="... is ${nextTag}.  Tagging ..." />
      <exec command="hg tag deploy-${nextTag}" />
      <echo msg="Grab code ..." />
      <exec command="hg archive -r deploy-${nextTag} build/deploy-${nextTag}" />
      <echo msg="Zip code ..." />
      <exec command="tar zcf deploy-${nextTag}.tgz deploy-${nextTag}"
dir="build" />

      <!-- copy to live -->
      <echo msg="Put code onto live server ..." />
      <exec command="scp -i /path/to/ssh/key build/deploy-${nextTag}.tgz apache@server.example.com:/path/to/bitestats/htdocs/" />
      <!-- make live-->
      <exec command="ssh -i /path/to/ssh/key apache@server.example.com 'cd bitestats/htdocs && tar -zxf deploy-${nextTag}.tgz && ln -s deploy-${nextTag} next && ln -s /usr/local/Zend/ZendFramework-1.11.2-minimal/library/Zend/ public/library/Zend && php -f public/cli.php -- -a updatedbpatch && mv -fT next public'" logoutput="yes"/>
      <exec command="ssh lorna@server.example.com 'supervisorctl restart worker'" logoutput="yes"/>


A quick recap of what actually happens here:

  1. We work out what number the next tag would be and tag the codebase. I use Mercurial for source control and host my code in a private repo on BitBucket

  2. We use hg archive, which is basically like SVN export, to get the appropriate code version and then tar and zip it.

  3. The code is scp-ed to the server, by the apache user

  4. On the remote server, a few more commands get run all in one go. Firstly, the code is unpacked

  5. A symlink called "next" is created and pointed to the new folder (I blogged about my symlink strategy already if you are interested)

  6. A symlink is created to the library code, in this case ZF but you'd also create symlinks to config files, upload/cache directories, and anything else that's needed at this point too

  7. Any database patches needed are run at this point (again, I blogged this script before in case you want to see it)

  8. We switch the symlink so that the new content is live

  9. Finally, we stop and start the gearman worker so it picks up new code (one worker, all on the one server, there really isn't a lot of load on this site!). My gearman processes are overseen by supervisord, but it only answer to my user. It actually prompts me for my password at this point

Having a Deployment Process

The whole process takes a few seconds and so far it has worked like a charm. I'm really happy with it, and really happy I took the time to put this in place. Every time I deploy I feel a little bit smug :) I know it can be hard to get deployment stuff set up, after all we can do all the individual steps pretty easily and everyone has plenty of other stuff to worry about - but I think it's important and even though the "right" solution will be different for every application and platform, I wanted to share what works for me. What works for you? Leave me a comment!

Published at DZone with permission of Lorna Mitchell , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}