Ken Guest’s online diary

November 5, 2015

Getting phing custom tasks up to PR standard. What I did.

Filed under: phing,php — kenguest @ 13:44
Tags: ,


Phing is a build system that I’ve written about before which is open source, and developed in PHP.

Two custom tasks that I wrote have been merged into the main phing repository on github and I thought I might write up my notes on what I did to get them up to “PR Standard”.

The first task is for creating and modifying .ini files and is now documented at

The second one is simply a ‘task-wrapper’ (trademark pending) for running notify-send so that pop-up or toaster messages (whatever you want to call them) get displayed at certain stages of a build. This is more efficient for me that alt-tabbing to the window that I’ve phing running in to see what’s going on.

I’m not going to describe how to use them here; hopefully the documentation that I wrote for them in phing is sufficient.

I’m also not going to outline how to write custom tasks – there’s already good documentation for that at

Instead I want to document what I learnt in the process of taking the earlier version of NotifySendTask ( ) and the IniFileTask, and getting them to Pull Request standard for inclusion in phing.

Who knows, this might help some of you decide to add a phing Pull Request of your own.

Documentation Driven Development.

Documentation is always a factor on how well an OSS project will be used, so this is one of the first things that I wanted to address.

The inifile task is an optional one – it is not a core phing task – so I added a new section to the optionaltasks docbook file at

Because the IniFileTask is pretty much a direct port of the ant-contrib equivalent, I took the documentation from and made just a few changes as I wanted to add support for a ‘haltonerror’
Converting it over to docbook from HTML was relatively trivial.

If I wanted, I could have used a docbook specific editor for doing this, but I was comfortable using vim, so I did.

I did something similar for the NotifySend task, except that there is an intentional non-documented attribute.


I added some examples to the documentation and only after the PR for inifiletask did I look at the PDF version of the manual.

I noticed only then that example listings look best when kept to a maximum length of 80 characters per line – after 90 characters or so the lines get truncated.

On with the code.


Obviously in a build system it is expected to see messages being logged, and possibly at differing levels of importance.

To log a message at the default level:
    $this->log("Log this message");

Or, explicitly:
    $this->log("Log this message", Project::MSG_INFO);

These are the levels/priorities that messages can be inserted into the log at:

  • Project::MSG_DEBUG
  • Project::MSG_ERR
  • Project::MSG_INFO
  • Project::MSG_VERBOSE
  • Project::MSG_WARN

You will see below that you can assert against entries being present, or not, in the log.

Knowing all this makes it easier to assert in unit tests that various edge cases are being tested properly, and without adding any visual clutter when the task is being run casually.

Enabling a new task by default.

To make the tasks visible by default, for testing and also so they can be used with the minimum of hassle, I referenced them in the file classes/phing/tasks/ by adding these lines:



The location of the unit test file for the task has to mirror the location of the actual task files; so because the ‘working’ code for the task is in phing/classes/phing/tasks/ext/inifile I put the unit tests into the test/classes/phing/tasks/ext/inifile/ directory.

The NotifySendTask that I developed is less complex in that it requires just one class rather than the four for the IniFileTask so the files for that are ini the tasks/ext directory instead of requiring a separate subdirectory for them. Though if I split out the unit tests for that into separate files then I would’ve created a subdirectory for them rather than clutter up tasks/ext.

Test specific build files for unit tests can be used, they are executed via the executeTarget and should be placed inside the etc/tasks/ext directory structure.

For example: etc/tasks/ext/NotifySendTaskTest.xml

Or a separate subdirectory structure such as ./etc/tasks/ext/inifile/inifile.xml

The unit test class should be a subclass of BuildFileTest.

BuildFileTest provides a number of helper methods, for example assertInLogsassertNotInLogs, configureProject and executeTarget, expectBuildException and others.

Use configureProject in the setUp method to initialise the project that the unit tests are for.

assertInLogs("Assert this text exists in the logs");
assertInLogs("Assert this text exists in the logs", Project::MSG_DEBUG);

 * Configure the project to point to the correct .xml file for these tests
public function setUp() {
    $this->configureProject(PHING_TEST_BASE . "/etc/tasks/ext/inifile/inifile.xml");
    $this->object = new ThisIsTheNewTask();

public function testSetterOutsideXml() {
    $this->object->setTitleProperty("test title");
    $this->assertEquals("test title", $this->object->getTitleProperty());

public function testInTarget()
    $this->assertInLogs("expected as debug log entry", Project::MSG_DEBUG);

public function testTargetUsingHelper()
    $this->expectDebuglog("targetToTest", "expected as debug log entry");

The PHING_TEST_BASE constant is used to refer to the base directory for all phing test files. As well as that, the test functions testInTarget and testTargetUsingHelper are functionally identical. There are many other useful methods in the BuildFileTest class and I looked through phing/test/classes/phing/BuildFileTest.php to get familiar with
what else it can do.
There is also a PhingTestListener class in that BuildFileTest.php file, but there currently are no tasks with unit tests which utilise it.

As mentioned earlier, build files can be used in unit tests. This is very useful so that you can test that the setters in tasks load and work properly when phing parses the XML files. Explicitly calling setters and getters for your unit tests will only go so far, while testing against actual build files will highlight whether or not setters that are expected to be called will be.
The build file to use for unit tests is specified in the configureProject call.
There are a number of helper functions that go along with the executeTarget method, such as expectPropertySet, expectLog, expectLogContaining, expectDebuglog, expectOutput, expectOutputAndError, and two more related to
catching expected exceptions.

Working on the Task code.

One of the main things that I added to the Pull-Request version of the NotifySendTask was additional logging and getters that could be utilised for unit testing.

I also added extra validation on the values being set – checking generic icon values are valid, and if a specified icon is a filename then the task logs whether the file actually exists and so on.

After considering how to handle unit tests being run on systems where notify-send itself wasn’t installed – I added a control parameter, in the vein of an Operator Presence Control, so the system can essentially fake running successfully.

This is the one attribute that I didn’t add to the documentation.

The IniFileTask code is perhaps a level of complexity above that for NotifySendTask – in that there are extra XML elements required, and so extra classes are required as well; all of which have their respective setter methods by necesitty. Also I opted to add corresponding getter methods to these to allow unit tests that don’t require specific build files for each test.

This is also why inifile got a subdirectory for itself and the simpler notifysend task did not.


That’s all folks!

If there’s anything you think I should have covered, let me know and I’ll either edit this with an update or write a follow-up, depending on how substantial it is 🙂


Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

Blog at