Getting Antsy with Phing

This blog post is more than 3 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(&quot;doc/db/patch*.sql&quot;); natsort ($list); return implode(&quot;,&quot;, $list);');" returnProperty="db.patchfiles" />

Et voila - a sorted comma-separated list of patch files.

Comments

This post gave me some insights on how to solve a similar problem.

Thanks!

Great post, thanks dude!

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <apache>, <bash>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <python>, <ruby>. The supported tag styles are: <foo>, [foo]. PHP source code can also be enclosed in <?php ... ?> or <% ... %>.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.