Getting Antsy with Phing
This blog post is more than 2 years old, so the content may be out of date.
Earlier today, I set to work on an open source project...pulled down a copy from github, and started reading through the code.
And there I found a build.xml file.
At first I didn't think anything of it - plenty of my projects have ended up using Ant for setup, builds, CI and other myriad tasks.
However, when I tried to run ant, it complained: <echo msg="foo" /> is invalid syntax, it should be <echo message="foo" />. It took a while before I eventually twigged that this wasn't an ant build file, it was a Phing file.
Phing is a port of apache ant to work with PHP instead of Java. It's mostly pretty similar, with a few minor differences here and there - such as msg attributes instead of message.
I decided to simplify setup of the project by adding database build tasks. The project included an initial database export file, and a series of patch files which needed to be run sequentially (LornaJane has a great post on this kind of database patching strategy).
So my initial build code looked like this:
<target name="initialiseDB">
<echo msg="DB: Remove database..." />
<pdo
url="${db.dbdriver}:host=${db.hostname};"
userid="${db.username}"
password="${db.password}">
DROP DATABASE IF EXISTS ${db.database};
</pdo>
<echo msg="DB: Create empty database..." />
<pdo
url="${db.dbdriver}:host=${db.hostname};"
userid="${db.username}"
password="${db.password}">
CREATE DATABASE ${db.database};
</pdo>
<!-- import the init_db file -->
<echo msg="DB: Import database..." />
<pdo
url="${db.dbdriver}:host=${db.hostname};dbname=${db.database}"
userid="${db.username}"
password="${db.password}">
<filelist dir="./doc/db" files="init_db.sql" />
</pdo>
<!-- process patch files -->
<echo msg="DB: Process patch files..." />
<pdo
url="${db.dbdriver}:host=${db.hostname};dbname=${db.database}"
userid="${db.username}"
password="${db.password}">
<fileset dir="./doc/db">
<include name="patch*.sql" />
</fileset>
</pdo>
</target>However, there's a problem with this code
<!-- process patch files -->
<echo msg="DB: Process patch files..." />
<pdo
url="${db.dbdriver}:host=${db.hostname};dbname=${db.database}"
userid="${db.username}"
password="${db.password}">
<fileset dir="./doc/db">
<include name="patch*.sql" />
</fileset>
</pdo>The fileset type will iterate all the files, but in the order they're returned by the filesystem. We need them to run in numerical order: patch1.sql, patch2.sql, etc.
The alternative to fileset is filelist, which takes a comma-separated list and processes them in order:
<!-- process patch files -->
<echo msg="DB: Process patch files..." />
<pdo
url="${db.dbdriver}:host=${db.hostname};dbname=${db.database}"
userid="${db.username}"
password="${db.password}">
<filelist dir="doc/db" files="patch1.sql,patch2.sql,patch3.sql" />
</pdo>This works, but comes with a different problem - every time a patch file is added, the build.xml needs updating.
So we need a way of building an ordered list of files into a comma-separated list. Tricky.
Phing provides a php task, which will set a Phing property to the result of a php expression.
<!-- set a Phing property called 'foo' to the value 'bar' -->
<php expression="'bar'" returnProperty="foo" />
<!-- set a Phing property called 'foo' to the value '7' -->
<php expression="3+4" returnProperty="foo" />
<!-- set a Phing property called 'foo' to the value 'bar,baz' -->
<php expression="implode(',', array('bar', 'baz'))" returnProperty="foo" />So we're a few steps closer...what about this?
<php expression="implode(',', glob('doc/db/patch*.sql'))" returnProperty="db.patchfiles" />It's close - we get a comma-separated list of patch files...but they're still not sorted.
At first glance, you might think to just wrap it in a sort function: implode(',', sort(glob('doc/db/patch*.sql'))). Unfortunately, sort acts on an array variable, but doesn't return a sorted array, it returns a boolean which means it can't be chained: the expression collapses to implode(',', true), and PHP becomes unhappy.
What we need is a series of expressions:
$list = glob('doc/db/patch*.sql'); natsort $list; $list = implode(',', $list);
The solution is to use eval to wrap this series of expressions into a single expression:
<php expression="eval('$list=glob("doc/db/patch*.sql"); natsort ($list); return implode(",", $list);');" returnProperty="db.patchfiles" />Et voila - a sorted comma-separated list of patch files.


Comments
Jeremy (not verified)
February 18, 2011 - 4:41pm
Permalink
This post gave me some insights on how to solve a similar problem.
Thanks!
Cédric D (not verified)
March 27, 2012 - 8:21am
Permalink
Great post, thanks dude!
Add new comment