Testing with PHPUnit (Best Tutorial 2019)

Testing with PHPUnit

PHP Unit Testing and Testing with PHP Unit

Every component in a system depends for its continued smooth running on the consistency of operation and interface of its peers. By definition, then, development breaks systems. As you improve your classes and packages, you must remember to amend any code that works with them.

 

For some changes, this can create a ripple effect, affecting components far away from the code you originally changed. Eagle-eyed vigilance and an encyclopedic knowledge of a system’s dependencies can help to address this problem.

 

Of course, while these are excellent virtues, systems soon grow too complex for every unwanted effect to be easily predicted, not least because systems often combine the work of many developers.

 

To address this problem, it is a good idea to test every component regularly. This, of course, is a repetitive and complex task; and as such, it lends itself well to automation.

 

Among the test solutions available to PHP programmers, PHPUnit is perhaps the most ubiquitous and certainly the most fully featured tool. In this blog, you will learn the following about PHPUnit:

  • Installation: Using PEAR to install PHPUnit
  • Writing Tests: Creating test cases and using assertion methods
  • Handling Exceptions: Strategies for confirming the failure
  • Running multiple tests: Collecting tests into suites
  • Constructing assertion logic: Using constraints
  • Faking components: Mocks and stubs
  • Testing web applications: Testing with and without additional tools

 

Functional Tests and Unit Tests

Unit Tests

Testing is essential in any project. Even if you don’t formalize the process, you must have found yourself developing informal lists of actions that put your system through its paces. This process soon becomes wearisome, and that can lead to a fingers-crossed attitude to your projects.

 

One approach to testing starts at the interface of a project, modeling the various ways in which a user might negotiate the system. This is probably the way you would go when testing by hand, although there are various frameworks for automating the process.

 

These functional tests are sometimes called acceptance tests because a list of actions performed successfully can be used as criteria for signing off a project phase.

 

Using this approach, you typically treat the system as a black box—your tests remain willfully ignorant of the hidden components that collaborate to form the system under test.

 

Whereas functional tests operate from without, unit tests work from the inside out. Unit testing tends to focus on classes, with test methods grouped together in test cases. Each test case puts one class through a rigorous workout, checking that each method performs as advertised and fails as it should.

 

The objective, as far as possible, is to test each component in isolation from its wider context. This often supplies you with a sobering verdict on the success of your mission to decouple the parts of your system.

 

Tests can be run as part of the build process, directly from the command line, or even via a web page. In this blog, I’ll concentrate on the command line.

Unit testing

Unit testing is a good way of ensuring the quality of design in a system. Tests reveal the responsibilities of classes and functions. Some programmers even advocate a test-first approach.

 

You should, they say, write the tests before you even begin work on a class. This lays down a class’s purpose, ensuring a clean interface and short, focused methods. Personally, I have never aspired to this level of purity—it just doesn’t suit my style of coding.

 

Nevertheless, I attempt to write tests as I go. Maintaining a test harness provides me with the security I need to refactor my code. I can pull down and replace entire packages with the knowledge that I have a good chance of catching unexpected errors elsewhere in the system.

 

Testing by Hand

Testing by Hand

In the last section, I said that testing was essential in every project. I could have said instead that testing is inevitable in every project. We all test. The tragedy is that we often throw away this good work.

 

So, let’s create some classes to test. Here is a class that stores and retrieves user information. For the sake of demonstration, it generates arrays, rather than the User objects you’d normally expect to use:

listing class UserStore
{
private $users = [];
public function addUser(string $name, string $mail, string $pass):bool {
if (isset($this->users[$mail])) {
throw new \Exception(
"User {$mail} already in the system"
);
}
if (strlen($pass) < 5) {
throw new \Exception(
"Password must have 5 or more letters"
);
}
$this->users[$mail] = [
'pass' => $pass,
'mail' => $mail,
'name' => $name
];
return true;
}
public function notifyPasswordFailure(string $mail)
{
if (isset($this->users[$mail])) {
$this->users[$mail]['failed'] = time();
}
}
public function getUser(string $mail):array
{
return ( $this->users[$mail] );
}
}

 

This class accepts user data with the addUser() method and retrieves it via getUser(). The user’s e-mail address is used as the key for retrieval. If you’re like me, you’ll write some sample implementation as you develop, just to check that things are behaving as you designed them:

 // listing
$store = new UserStore();
$store->addUser(
"bob williams",
"bob@example.com",
"12345"
);
$store->notifyPasswordFailure("bob@example.com"); $user = $store->getUser("bob@example.com"); print_r($user);

 

This is the sort of thing that I might add to the foot of a file as I work on the class it contains. The test validation is performed manually, of course; it’s up to me to eyeball the results and confirm that the data returned by UserStore::getUser() corresponds with the information I added initially. It’s a test of sorts, nevertheless.

 

Here is a client class that uses UserStore to confirm that a user has provided the correct authentication information:

listing class Validator
{
private $store;
public function __construct(UserStore $store)
{
$this->store = $store;
}
public function validateUser(string $mail, string $pass): bool {
if (! is_array($user = $this->store->getUser($mail))) { return false;
}
if ($user['pass'] == $pass) {
return true;
}
$this->store->notifyPasswordFailure($mail);
return false;
}
}

 

The class requires a UserStore object, which it saves in the $store property. This property is used by the validateUser() method to ensure, first of all, that the user referenced by the given e-mail address exists in the store; and, second, that the user’s password matches the provided argument.

 

If both these conditions are fulfilled, the method returns true. Once again, I might test this as I go along:

 style="margin:0;width:979px;height:79px">// listing
$store = new UserStore();
$store->addUser("bob williams", "bob@example.com", "12345"); $validator = new Validator($store);
if ($validator->validateUser("bob@example.com", "12345")) { print "pass, friend!\n";
}

 

I instantiate a UserStore object, which I prime with data and pass to a newly instantiated Validator object. I can then confirm a username and password combination.

 

Once I’m finally satisfied with my work, I could delete these sanity checks altogether or comment them out. This is a terrible waste of a valuable resource. These tests could form the basis of a harness to scrutinize the system as I develop. One of the tools that might help me to do this is PHPUnit.

 

Introducing PHPUnit

Introducing PHPUnit

PHPUnit is a member of the xUnit family of testing tools. The ancestor of these is SUnit, a framework invented by Kent Beck to test systems built with the Smalltalk language.

 

The xUnit framework was probably established as a popular tool, however, by the Java implementation, jUnit, and by the rise to prominence of agile methodologies like Extreme Programming (XP) and Scrum, all of which place great emphasis on testing.

 

You can get PHPUnit with Composer:

{
"require-dev": {
"phpunit/phpunit": "5.3.*"
}
}
Once you have run composer install, you will find the phpunit script at vendor/bin/phpunit. Or, you can download a PHP archive (.phar) file. You can then make the archive executable:
$ wget https://phar.phpunit.de/phpunit.phar
$ chmod 755 phpunit.phar
$ sudo mv phpunit.phar/usr/local/bin/phpunit

 

Creating a Test Case

Test Case

Armed with PHPUnit, I can write tests for the UserStore class. Tests for each target component should be collected in a single class that extends PHPUnit_Framework_TestCase, one of the classes made available by the PHPUnit package. Here’s how to create a minimal test case class:


namespace popp\ch18\batch01;
class UserStoreTest extends \PHPUnit_Framework_TestCase {
public function setUp()
{
}
public function tearDown()
{
}
...
}

 

I named the test case class UserStoreTest. You are not obliged to use the name of the class you are testing in the test’s name, although that is what many developers do.

 

Naming conventions of this kind can greatly improve the accessibility of a test harness, especially as the number of components and tests in the system begins to increase. It is often useful to place your test in the same namespace as the class under test.

 

This will give you easy access to the class under test and its peers, and the structure of your test files will likely mirror that of your system. Remember that, thanks to Composer’s support for PSR-4, you can maintain separate directory structures for class files in the same package.

 

Here’s how we might do this in Composer:

"autoload": {
"psr-4": {
"popp\\": ["myproductioncode/", "mytestcode/"]
}
}

 

In this code, I have nominated two directories that map to the popp namespace. I can now maintain these in parallel, making it easy to keep my test and production code separate.

 

Each test in a test case class is run in isolation from its siblings. The setUp() method is automatically invoked for each test method, allowing us to set up a stable and suitably primed environment for the test. tearDown() is invoked after each test method is run.

 

If your tests change the wider environment of your system, you can use this method to reset state. The common platform managed by setUp() and tearDown() is known as a fixture.

 

In order to test the UserStore class, I need an instance of it. I can instantiate this in setUp() and assign it to a property. Let’s create a test method as well:

// listing
namespace popp\ch18\batch01;
class UserStoreTest extends \PHPUnit_Framework_TestCase {
private $store;
public function setUp()
{
$this->store = new UserStore();
}
public function tearDown()
{
}
public function testGetUser()
{
$this->store->addUser("bob williams", "a@b.com", "12345");
$user = $this->store->getUser("a@b.com");
$this->assertEquals($user['mail'], "a@b.com");
$this->assertEquals($user['name'], "bob williams");
$this->assertEquals($user['pass'], "12345");
}
public function testAddUserShortPass()
{
try {
$this->store->addUser("bob williams", "bob@example.com", "ff"); } catch (\Exception $e) {
return;
}
$this->fail("Short password exception expected");
}
}

 

Note Remember that setUp() and tearDown() are called once for every test method in your class.

Test methods should be named to begin with the word “test” and should require no arguments. This is because the test case class is manipulated using reflection.

 

The object that runs the tests looks at all the methods in the class and invokes only those that match this pattern (that is, methods that begin with “test”).

 

In the example, I tested the retrieval of user information. I don’t need to instantiate UserStore for each test because I handled that in setUp().

 

Because setUp() is invoked for each test, the $store property is guaranteed to contain a newly instantiated object. 

 

Within the testgetUser() method, I first provide UserStore::addUser() with dummy data, and then I retrieve that data and test each of its elements.

 

There is one additional issue to be aware of here before we can run our test. I am using use statements without require or require_once. In other words, I am relying on autoloading. That’s fine, but where do I tell my tests how to locate the generated autoload.php file?

 

I could put a require_once statement in the test class (or a superclass), but that would break the PSR-1 rule that class files should not have side effects. The simplest thing to do is to tell PHPUnit about the autoload.php file from the command line:

$ phpunit class='lazy' data-src/ch18/batch01/UserStoreTest.php --bootstrap vendor/autoload.php
PHPUnit 5.0.10 by Sebastian Bergmann and contributors.
.. 2 / 2 (100%)
Time: 175 ms, Memory: 4.00MB
OK (2 tests, 3 assertions)

 

[Note: You can free download the complete Office 365 and Office 2019 com setup Guide for here]

 

Assertion Methods

Assertion Methods

An assertion in programming is a statement or method that allows you to check your assumptions about an aspect of your system.

 

In using an assertion, you typically define an expectation that something is the case, that $cheese is "blue" or $pie is "apple". If your expectation is confounded, a warning of some kind will be generated.

 

Assertions are such a good way of adding safety to a system that PHP supports them natively inline and allows you to turn them off in a production context.

 

Note See the manual page at PHP: assert - Manual for more information on PHP’s support for assertions. PHPUnit supports assertions through a set of static methods.

 

In the previous example, I used an inherited static method, assertEquals(). This method compares its two provided arguments and checks them for equivalence.

 

If they do not match, the test method will be chalked up as a failed test. Having subclassed PHPUnit_Framework_TestCase, I have access to a set of assertion methods. Some of these methods are listed in Table.

 

Table . PHPUnit_Framework_TestCase Assert Methods

Method Description

assertEquals($val1, $val2, Fail if $val1 is not equivalent to $val2 ($delta represents an $message, $delta) allowable margin of error)
assertFalse($expression, Evaluate $expression; fail if it does not resolve to false $message) 
assertTrue($expression, Evaluate $expression; fail if it does not resolve to true $message) 
assertNotNull($val, $message) Fail if $val is null
assertNull($val, $message) Fail if $val is anything other than null
assertSame($val1, $val2, Fail if $val1 and $val2 are not references to the same object, or if $message) they are variables of different types or values
assertNotSame($val1, $val2, Fail if $val1 and $val2 are references to the same object or variables $message) of the same type and value
assertRegExp($regexp, $val, Fail if $val is not matched by the regular expression, $regexp $message) 

assertAttributeSame($val, $attribute, $classname, $message)

Fail if $val is not the same type and value as $classname::$attribute

fail() Fail

 

Testing Exceptions

Testing Exceptions

Your focus as a coder is usually to make stuff work and work well. Often, that mentality carries through to testing, especially if you are testing your own code. The temptation is to test that a method behaves as advertised. It’s easy to forget how important it is to test for failure.

 

How good is a method’s error checking? Does it throw an exception when it should? Does it throw the right exception?

 

Does it clean up after an error if, for example, an operation is half complete before the problem occurs? It is your role as a tester to check all of this. Luckily, PHPUnit can help.

 

Here is a test that checks the behavior of the UserStore class when an operation fails:

//...
public function testAddUserShortPass()
{
try {
this->store->addUser("bob williams", "bob@example.com", "ff");
} catch (\Exception $e) { return;
}
$this->fail("Short password exception expected");
}
//...

 

If you look back at the UserStore::addUser() method, you will see that I throw an exception if the user’s password is less than five characters long. My test attempts to confirm this.

 

I add a user with an illegal password in a try clause. If the expected exception is thrown, then flow skips to the catch clause, and all is well.

If the addUser() method does not throw an exception as expected, the execution flow reaches the fail() method call.

 

Another way to test that an exception is thrown is to use an assertion method called expectException(), which requires the name of the exception type you expect to be thrown (either Exception or a subclass). If the test method exits without the correct exception having been thrown, the test will fail.

 

Note The expectedException() method was added in PHP 5.2.0.Here’s a quick reimplementation of the previous test:

// listing
namespace popp\ch18\batch02;
class UserStoreTest extends \PHPUnit_Framework_TestCase {
private $store;
public function setUp()
{
$this->store = new UserStore();
}
public function testAddUserShortPass()
{
$this->expectException('\\Exception');
$this->store->addUser("bob williams", "a@b.com", "ff"); $this->fail("Short password exception expected");
}
}

 

Running Test Suites

Running Test Suites

If I am testing the UserStore class, I should also test Validator. Here is a cut-down version of a class called

 

ValidateTest that tests the Validator::validateUser() method:

// listing
namespace popp\ch18\batch02;
class ValidatorTest extends \PHPUnit_Framework_TestCase {
private $validator;
public function setUp()
{
$store = new UserStore();
$store->addUser("bob williams", "bob@example.com", "12345"); $this->validator = new Validator($store);
}
public function tearDown()
{
}
public function testValidateCorrectPass()
{
$this->assertTrue(
$this->validator->validateUser("bob@example.com", "12345"),
"Expecting successful validation"
);
}
}

 

So now that I have more than one test case, how do I go about running them together? The best way is to place your test classes in a common root directory. You can then specify this directory, and PHPUnit will run all the tests beneath it:

$ phpunit class='lazy' data-src/ch18/batch02/
PHPUnit 5.0.10 by Sebastian Bergmann and contributors.
......
Time: 104 ms, Memory: 3.75Mb
OK (7 tests, 8 assertions)

 

Constraints

Constraints

In most circumstances, you will use off-the-peg assertions in your tests. In fact, at a stretch you can achieve an awful lot with AssertTrue() alone. As of PHPUnit 3.0, however, PHPUnit_Framework_TestCase included a set of factory methods that return PHPUnit_Framework_Constraint objects.

 

You can combine these and pass them to PHPUnit_Framework_TestCase::AssertThat() in order to construct your own assertions.

 

It’s time for a quick example. The UserStore object should not allow duplicate e-mail addresses to be added. Here’s a test that confirms this:

listing
UserStoreTest
public function testAddUserDuplicate()
{
try {
$ret = $this->store->addUser("bob williams", "a@b.com", "123456");
$ret = $this->store->addUser("bob stevens", "a@b.com", "123456"); self::fail("Exception should have been thrown");
} catch (\Exception $e) {
$const = $this->logicalAnd(
$this->logicalNot($this->contains("bob stevens")), $this->isType('array')
);
self::AssertThat($this->store->getUser("a@b.com"), $const);
}
}

 

This test adds a user to the UserStore object and then adds a second user with the same e-mail address. The test thereby confirms that an exception is thrown with the second call to addUser().

 

In the catch clause, I build a constraint object using the convenience methods available to us.

These return corresponding instances of PHPUnit_Framework_Constraint.

Let’s break down the composite constraint in the previous example:

$this->contains("bob stevens")

 

This returns a PHPUnit_Framework_Constraint_TraversableContains object. When passed to AssertThat, this object will generate an error if the test subject does not contain an element matching the given value ("bob stevens").

 

I negate this, though, by passing this constraint to another: PHPUnit_ Framework_Constraint_Not.

Once again, I use a convenience method, available through the TestCase class (actually through a superclass, Assert):

$this->logicalNot( $this->contains("bob stevens"))

 

Now, the AssertThat assertion will fail if the test value (which must be traversable) contains an element that matches the string, "bob stevens".

 

In this way, you can build up quite complex logical structures. By the time I have finished, my constraint can be summarized as follows: 'Do not fail if the test value is an array and does not contain the string "bob stevens".'

 

You could build much more involved constraints in this way. The constraint is run against a value by passing both to AssertThat().

You could achieve all this with standard assertion methods, of course, but constraints have a couple of virtues. First, they form nice logical blocks with clear relationships among components (although good use of formatting may be necessary to support clarity). Second, and more important, a constraint is reusable.

 

You can set up a library of complex constraints and use them in different tests. You can even combine complex constraints with one another:

$const = $this->logicalAnd(

$a_complex_constraint,

$another_complex_constraint

);

 

Mocks and Stubs

Mocks and Stubs

Unit tests aim to test a component in isolation of the system that contains it to the greatest possible extent. Few components exist in a vacuum, however. Even nicely decoupled classes require access to other objects as methods arguments. Many classes also work directly with databases or the filesystem.

 

You have already seen one way of dealing with this. The setUp() and tearDown() methods can be used to manage a fixture (i.e., a common set of resources for your tests, which might include database connections, configured objects, a scratch area on the file system, etc.)

 

Another approach is to fake the context of the class you are testing. This involves creating objects that pretend to be the objects that do real stuff. For example, you might pass a fake database mapper to your test object’s constructor.

 

Because this fake object shares a type with the real mapper class (extends from a common abstract base or even overrides the genuine class itself), your subject is none the wiser. You can prime the fake object with valid data.

 

Objects that provide a sandbox of this sort for unit tests are known as stubs. They can be useful because they allow you to focus in on the class you want to test without inadvertently testing the entire edifice of your system at the same time.

 

Fake objects can be taken a stage further than this, however. Because the object you are testing is likely to call a fake object in some way, you can prime it to confirm the invocations you are expecting. Using a fake object as a spy in this way is known as behavior verification, and it is what distinguishes a mock object from a stub.

 

You can build mocks yourself by creating classes hard-coded to return certain values and to report on method invocations. This is a simple process, but it can be time-consuming.

 

PHPUnit provides access to an easier and more dynamic solution. It will generate mock objects on the fly for you. It does this by examining the class you wish to mock and building a child class that overrides its methods. Once you have this mock instance, you can call methods on it to prime it with data and to set the conditions for success.

 

Let’s build an example. The UserStore class contains a method called notifyPasswordFailure(), which sets a field for a given user. This should be called by Validator when an attempt to set a password fails.

 

Here, I mock up the UserStore class so that it both provides data to the Validator object and confirms that its notifyPasswordFailure() method was called as expected:

listing
ValidatorTest
public function testValidateFalsePass()
{
$store = $this->getMock(__NAMESPACE__ . "\\UserStore"); $this->validator = new Validator($store);
$store->expects($this->once())
->method('notifyPasswordFailure')
->with($this->equalTo('bob@example.com'));
$store->expects($this->any())
->method("getUser")
->will($this->returnValue([
"name" => "bob williams",
"mail" => "bob@example.com",
"pass" => "right"
]));
$this->validator->validateUser("bob@example.com", "wrong");

 

Mock objects use a fluent interface; that is, they have a language-like structure. These are much easier to use than to describe. Such constructs work from left to right, each invocation returning an object reference, which can then be invoked with a further modifying method call (itself returning an object). This can make for easy use, but painful debugging.

 

In the previous example, I called the PHPUnit_Framework_TestCase method: getMock(), passing it "UserStore", the name of the class I wish to mock. This dynamically generates a class and instantiates an object from it.

 

I store this mock object in $store and pass it to Validator. This causes no error because the object’s newly minted class extends UserStore. I have fooled Validator into accepting a spy into its midst.

 

Mock objects generated by PHPUnit have an expects() method. This method requires a matcher object (actually it’s of type PHPUnit_Framework_MockObject_Matcher_Invocation.

 

But don’t worry; you can use the convenience methods in TestCase to generate your matcher). The matcher defines the cardinality of the expectation; that is, it dictates the number of times a method should be called.

 

TestCase Method Match Fails Unless . . .

  • any() Zero or more calls are made to the corresponding method
  • never() No calls are made to the corresponding method
  • atLeastOnce() One or more calls are made to the corresponding method
  • once() A single call is made to the corresponding method
  • exactly($num) $num calls are made to the corresponding method
  • at($num) A call to the corresponding method made at $num index

 

Having set up the match requirement, I need to specify a method to which it applies.

For instance, expects() returns an object (PHPUnit_Framework_MockObject_Builder_InvocationMocker, if you must know) that has a method called method(). 

I can simply call that with a method name. This is enough to get some real mocking done:

$store = $this->getMock("UserStore");

$store->expects($this->once())

->method('notifyPasswordFailure');

 

I need to go further, however, and check the parameters that are passed to notifyPasswordFailure().

The InvocationMocker::method() returns an instance of the object it was called on.


InvocationMocker includes a method name, with(), which accepts a variable list of parameters to match.

 

It also accepts constraint objects, so you can test ranges and so on. Armed with this, you can complete the statement and ensure that the expected parameter is passed to notifyPasswordFailure():

$store->expects($this->once())
->method('notifyPasswordFailure')
->with($this->equalTo('bob@example.com'));
You can see why this is known as a fluent interface. It reads a bit like a sentence: “The $store object expects a single call to the notifyPasswordFailure() method with parameter bob@example.com.”

 

Notice that I passed a constraint to with(). Actually, that’s redundant; any bare arguments are converted to constraints internally, so I could write the statement like this:

$store->expects($this->once())

->method('notifyPasswordFailure')

->with('bob@example.com');

 

Sometimes, you only want to use PHPUnit’s mocks as stubs; that is, as objects that return values to allow your tests to run. In such cases, you can invoke InvocationMocker::will() from the call to method().

 

The will() method requires the return value (or values if the method is to be called repeatedly) that the associated method should be primed to return.

 

You can pass in this return value by calling either TestCase::returnValue() or TestCase::onConsecutiveCalls(). Once again, this is much easier to do than to describe. Here’s the fragment from my earlier example in which I prime UserStore to return a value:

$store->expects($this->any())
->method("getUser")
->will($this->returnValue([
"name" => "bob williams",
"mail" => "bob@example.com",
"pass" => "right"
]));

 

I prime the UserStore mock to expect any number of calls to getUser(). Right now, I’m concerned with providing data, not with testing calls.

 

Next, I call will() with the result of invoking TestCase::returnValue() with the data I want returned (this happens to be a PHPUnit_Framework_ MockObject_Stub_Return object, although if I were you, I’d just remember the convenience method you use to get it).

 

You can alternatively pass the result of a call to TestCase::onConsecutiveCalls() to will(). This accepts any number of parameters, each one of which will be returned by your mocked method as it is called repeatedly.

 

Tests Succeed When They Fail

Tests Succeed

Although most agree that testing is a fine thing, you grow to really love it generally only after it has saved your bacon a few times. Let’s simulate a situation where a change in one part of a system has an unexpected effect elsewhere.

 

The UserStore class has been running for a while when, during a code review, it is agreed that it would be neater for the class to generate User objects rather than associative arrays. Here is the new version:

// listing
namespace popp\ch18\batch03;
class UserStore
{
private $users = [];
public function addUser(string $name, string $mail, string $pass)
{
if (isset($this->users[$mail])) {
throw new \Exception(
"User {$mail} already in the system"
);
}
$this->users[$mail] = new User($name, $mail, $pass);
return true;
}
public function notifyPasswordFailure(string $mail)
{
if (isset($this->users[$mail])) {
$this->users[$mail]->failed(time());
}
}
public function getUser(string $mail)
{
if (isset($this->users[$mail])) {
return ( $this->users[$mail] );
}
return null;
}
}
Here is the simple User class:
// listing
namespace popp\ch18\batch03;
class User
{
private $name;
private $mail;
private $pass;
private $failed;
public function __construct(string $name, string $mail, string $pass)
{
$this->name = $name;
$this->mail = $mail;
if (strlen($pass) < 5) {
throw new \Exception(
"Password must have 5 or more letters"
);
}
$this->pass = $pass;
}
public function getMail(): string
{
return $this->mail;
}
public function getPass(): string
{
return $this->pass;
}
public function failed(string $time)
{
$this->failed = $time;
}
}
Of course, I amend the UserStoreTest class to account for these changes. Consider this code designed to work with an array:
public function testGetUser()
{
$this->store->addUser("bob williams", "a@b.com", "12345");
$user = $this->store->getUser("a@b.com");
$this->assertEquals($user['mail'], "a@b.com");
//...
It is now converted into code designed to work with an object, like this:
public function testGetUser()
{
$this->store->addUser("bob williams", "a@b.com", "12345");
$user = $this->store->getUser("a@b.com");
$this->assertEquals($user->getMail(), "a@b.com");
}
When I come to run my test suite, however, I am rewarded with a warning that my work is not yet done:
$ phpunit class='lazy' data-src/ch18/batch03/
PHPUnit 5.0.10 by Sebastian Bergmann and contributors.
....F.. 7 / 7 (100%)
Time: 254 ms, Memory: 4.00MB
There was 1 failure:
popp\ch18\batch03\ValidatorTest::testValidateCorrectPass Expecting successful validation
Failed asserting that false is true.
/var/popp/class='lazy' data-src/ch18/batch03/ValidatorTest.php:24
FAILURES!
Tests: 7, Assertions: 6, Failures: 1.

I invoke getUser(). Although getUser() now returns an object and not an array, my method does not generate a warning. getUser() originally returned the requested user array on success or null on failure, so I validated users by checking for an array using the is_array() function.

 

Now, of course, getUser() returns an object, and the validateUser() method will always return false. Without the test framework, the Validator would have simply rejected all users as invalid without fuss or warning.

 

Now, imagine making this neat little change on a Friday night without a test framework in place. Think about the frantic text messages that would drag you out of your pub, armchair, or restaurant: “What have you done? All our customers are locked out!”

 

The most insidious bugs don’t cause the interpreter to report that something is wrong. They hide in the perfectly legal code, and they silently break the logic of your system.

 

Many bugs don’t manifest themselves where you are working; they are caused there, but the effects pop up elsewhere, days or even weeks later. A test framework can help you catch at least some of these, preventing rather than discovering problems in your systems.

 

Write tests as you code, and run them often. If someone reports a bug, first add a test to your framework to confirm it. Next, fix the bug so that the test is passed. Bugs have a funny habit of recurring in the same area. Writing tests to prove bugs and then to guard the fix against subsequent problems is known as regression testing.

 

Incidentally, if you keep a separate directory of regression tests, remember to name your files descriptively. On one project, our team decided to name our regression tests after Bugzilla ticket numbers. W

 

e ended up with a directory containing 400 test files, each with a name like test_973892.php. Finding an individual test became a tedious chore!

 

Writing Web Tests

Writing Web Tests

You should engineer your web systems in such a way that they can be invoked easily from the command line or an API call. In blog 12, you saw some tricks that might help you with this.

 

In particular, if you create a Request class to encapsulate an HTTP request, you can just as easily populate an instance from the command line or method argument lists as from request parameters. The system can then run in ignorance of its context.

 

If you find a system hard to run in different contexts, that may indicate a design issue. If, for example, you have numerous filepaths hard-coded into components, it’s likely you are suffering from the tight coupling.

You should consider moving elements that tie your components to their context into encapsulating objects that can be acquired from a central repository. 

 

Once your system can be run directly from a method call, you’ll find that high-level web tests are relatively easy to write without any additional tools.

 

You may find, however, that even the most well-thought-out project will need some refactoring to get things ready for testing. In my experience, this almost always results in design improvements.

 

Refactoring a Web Application for Testing

Web Application

We actually left the WOO example in a reasonable statement from a tester’s point of view. Because the system uses a single Front Controller, there’s a simple API interface. This is a simple class called Runner.php:

// listing
require_once("vendor/autoload.php");
use popp\ch18\batch04\woo\controller\Controller;
Controller::run();
That would be easy enough to add to a unit test, right? But what about command line arguments? To some extent, this is already handled in the Request class:
// listing
public function init()
{
if (isset($_SERVER['REQUEST_METHOD'])) {
if ($_SERVER['REQUEST_METHOD']) {
$this->properties = $_REQUEST;
return;
}
}
foreach ($_SERVER['argv'] as $arg) {
if (strpos($arg, '=')) {
list($key, $val) = explode("=", $arg);
$this->setProperty($key, $val);
}
}
}

Note Just a reminder that, if you do implement your own Request class, you should capture and store GET, POST and even PUT properties separately, rather than dumping them into a single $request property.

 

The init() method detects whether the process is running in a server context, and populates the $properties array accordingly (either directly or via setProperty()). This works fine for command-line invocation. For example, it means I can run something like this:

$ php class='lazy' data-src/ch18/batch04/Runner.php cmd=AddVenue venue_name=bob

 

The preceding line generates this response:

<html>
<head>
<title>Add a Space for venue bob</title>
</head>
<body>
<h1>Add a Space for Venue 'bob'</h1>
<table>
<tr>
<td>
'bob' added (18)</td></tr><tr><td>please add name for the space</td>
</tr>
</table>
[add space]
<form method="post">
<input type="text" value="" name="space_name"/> <input type="hidden" name="cmd" value="AddSpace" /> <input type="hidden" name="venue_id" value="18" /> <input type="submit" value="submit" />
</form>
</body>
</html>

 

Although this works for the command line, it remains a little tricky to pass in arguments via a method call. One inelegant solution would be to manually set the $argv array before calling the controller’s run() method.

 

I don’t much like this, though. Playing directly with magic arrays feels plain wrong, and the string manipulation involved at each end would compound the sin. Looking at the Controller class more closely, however, reveals a design decision that can help us:

listing
Controller
public function handleRequest()
{
$request = ApplicationRegistry::getRequest(); $app_c = ApplicationRegistry::appController();
while ($cmd = $app_c->getCommand($request)) {
$cmd->execute($request);
}
$this->invokeView($app_c->getView($request));
}

 

This method is designed to be invoked by the static run() method. Note how the Request object is not directly instantiated. Instead, I acquire it from the ApplicationRegistry.

 

When the Registry holds a single instance of an object like Request, I can acquire a reference to it and load it with data from within my test before I start the system running by invoking the controller. In this way, I can simulate a web request.

 

Because my system uses a Request object as its only interface to a web request, it is decoupled from the data source.

As long as Request is sane, the system will not care whether its data originates ultimately from a test or from a web server. As a general principle, I prefer to push instantiations back to a registry, where possible.

 

That way, I can later extend my implementation of the static factory method, ApplicationRegistry::instance() in this case. This approach returns a mock registry populated with fake components if a flag is set, thereby creating an entirely mocked environment. I love to fool my systems.

 

Here, however, I will demonstrate the first, more conservative trick by preloading my Request object with test data.

 

Simple Web Testing

Simple Web Testing

Here’s a test case that performs a very basic test on the WOO system:

/ listing
namespace popp\ch18\batch04;
use popp\ch18\batch04\woo\controller\Controller; use popp\ch18\batch04\woo\controller\Request;
use popp\ch18\batch04\woo\base\ApplicationRegistry; use popp\ch18\batch04\woo\controller\ApplicationHelper;
class AddVenueTest extends \PHPUnit_Framework_TestCase {
public function testAddVenueVanilla()
{
$output = $this->runCommand("AddVenue", ["venue_name"=>"bob"]);
}
public function runCommand($command = null, array $args = null)
{
$applicationHelper = ApplicationHelper::instance(); $applicationHelper->init();
$request = ApplicationRegistry::getRequest();
if (! is_null($args)) {
foreach ($args as $key => $val) {
$request->setProperty($key, $val);
}
}
if (! is_null($command)) {
$request->setProperty('cmd', $command);
}
woo\controller\Controller::run();
}
}

In fact, it does not so much test anything as proof that the system can be invoked. The real work is done in the runCommand() method. There is nothing terribly clever here.

 

I get a Request object from the RequestRegistry, and I populate it with the keys and values provided in the method call. Because the Controller will go to the same source for its Request object, I know that it will work with the values that I have set.

 

Running this test confirms that all is well. I see the output I expect. The problem is that this output is printed by the view, so it’s hard to test. I can fix that quite easily by buffering the output:

// listing
namespace popp\ch18\batch04;
use popp\ch18\batch04\woo\controller\Controller; use popp\ch18\batch04\woo\controller\Request;
use popp\ch18\batch04\woo\base\ApplicationRegistry; use popp\ch18\batch04\woo\controller\ApplicationHelper;
class AddVenueTest2 extends \PHPUnit_Framework_TestCase {
public function testAddVenueVanilla()
{
$output = $this->runCommand("AddVenue", ["venue_name"=>"bob"]); self::AssertRegexp("/added/", $output);
}
public function runCommand($command = null, array $args = null)
{
$applicationHelper = ApplicationHelper::instance(); $applicationHelper->init(); ob_start();
$request = ApplicationRegistry::getRequest();
if (! is_null($args)) {
foreach ($args as $key => $val) {
$request->setProperty($key, $val);
}
}
if (! is_null($command)) {
$request->setProperty('cmd', $command);
}
woo\controller\Controller::run();
$ret = ob_get_contents();
ob_end_clean();
return $ret;
}
}

By catching the system’s output in a buffer, I’m able to return it from the runCommand() method. Next, I apply a simple assertion to the return value to examine.

 

Here is the view from the command line:

command line

$ phpunit class='lazy' data-src/ch18/batch04/AddVenueTest2.php

PHPUnit 5.0.10 by Sebastian Bergmann and contributors. 
. 1 / 1 (100%)
Time: 194 ms, Memory: 4.00MB 
OK (1 test, 1 assertion) 

 

If you are going to be running lots of tests on a system in this way, it would make sense to create a Web UI superclass to hold runCommand().

 

I am glossing over some details here that you will face in your own tests. You will need to ensure that the system works with configurable storage locations. You don’t want your tests going to the same datastore that you use for your development environment. This is another opportunity for design improvement.

 

Look for hard-coded file paths and DSN values, and push them back to the Registry. Next, ensure your tests work within a sandbox but set these values in your test case’s setup() method.

 

Finally, look into swapping in a MockRequestRegistry, which you can charge up with stubs, mocks, and various other sneaky fakes.

 

Approaches like this are great for testing the inputs and output of a web application. There are some distinct limitations, however. This method won’t capture the browser experience.

 

Where a web application uses JavaScript, Ajax, and other client-side cleverness, testing the text generated by your system won’t tell you whether the user is seeing a sane interface.

Luckily, there is a solution.

 

Introducing Selenium

Selenium (http://seleniumhq.org/) consists of a set of commands for defining web tests. It also provides tools and APIs for authoring and running browser tests.  The test will work in conjunction with Selenium Server via an API called php-web driver.

 

Getting Selenium

You can download Selenium components at http://seleniumhq.org/download/. For the purposes of this example, you will need the Selenium Standalone Server.

 

Once you’ve downloaded the package, you should find a file named selenium-server-standalone-2.53.0.jar (although, of course, your version number will probably be different). Copy this file somewhere central. To proceed further, you’ll need Java installed on your system. Once you’ve confirmed this, you can start the Selenium Server.

 

Here, I copy the server to the /usr/local/lib directory. Then I start the server:

$ sudo cp selenium-server-standalone-2.53.0.jar /usr/local/lib/ $ java -jar /usr/local/lib/selenium-server-standalone-2.53.0.jar
20:20:06.501 INFO - Launching a standalone Selenium Server
20:20:06.673 INFO - Java: Oracle Corporation 25.65-b01
20:20:06.673 INFO - OS: Linux 4.0.4-303.fc22.x86_64 amd64
20:20:06.746 INFO - v2.53.0, with Core v2.53.0. Built from revision 35ae25b
20:20:06.922 INFO - Driver provider org.openqa.selenium.ie.InternetExplorerDriver registration is skipped:
registration capabilities Capabilities [{ensureCleanSession=true, browserName=internet
explorer, version=, platform=WINDOWS}] does not match the current platform LINUX
20:20:06.923 INFO - Driver provider org.openqa.selenium.edge.EdgeDriver registration is
skipped:
registration capabilities Capabilities [{browserName=MicrosoftEdge, version=, platform=WINDOWS}] does not match the current platform LINUX
20:20:06.923 INFO - Driver class not found: com.opera.core.systems.OperaDriver
20:20:06.923 INFO - Driver provider com.opera.core.systems.OperaDriver is not registered
20:20:06.925 INFO - Driver provider org.openqa.selenium.safari.SafariDriver registration is skipped:
registration capabilities Capabilities [{browserName=safari, version=, platform=MAC}] does not match the current platform LINUX
20:20:06.926 INFO - Driver class not found: org.openqa.selenium.htmlunit.HtmlUnitDriver
20:20:06.927 INFO - Driver provider org.openqa.selenium.htmlunit.HtmlUnitDriver is not registered
20:20:07.254 INFO - RemoteWebDriver instances should connect to: http://127.0.0.1:4444/wd/hub
20:20:07.254 INFO - Selenium Server is up and running

 

Note that the startup output tells us the URL we should use in order to communicate with the server. This will come in handy later. Now I’m ready to proceed.

 

PHPUnit and Selenium

PHPUnit and Selenium

Although PHPUnit has provided APIs for working with Selenium in the past, its support has been patchy and its documentation has been even more so. In order to access as many of Selenium’s features as possible, therefore, it makes sense to use PHPUnit in conjunction with a tool that is designed to provide the bindings that we need.

 

Introducing php-web driver

WebDriver is the mechanism by which Selenium controls browsers, and it was introduced with Selenium

 

Selenium developers provide Java, Python, and C# APIs for WebDriver. There are a few PHP APIs available. I have chosen to use the php-web driver, which was created by Facebook developers. It is under active development and mirrors the official APIs.

 

This is very handy when you want to look up a technique since many examples you’ll find online will be offered in Java. Which means they will apply readily to the php-web driver with a little porting of code.

 

You can add php-web driver it to your project with Composer:

"require-dev": {
"phpunit/phpunit": "5.0.*",
"facebook/webdriver" : "*"
},

Update your composer.json file, run composer update, and you should be ready to go.

 

Creating the Test Skeleton

Test Skeleton

I will be working with an instance of the WOO application, which will run on my system at the URL, http:// popp.vagrant.internal/webwoo/.

 

I’ll start off with a boilerplate test class:

// listing
namespace popp\ch18\batch04;
use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\WebDriverCapabilityType; use Facebook\WebDriver\WebDriverBy;
class SeleniumTest extends \PHPUnit_Framework_TestCase {
protected function setUp()
{
}
public function testAddVenue()
{
}
}

I specify some of the php-webdriver classes I will be using, and then create a bare bones test class. Now it’s time to make this test do something.

 

Connecting to Selenium

Connecting to Selenium

Remember that, on start up, the server outputs its connection URL. In order to make the connection to Selenium, I need to pass this URL and a capabilities array to a class named RemoteWebDriver:

// listing
namespace popp\ch18\batch04;
use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\WebDriverCapabilityType; use Facebook\WebDriver\WebDriverBy;
class SeleniumTest extends PHPUnit_Framework_TestCase {
private $driver;
protected function setUp()
{
$host = "http://127.0.0.1:4444/wd/hub";
$capabilities = [WebDriverCapabilityType::BROWSER_NAME => 'firefox']; $this->driver = RemoteWebDriver::create($host, $capabilities);
}
public function testAddVenue()
{
}
}

If you installed php-webdriver with Composer, 

you can see a full list of capabilities in the class file at vendor/facebook/webdriver/lib/Remote/WebDriverCapabilityType.php.

For my present purposes, however, I really only need to specify the browser name.

 

I pass the host string and the $capabilities array to the static RemoteWebDriver::create() method,

 and store the resulting object reference in the $driver property.

When I run this test, I should see that Selenium launches a fresh browser window in preparation for my tests.

 

Note These code examples were tested with Selenium 2.53 and Firefox 43.0, and then tested again with Selenium 2.53 and Firefox 45.0. There have been reports that some later versions of Firefox have manifested compatibility issues with Selenium. Hopefully, by the time you read this, these issues will have been resolved!

 

Writing the Test

Writing the Test

I would like to test a simple workflow. I will navigate to the AddVenue page, add a venue, and then add a space. This involves interacting with three web pages. Here is my test:

 // listing
namespace popp\ch18\batch04;
use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\WebDriverCapabilityType; use Facebook\WebDriver\WebDriverBy;
class seleniumtest3 extends \PHPUnit_Framework_TestCase {
protected function setUp() {
$host = "http://127.0.0.1:4444/wd/hub";
$capabilities = array(WebDriverCapabilityType::BROWSER_NAME => 'firefox'); $this->driver = RemoteWebDriver::create($host, $capabilities);
}
public function testAddVenue() {
$this->driver->get("http://popp.vagrant.internal/webwoo/AddVenue.php");
$venel = $this->driver->findElement(WebDriverBy::name("venue_name"));
$venel->sendKeys("my_test_venue");
$venel->submit();
$tdel = $this->driver->findElement( WebDriverBy::xpath("//td[1]") ); $this->assertRegexp("/'my_test_venue' added/", $tdel->getText());
$spacel = $this->driver->findElement(WebDriverBy::name("space_name")); $spacel->sendKeys("my_test_space"); $spacel->submit();
$el = $this->driver->findElement(WebDriverBy::xpath("//td[1]")); $this->assertRegexp("/'my_test_space' added/", $el->getText());
}
}

Here’s what happens when I run this test on the command line:

$ phpunit class='lazy' data-src/ch18/batch04/seleniumtest3.php
PHPUnit 5.0.10 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 3.11 seconds, Memory: 3.00MB
OK (1 test, 2 assertions)

 

Of course, it’s not all that happens. Selenium also launches a browser window and performs my specified operations upon it. I have to admit, I find this effect a little eerie!

 

Let’s run through the code. First, I invoke WebDriver::get(), which acquires my starting page. Note that this method expects a full URL (which does not need to be local to the Selenium Server host).

 

In this case, I configured an Apache server on a Vagrant virtual machine to serve up a mocked up AddVenue.php script. Selenium will load the specified document into the browser it has launched.

 

Once the page has loaded, I have access to it via the WebDriver API. I can acquire a reference to a page element using the RemoteWebDriver::findElement() method. This requires an object of type WebDriverBy.

 

The WebDriverBy class provides a set of factory methods, each of which returns a WebDriverBy object configured to specify a particular means of locating an element. My form element has a name attribute set to "venue_name", so I use the WebDriverBy::name() method to tell findElement() to look for an element this way.

 

WebDriverBy factory methods

factory methods

  • className() Find elements by CSS class name
  • cssSelector() Find elements by CSS selector
  • id() Find an element by its id
  • name() Find elements by name attribute
  • linkText() Find elements by their link text
  • partialLinkText() Find elements by a fragment of link text
  • tagName() Find elements by their tag
  • xpath() Find elements that match an Xpath expression

 

Once I have a reference to the venue_name form element, an object of type RemoteWebElement, I can use its sendKeys() method to set a value. It’s important to note that sendKeys() do more than just set a value.

 

It also simulates the act of typing into an element. This is useful for testing systems that use JavaScript to capture keyboard events.

 

With my new value set, I submit the form. The API is smart about this. When I call submit() on an element, Selenium locates the containing form and submits it.

 

Submitting the form, of course, causes a new page to be loaded. So, next, I check that all is as I expect.

Once again, I use WebDriver::findElement(), although this time I pass it a WebDriverBy object configured for Xpath.

If my search is successful, findElement() will return a new RemoteWebElement object.

 

If my search fails, on the other hand, the resulting exception will bring down my test. Assuming that all is well, I acquire the element’s value using the RemoteWebElement::getText() method. At this stage, I have submitted the form and checked the state of the returned web page.

 

Now all that remains is to populate the form once again, submit, and check the new page. I use techniques that you have already encountered to achieve this. Of course, I’ve only just scratched the surface of Selenium here.

 

But I hope this discussion has been enough to give you an idea of the possibilities. If you want to learn more, there is a complete Selenium manual at http://seleniumhq.org/docs/index.html.

 

A Note of Caution

automated tests

It’s easy to get carried away with the benefits that automated tests can offer. I add unit tests to my projects, and I use PHPUnit for functional tests, as well.

 

That is, I test at the level of the system, as well as that of the class. I have seen real and observable benefits, but I believe that these come at a price.

 

Tests add a number of costs to your development. As you build safety into the project, for example, you are also adding a time penalty into the build process that can impact releases.

 

The time it takes to write tests is part of this, but so is the time it takes to run them. On one system, we may have suites of functional tests that run against more than one database and more than one version control system. Add a few more contextual variables like that, and we face a real barrier to running the test suite.

 

Of course, tests that aren’t run aren’t useful. One answer to this is to fully automate your tests, so runs are kicked off by a scheduling application like cron. Another is to maintain a subset of your tests that can be easily run by developers as they commit code. These should sit alongside your longer, slower test run.

 

Another issue to consider is the brittle nature of many test harnesses. Your tests may give you the confidence to make changes, but as your test coverage increases along with the complexity of your system, it becomes easier to break multiple tests.

 

Of course, this is often what you want. You want to know when expected behavior does not occur or when unexpected behavior does.

 

Oftentimes, however, a test harness can break because of a relatively trivial change, such as the wording of a feedback string. Every broken test is an urgent matter, but it can be frustrating to have to change 30 test cases to address a minor alteration in architecture or output. Unit tests are less prone to problems of this sort because, by and large, they focus on each component in isolation.

 

The cost involved in keeping tests in step with an evolving system is a tradeoff that you simply have to factor in. On the whole, I believe the benefits justify the costs.

 

You can also do some things to reduce the fragility of a test harness. It’s a good idea to write tests with the expectation of change built in, to some extent. I tend to use regular expressions to test output rather than direct equality tests, for example.

 

Testing for a few keywords is less likely to make my test fail when I remove a newline character from an output string. Of course, making your tests too forgiving is also a danger, so it is a matter of using your judgment.

 

Another issue is the extent to which you should use mocks and stubs to fake the system beyond the component you wish to test. Some insist that you should isolate your component as much as possible and mock everything around it. This works for me on some projects.

 

In others, however, I have found that maintaining a system of mocks can become a time sink. Not only do you have the cost of keeping your tests in line with your system, but you must keep your mocks up-to-date.

 

Imagine changing the return type of a method. If you fail to update the method of the corresponding stub object to return the new type, client tests may pass in error. With a complex fake system, there is a real danger of bugs creeping into mocks. Debugging tests is frustrating to work, especially when the system itself is not at fault.

 

I tend to play this by ear. I use mocks and stubs by default, but I’m unapologetic about moving to real components if the costs begin to mount up.

 

You may lose some focus on the test subject, but this comes with the bonus that errors originating in the component’s context are at least real problems with the system. You can, of course, use a combination of real and fake elements. I routinely use an in-memory database in test mode, for example.

 

As you may have gathered, I am not an ideologue when it comes to testing. I routinely “cheat” by combining real and mocked components; and because priming data is repetitive, I often centralize test fixtures into what Martin Fowler calls Object Mothers.

 

These classes are simple factories that generate primed objects for the purpose of testing. Shared fixtures of this sort are anathema to some.

 

Having pointed out some of the problems that testing may force you to confront, it is worth reiterating a few points that, for my money, trump all objections. Testing accomplishes several things:

  • It helps you prevent bugs (to the extent that you find them during development and refactoring)
  • It helps you discover bugs (as you extend test coverage)
  • It encourages you to focus on the design of your system
  • It lets you improve code design with less fear that changes will cause more problems than they solve
  • It gives you confidence when you ship code
  • In every project for which I’ve written tests, I’ve had occasion to be grateful for that fact sooner or later.

 

Automated Build with Phing

Automated Build with Phing

If version control is one side of the coin, then the automated build is the other. Version control allows multiple developers to work collaboratively on a single project. With many coders each deploying a project in her own space, automated build soon becomes essential.

 

One developer may have her web-facing directory in /usr/local/apache/htdocs; another might use /home/bibble/public_html. Developers may use different database passwords, library directories, or mail mechanisms.

 

A flexible codebase might easily accommodate all of these differences, but the effort of changing settings and manually copying directories around your file system to get things working would soon become tiresome—especially if you need to install code in progress several times a day (or several times an hour).

 

You have already seen that Composer automates package installation. You’ll almost certainly want to deliver a project to an end user via a Composer or PEAR package because that mechanism is straightforward for the user and because package-management systems handle dependencies. But there’s a lot of work that might need automating before a package has been created.

 

You may need to generate template-generated code, for example. You should run tests and provide mechanisms for creating and updating database tables. Finally, you may want to automate the creation of a production-ready package. In this blog, I introduce you to Phing, which handles just such jobs. This blog will cover the following:

 

Getting and installing Phing: Who builds the builder?

  • Properties: Setting and getting data
  • Types: Describing complex parts of a project
  • Targets: Breaking a build into callable, interdependent sets of functionality
  • Tasks: The things that get stuff done

 

What Is Phing?

What Is Phing?

Phing is a PHP tool for building projects. It is very closely modeled on the hugely popular (and very powerful) Java tool called Ant. Ant was so named because it is small but capable of constructing things that are very large indeed.

 

Both Phing and Ant use an XML file (usually named build.xml) to determine what to do in order to install or otherwise work with a project.

 

The PHP world really needs a good build solution. Serious developers have had a number of options in the past. First, it is possible to use make, the ubiquitous Unix build tool that is still used for most C and Perl projects. 

 

However, make is extremely picky about syntax and requires quite a lot of shell knowledge, up to and including scripting—this can be challenging for some PHP programmers who have not come to programming via the Unix or Linux command line.

 

What’s more, make provides very few built-in tools for common build operations such as transforming file names and contents. It is really just a glue for shell commands.

 

This makes it hard to write programs that will install across platforms. Not all environments will have the same version of make or even have it at all. Even if you have made, you may not have all the commands the makefile (the configuration file that drives make) requires.

 

Phing’s relationship with make is illustrated in its name: Phing stands for PHing Is Not Gnu make. This playful recursion is a common coder’s joke (for example, GNU itself stands for Gnu is Not Unix). 

 

Phing is a native PHP application that interprets a user-created XML file in order to perform operations on a project. Such operations would typically involve the copying of files from a distribution directory to various destination directories, but there is much more to Phing.

 

Phing can be used to generate documentation, run tests, invoke commands, run arbitrary PHP code, create packages, replace keywords in files, strip comments, and generate tar/gzipped package releases. Even if Phing does not yet do what you need, it is designed to be easily extensible.

 

Because Phing is itself a PHP application, all you need to run it is a recent PHP engine. As Phing is an application for installing PHP applications, the presence of a PHP executable is a reasonably safe bet.

 

Getting and Installing Phing

Installing Phing

If it is difficult to install an install tool, then something is surely wrong! However, assuming that you have PHP 5 or better on your system (and if you haven’t, this isn’t the blog for you!), installation of Phing could not be easier.

 

You can acquire and install Phing with two simple commands:

$ pear channel-discover Phing PEAR channel

$ pear install phing/phing

 

This will install Phing as a PEAR package. You should have write permission for your PEAR directories, which, on most Unix or Linux systems, will mean running the command as the root user.

Note At the time of this writing, Phing installed with Pear is broken due to a namespacing issue. This problem will likely be resolved by the time you read this. If not, however, we have had good results using Composer.

 

You can also install Phing with Composer. You should add this to your composer.json file:

{
"require-dev": {
"phing/phing": "2.*"
}
}

If you run into any installation problems, you should visit the download page at a PHP build tool trac/wiki/Users/Download. You will find plenty of installation instructions there.

 

Composing the Build Document

 Build Document

You should now be ready to get cracking with Phing! Let’s test things out:

$ phing -v

Phing 2.14.0

The -v flag to the phing command causes the script to return version information. By the time you read this, the version number may have changed, but you should see a similar message when you run the command on your system.

 

Note If you installed Phing using Composer, the runnable script file will be installed in your local vendor/bin/ directory. To run Phing you should either add this directory to your $PATH environment variable or use an explicit path to the executable.

 

Now I’ll run the phing command without arguments:

$ phing

Buildfile: build.xml does not exist!

 

As you can see, Phing is lost without instructions. By default, it will look for a file called build.xml. Let’s build a minimal document so that we can at least make that error message go away:

<?xml version="1.0"?>
<project name="megaquiz" default="main">
<target name="main"/>
</project>

 

This is the bare minimum you can get away within a build file. If we save the previous example as build.xml and run phing again, we should get some more interesting output:

$ phing
Buildfile: /home/bob/working/megaquiz/build.xml
Warning: target 'main' has no tasks or dependencies
megaquiz > main:
BUILD FINISHED
Total time: 1.3404 second

 

A lot of effort to achieve precisely nothing, you may think, but we have to start somewhere! As you can see, Phing also helpfully points out there’s nothing very useful about this build file. Look again at that build file.

Because we are dealing with XML, I include an XML declaration. As you probably know, XML comments look like this:

<!-- this is an XML comment. OK? -->

 

So, because it’s a comment, the second line in my build file is ignored. You can put as many comments as you like in your build files, and as they grow, you should make full use of this fact. Large build files can be hard to follow without suitable comments.

 

The real start of any build file is the project element. The project element can include up to five attributes. Of these, name and default are compulsory.

 

The name attribute establishes the project’s name; default defines a target to run if none are specified on the command line. An optional description attribute can provide summary information.

 

You can specify the context directory for the build using a basedir attribute. If this is omitted, the current working directory will be assumed. Finally, you can specify the minimum version of the Phing application with which the build file should work using phing Version.

 

Once I have defined a project element, I must create at least one target—the one I reference in the default attribute.

 

Targets

Targets

Targets are similar, in some senses, to functions. A target is a set of actions grouped together to achieve an objective: to copy a directory from one place another, for example, or to generate documentation.

 

In my previous example, I included a bare-minimum implementation for a target:

<target name="main"/>

 

As you can see, a target must define at least a name attribute. I have made use of this in the project element. Because the default element points to the main target, this target will be invoked whenever Phing is run without command-line arguments. This was confirmed by the output:

megaquiz > main:

Targets can be organized to depend on one another. By setting up a dependency between one target and another, you tell Phing that the first target should not run before the target it depends on has been run. Now, I’ll add a dependency to my build file:

<?xml version="1.0"?>
<project name="megaquiz" default="main">
<target name="runfirst" />
<target name="runsecond" depends="runfirst"/>
<target name="main" depends="runsecond"/>
</project>

 

As you can see, I have introduced a new attribute for the target element. depends tells Phing that the referenced target should be executed before the current one, so I might want a target that copies certain files to a directory to be invoked before one that runs a transformation on all files in that directory.

 

I added two new targets in the example:

 runsecond, on which main depends; and runfirst, on which runsecond depends. 

Here’s what happens when I run Phing with this build file:

$ phing
Buildfile: /home/bob/working/megaquiz/build.xml
Warning: target 'runfirst' has no tasks or dependencies
megaquiz > runfirst:
megaquiz > runsecond:
megaquiz > main:
BUILD FINISHED
Total time: 0.3029 seconds

 

As you can see, the dependencies are honored.

Phing encounters the main target, sees its dependency, and moves back to runsecond.

runsecond has its own dependency, and Phing invokes runfirst.


Having satisfied its dependency, Phing can invoke runsecond.

Finally, main is invoked.

The depends attribute can reference more than one target at a time.

A comma-separated list of dependencies can be provided, and each will be honored in turn.

Now that I have more than one target to play with, I can override the project element’s default attribute from the command line:

$ phing runsecond
Buildfile: /home/bob/working/megaquiz/build.xml
Warning: target 'runfirst' has no tasks or dependencies
megaquiz > runfirst:
megaquiz > runsecond:
BUILD FINISHED
Total time: 0.2671 seconds

 

By passing in a target name, I cause the default attribute to be ignored. The target matching my argument is invoked instead (as well as the target on which it depends). This is useful for invoking specialized tasks, such as cleaning up a build directory or running post-install scripts.

The target element also supports an optional description attribute, to which you can assign a brief description of the target’s purpose:

?xml version="1.0"?>
<project name="megaquiz" default="main" description="A quiz engine">
<target name="runfirst" description="The first target" />
<target name="runsecond" depends="runfirst" description="The second target" />
<target name="main" depends="runsecond" description="The main target" />
</project>

 

Adding a description to your targets makes no difference to the normal build process. If the user runs

Phing with a -projecthelp flag, however, the descriptions will be used to summarize the project:

$ phing -projecthelp
Buildfile: /home/bob/working/megaquiz/build.xml
Warning: target 'runfirst' has no tasks or dependencies
A quiz engine
Default target:
-------------------------------------------------------------------------------
main The main target
Main targets:
-------------------------------------------------------------------------------
main The main target
runfirst The first target
runsecond The second target

 

Notice that I added the description attribute to the project element, too. If you want to hide a target from a listing like this, you can add a hidden attribute. This is useful for targets that provide housekeeping functionality, but which should not be invoked directly from the command line:

<target name="housekeeping" hidden="true">

<!-- useful things that should not be called directly --> </target>

 

Properties

Properties

Phing allows you to set such values using the property element. Properties are similar to global variables in a script. As such, they are often declared toward the top of a project to make it easy for developers to work out what’s what in the build file. Here I create a build file that works with database information:

<?xml version="1.0"?>
<project name="megaquiz" default="main">
<property name="dbname" value="megaquiz" />
<property name="db_pass" value="default" />
<property name="dbhost" value="localhost" />
<target name="main">
<echo>database: ${dbname}</echo>
<echo>pass: ${db_pass}</echo>
<echo>host: ${dbhost}</echo>
</target>
</project>

 

I introduced a new element: property. the property requires name and value attributes. Notice also that I have added this to the main target. echo is an example of a task. I will explore tasks more fully in the next section.

 

For now, though, it’s enough to know that the echo does exactly what you would expect—it causes its contents to be output.

 

Notice the syntax used to reference the value of a property here. By using a dollar sign, and wrapping the property name in curly brackets, you tell Phing to replace the string with the property value:

${propertyname}

All this build file achieves is to declare three properties and to print them to standard output. Here it is in action:

$ phing
Buildfile: /home/bob/working/megaquiz/build.xml
megaquiz > main:
[echo] database: megaquiz
[echo] pass: default
[echo] host: localhost
BUILD FINISHED
Total time: 0.4402 seconds

 

Now that I have introduced properties, I can wrap up my exploration of targets. The target element accepts two additional attributes: if and unless. Each of these should be set with the name of a property. When you use if, with a property name, the target will only be executed if the given property is set.

 

If the property is not set, the target will exit silently. Here, I comment out the db_pass property and make the main task require it using the if attribute:

<property name="dbname" value="megaquiz" />
<property name="dbhost" value="localhost" />
<target name="main" if="db_pass">
<echo>database: ${dbname}</echo>
<echo>pass: ${db_pass}</echo>
<echo>host: ${dbhost}</echo>
</target>

Let’s run phing again:

$ phing


Buildfile: /home/bob/working/megaquiz/build.xml megaquiz > main:

BUILD FINISHED

Total time: 0.2628 seconds

 

As you can see, I have raised no error, but the main task did not run. Why might I want to do this? There is another way of setting properties in a project.

 

They can be specified on the command line. You tell Phing that you are passing it a property with the -D flag followed by a property assignment. So the argument should look like this:

-Dname=value

In my example, I want the db_name property to be made available via the command line:

$ phing -Ddb_pass=userset
Buildfile: /home/bob/working/megaquiz/build.xml
megaquiz > main:
[echo] database: megaquiz
[echo] pass: userset
[echo] host: localhost
BUILD FINISHED
Total time: 0.4611 seconds

 

The if the attribute of the main target is satisfied that the db_pass property is present, and the target is allowed to execute.

As you might expect, the unless attribute is the opposite of it. If a property is set and it is referenced in a target’s unless attribute, then the target will not run.

 

This is useful if you want to make it possible to suppress a particular target from the command line. So I might add something like this to the main target:

<target name="main" unless="suppressmain"> main will be executed unless a suppressmain property is present:

$ phing -Dsuppressmain=yes I have wrapped up the target element; Table shows a summary of its attributes.

 

When a property is set on the command line, it overrides any and all property declarations within the build file. There is another condition in which a property value can be overwritten.

 

By default, if a property is declared twice, the original value will have primacy. You can alter this behavior by setting an attribute called override in the second property element. Here’s an example:

<?xml version="1.0"?>
<project name="megaquiz" default="main">
<property name="db_pass" value="default" />
<target name="main">
<property name="db_pass" override="yes" value="specific" />
<echo>pass: ${db_pass}</echo>
</target>
</project>

 

I set a property called db pass, giving it the initial value "default". In the main target, I set the property once again, adding an override attribute set to "yes" and providing a new value. The new value is reflected in the output:

$ phing


Buildfile: /home/bob/working/megaquiz/build.xml

megaquiz > main:

[echo] pass: specific

BUILD FINISHED

 

If I had not set the override element in the second property element, the original value of "default" would have stayed in place. It is important to note that targets are not functions: there is no concept of local scope.

 

If you override a property within a task, it remains overridden for all other tasks throughout the build file. You could get around this, of course, by storing a property value in a temporary property before overriding, and then resetting it when you have finished working locally.

 

So far, I have dealt with properties that you define yourself. Phing also provides built-in properties. You reference these in exactly the same way that you would reference properties you have declared yourself. Here’s an example:

<?xml version="1.0"?>
<project name="megaquiz" default="main">
<target name="main">
<echo>name: ${phing.project.name}</echo>
<echo>base: ${project.basedir}</echo>
<echo>home: ${user.home}</echo>
<echo>pass: ${env.db_pass}</echo>
</target>
</project>

 

I reference just a few of the built-in Phing properties. phing.project.name resolves to the name of the project as defined in the name attribute of the project element; project.basedir gives the starting directory, and user.home provides the executing user’s home directory (this is useful for providing default install locations).

 

Finally, the env prefix in a property reference indicates an operating system environment variable. So by specifying ${env.db_pass}, I am looking for an environment variable called db_pass. Here I run Phing on this file:

$ phing


Buildfile: /home/bob/working/megaquiz/build.xml

megaquiz > main: 
[echo] name: megaquiz
[echo] base: /home/bob/working/megaquiz
[echo] home: /home/bob
[echo] pass: ${env.db_pass}

BUILD FINISHED

Total time: 0.1120 seconds

 

Notice that the final property has not been translated. This is the default behavior when a property is not found—the string referencing the property is left untransformed. If I set the db_pass environment variable and run again, I should see the variable reflected in the output:

$ export db_pass=wooshpoppow
$ phing
Buildfile: /home/bob/working/megaquiz/build.xml
megaquiz > main:
...
[echo] pass: whooshpoppow
BUILD FINISHED
Total time: 0.2852 seconds

 

So now you have seen three ways of setting a property: the property element, a command-line argument, and an environment variable.

 

There is a fourth approach that complements these. You can use a separate file to specify property values. As my projects grow in complexity, I tend to favor this approach. Let’s return to a basic build file:

 <?xml version="1.0"?>
<project name="megaquiz" default="main">
<target name="main">
<echo>database: ${dbname}</echo>
<echo>pass: ${db_pass}</echo>
<echo>host: ${dbhost}</echo>
</target>
</project>

 

As you can see, this build file simply outputs properties without first declaring them, or checking that their values exist. This is what I get when I run this with no arguments:

$ phing
...
[echo] database: ${dbname}
[echo] pass: ${db_pass}
[echo] host: ${dbhost}
...
Now I’ll declare my properties in a separate file. I’ll call it megaquiz.properties:
dbname=filedb
db_pass=filepass
dbhost=filehost

 

Now I can apply this file to my build process with Phing’s property file option:

$ phing -propertyfile megaquiz.properties
...
[echo] database: filedb
[echo] pass: filepass
[echo] host: filehost
...

 

I find this mechanism much more convenient than managing long lists of command-line options. However, you do need to be careful not to check your property file into your version-control system!

 

You can use targets to ensure that properties are populated. Let’s say, for example, that my project requires a db pass the property. I would like the user to set db pass on the command line (this always has priority over other property-assignment methods). Failing that, I should look for an environment variable. Finally, I should give up and go for a default value:

<?xml version="1.0"?>
<project name="megaquiz" default="main">
<target name="setenvpass" if="env.db_pass" unless="db_pass"> <property name="db_pass" override="yes" value="${env.db_pass}" />
</target>
<target name="setpass" unless="db_pass" depends="setenvpass"> <property name="db_pass" override="yes" value="default" />
</target>
<target name="main" depends="setpass">
<echo>pass: ${db_pass}</echo>
</target>
</project>

So, as usual, the default target main is invoked first. 

This has a dependency set, so Phing goes back to the setpass target. setpass, though, depends on setenvpass,

so I start there. setenvpass is configured to run only if db_pass has not been set and if env.db_pass is present.

If these conditions are met, then I set the db_pass property using the property element.

At this stage then, db_pass is populated either by a command-line argument or by an environment variable.

If neither of these were present, then the property would remain unset at this stage.

The setpass target is now executed, but only if db_pass is not yet present. In this case, it sets the property to the default string: "default".

 

Conditionally Setting Property Values with the Condition Task

The previous example set up quite a complex assignment logic. More often, however, you’ll need a simple default value. The condition task allows you to set a property’s value based upon configurable conditions. Here is an example:

<?xml version="1.0"?>
<project name="megaquiz" default="main">
<condition property="db_pass" value="default">
<not>
<isset property="db_pass" />
</not>
</condition>
<target name="main">
<echo>pass: ${db_pass}</echo>
</target>
</project>

 

The condition task requires a property attribute. It also optionally accepts a value attribute, which is assigned to the property if the nested test clause resolves to true. If no value attribute is provided, then the property will be set to true if the nested test resolves to true.

 

The test clause is one of a number of tags, some of which, like not in this example, accept their own nested elements. I used the isset element, which returns true if the referenced property is set.

 

Because I want to assign a value to the db pass property if it is not set, I need to negate this result by wrapping it in the not tag. This inverts the resolution of the tag it contains. So, in terms of PHP syntax, the condition task in my example is analogous to this:

if (! isset($db_pass)) {

$db_pass = "default";

}

 

Types

You may think that having looked at properties, you are now through with data. In fact, Phing supports a set of special elements called types. These encapsulate different kinds of information useful to the build process.

 

FileSet

FileSet

Let’s say that you need to represent a directory in your build file. This is a common situation as you might imagine.

 

You could use a property to represent this directory, certainly, but you’d run into problems straightaway if your developers use different platforms that support distinct directory separators. The answer is the FileSet data type.

 

FileSet is platform independent, so if you represent a directory with forwarding slashes in the path, they will be automatically translated behind the scenes into backslashes when the build is run on a Windows machine. You can define a minimal fileset element like this:

<fileset dir="class='lazy' data-src/lib" />

 

As you can see, I use the dir attribute to set the directory I wish to represent. You can optionally add an id attribute so that you can refer to the fileset later on:

<fileset dir="class='lazy' data-src/lib" id="class='lazy' data-srclib">

 

The FileSet data type is particularly useful in specifying types of documents to include or exclude. When installing a set of files, you may not wish those that match a certain pattern to be included. You can handle conditions like this in an excludes attribute:

<fileset dir="class='lazy' data-src/lib" id="class='lazy' data-srclib"

excludes="**/*_test.php **/*Test.php" />

 

Notice the syntax I have used in the excludes attribute. Double asterisks represent any directory or subdirectory within class='lazy' data-src/lib. A single asterisk represents zero or more characters.

 

So I am specifying that I would like to exclude files that end in _test.php or Test.php in all directories below the starting point defined in the dir attribute. The excludes attribute accepts multiple patterns separated by white space.

 

I can apply the same syntax to an includes attribute. Perhaps my class='lazy' data-src/lib directories contain many non-PHP files that are useful to developers, but which should not find their way into an installation.

 

I could exclude those files, of course, but it might be simpler just to define the kinds of files I can include. In this case, if a file doesn’t end in .php, it isn’t going to be installed:

<fileset dir="class='lazy' data-src/lib" id="class='lazy' data-srclib"

excludes="**/*_test.php **/*Test.php"

includes="**/*.php" />

 

As you build up to include and exclude rules, your fileset element is likely to become overly long. Luckily, you can pull out individual exclude rules and place each one in its own exclude subelement. You can do the same for include rules. I can now rewrite my FileSet like this:

<fileset dir="class='lazy' data-src/lib" id="class='lazy' data-srclib">
<exclude name="**/*_test.php" />
<exclude name="**/*Test.php" />
<include name="**/*.php" />
</fileset>

You can see some of the attributes of the fileset element in Table

 

PatternSet

PatternSet

As you build up patterns in your fileset elements (and in others), there is a danger that you will begin to repeat groups of excluding and include elements. In my previous example, I defined patterns for test files and regular code files. I may add to these over time (perhaps I wish to include .conf and .inc extensions to my definition of code files).

 

If I define other fileset elements that also use these patterns, I will be forced to make any adjustments across all relevant fileset elements.

 

You can overcome this problem by grouping patterns into pattern set elements. The pattern set element groups include and exclude elements so that they can be referenced later from within other types. Here I extract the include and exclude elements from my fileset example and add them to pattern set elements:

<patternset id="inc_code">
<include name="**/*.php" />
<include name="**/*.inc" />
<include name="**/*.conf" />
</patternset>
<patternset id="exc_test">
<exclude name="**/*_test.php" />
<exclude name="**/*Test.php" />
</patternset>

 

I create two pattern set elements, setting their id attributes to inc_code and exc_test respectively. inc_code contains the include elements for including code files, and exc_test contains the exclude files for excluding test files. I can now reference these pattern set elements within a fileset:

<fileset dir="class='lazy' data-src/lib" id="class='lazy' data-srclib">
<patternset refid="inc_code" />
<patternset refid="exc_test" />
</fileset>

 

To reference existing patterns, you must use another pattern set element. The second element must set a single attribute: ref-id. The ref id attribute should refer to the id of the pattern set element you wish to use in the current context. In this way, I can reuse patterns elements across different fileset elements:

<fileset dir="class='lazy' data-src/views" id="class='lazy' data-srcviews">

<patternset refid="inc_code" />

</fileset>

 

Any changes I make to the inc_code patterns will automatically update any types that use it. As with FileSet, you can place exclude rules either in an excludes attribute or a set of exclude subelements.

 

FilterChain

FilterChain

The types that I have encountered so far have provided mechanisms for selecting sets of files. FilterChain, by contrast, provides a flexible mechanism for transforming the contents of text files. 

 

In common with all types, defining a filter chain element does not in itself cause any changes to take place. The element and its children must first be associated with a task—that is, an element that tells Phing to take a course of action. I will return to tasks a little later.

 

A filter chain element groups any number of filters together. Filters operate on files like a pipeline— the first alters its file and passes its results on to the second, which makes its own alterations, and so on. By combining multiple filters in a filter chain element, you can effect flexible transformations.

 

Here I dive straight in and create a filter chain that removes PHP comments from any text passed to it:

<filterchain>

<stripphpcomments />

</filterchain>

 

The StripPhpComments task does just what the name suggests. If you have provided detailed API documentation in your source code, you may have made life easy for developers, but you have also added a lot of dead weight to your project.

 

Because all the work that matters takes place within your source directories, there is no reason why you should not strip out comments on installation.

 

Note If you use a build tool for your projects, ensure that no-one makes changes in the installed code. The installer will copy over any altered files, and the changes will be lost. I have seen it happen.

 

Let’s sneak a peek at the next section and place the filter chain element in a task:

<target name="main">
<copy todir="build/lib">
<fileset refid="class='lazy' data-srclib"/>
<filterchain>
<stripphpcomments />
</filterchain>
</copy>
</target>

 

The Copy task is probably the one you get the most use out of. It copies files from place to place. As you can see, I define the destination directory in the dir attribute.

 

The source of the files is defined by the fileset element I created in the previous section. Then comes the filter chain element. Any file copied by the Copy task will have this transformation applied to it.

 

Phing supports filters for many operations, including stripping new lines (StripLineBreaks) and replacing tabs with spaces (TabToSpaces). There is even an XsltFilter for applying XSLT transformations to source files! Perhaps the most commonly used filter, however, is ReplaceTokens.

 

This allows you to swap tokens in your source code for properties defined in your build file, whether pulled from environment variables or passed in on the command line.

 

This is very useful for customizing an installation. It’s a good idea to centralize your tokens into a central configuration file for an easy overview of the variable aspects of your project.

 

Replace Tokens optionally accepts two attributes, begin token and end token. You can use these to define the characters that delineate token boundaries.

 

If you omit these, Phing will assume the default character of @. In order to recognize and replace tokens, you must add token elements to the replace tokens element. Now I’ll add a replace tokens element to my example:

<copy todir="build/lib">
<fileset refid="class='lazy' data-srclib"/>
<filterchain>
<stripphpcomments />
<replacetokens>
<token key="dbname" value="${dbname}" />
<token key="dbhost" value="${dbhost}" />
<token key="db_pass" value="${db_pass}" />
</replacetokens>
</filterchain>
</copy>
As you can see, token elements require key and value attributes. Let’s see the effect of
running this task with its transformations on a file in my project. The original file lives in a source directory, class='lazy' data-src/lib/Config.php:
/**
Quick and dirty Conf class
**/
class Config {
public $dbname ="@dbname@"; public $db_pass ="@db_pass@"; public $dbhost ="@dbhost@";
}
Running my main target containing the Copy task defined previously gives the following output:
$ phing
Buildfile: /home/bob/working/megaquiz/build.xml
megaquiz > main:
[copy] Copying 8 files to /home/bob/working/megaquiz/build/lib
BUILD FINISHED
Total time: 0.1413 seconds
The original file is untouched, of course, but thanks to the Copy task, it has been reproduced at build/ lib/Config.php:
class Config {
public $dbname ="megaquiz";
public $db_pass ="default";
public $dbhost ="localhost";
}

Not only has the comment been removed, but the tokens have been replaced with their property equivalents.

 

Tasks

Tasks are the elements in a build file that get things done. You won’t achieve much without using a task, which is why I have cheated and used a couple already. I’ll reintroduce these.

 

Echo

The Echo task is perfect for the obligatory “Hello World” example. In the real world, you can use it to tell the user what you are about to do or what you have done. You can also sanity-check your build process by displaying the values of properties.

 

As you have seen, any text placed within the opening and closing tags of an echo element will be printed to the browser:

<echo>The pass is '${db_pass}', shhh!</echo>
Alternatively, you can add the output message to a msg attribute:
<echo msg="The pass is '${db_pass}', shhh!" />
This will have the identical effect of printing the following to standard output:
[echo] The pass is 'default', shhh!

Recommend