We will start creating a rather simple task which basically does nothing more than echo a message to the screen. See [below] for the source code and the following [below] for the XML definition that is used for this task.
<?php use Phing\Task; class MyEchoTask extends Task { /** * The message passed in the buildfile. */ private $message = null; /** * Whether to reverse the message, for fun? */ private $reverse = false; /** * The setter for the attribute "message" */ public function setMessage($str) { $this->message = $str; } public function setReverse($str) { $this->reverse = StringHelper::booleanValue($str); } /** * The init method: Do init steps. */ public function init() { // nothing to do here } /** * The main entry point method. */ public function main() { if ($this->reverse) { print(strrev($this->message)); } else { print($this->message); } } } ?>
This code contains a rather simple, but complete Phing task. It is assumed that
the file is named MyEchoTask.php
. For
this example, we're assuming that the file is placed in
/home/example/classes
. We'll explain the source code
in detail shortly. But first we'd like to discuss how we should register the
task to Phing so that it can be executed during the build process.
The task shown [above] must somehow get loaded and called by Phing. Therefore it must be made available to Phing so that the buildfile parser is aware a correlating XML element and it's parameters. Have a look at the minimalistic buildfile example given in [the buildfile below] that does exactly this.
<?xml version="1.0" ?> <project name="test" basedir="." default="test.myecho"> <includepath classpath="/home/example/classes" /> <taskdef name="myecho" classname="MyEchoTask" /> <target name="test.myecho"> <myecho message="Hello World" reverse="yes"/> </target> </project>
To register the custom task with Phing, the
taskdef
element (line 5) is used. See Section B.60, “TaskdefTask” for
a more detailed explanation.
Optionally, before the taskdef
element,
the includepath
element adds a path
to PHP's include path. This is of course only required if the mentioned
path isn't already on the include path. See Section B.30, “IncludePathTask” for
a more detailed explanation.
Now, as we have registered the task by assigning a name and the worker class
([see source code above]) it is ready for usage within the
<target>
context (line 8). You see that we pass the
message that our task should echo to the screen via an XML attribute called
"message".
And for fun, if the "reverse" attribute is set to a "truth-like" value, the message will be reversed when displayed. So we get "dlroW olleH" displayed instead!
Now that you've got the knowledge to execute the task in a buildfile it's time to discuss how everything works.
All files containing the definition of a task class follow a common well formed structure:
Include/require statements to import all required classes
The class declaration and definition
The class's properties
The class's constructor
Setter methods for each XML attribute
The init()
method
The main()
method
Arbitrary private
(or
protected
)
class methods
Always include/require all the classes needed for this task in full written
notation. Furthermore you should always include phing/Task.php
at
the very top of your include block. Then include all other required system or
proprietary classes.
If you look at line 5 in [the source code of the task] you will find the
class declaration
. This will be familiar to you if you are
experienced with OOP in PHP (we assume here that you are). Furthermore there are
some fine-grained rules you must obey when creating the classes (see also,[naming
and coding standards]):
Your classname must be exactly like the taskname you are going to
implement plus the suffix "Task". In our example case the classname is
MyEchoTask
(constructed by the taskname
"myecho
" plus the suffix "task
").
The upper/lower case casing is currently only for better reading. However,
it is encouraged that you use it this way.
The task class you are creating must at least extend
"Task
" to inherit all task specific methods.
The next lines you are coding are class properties. Most of them are inherited
from the Task superclass, so there's not need to redeclare them. Nevertheless you
should declare
the following ones yourself:
Taskname. Always hard code the taskname
property that
equals the name of the XML element that your task claims. Currently this
information is not used - but it will be in the future.
Your arbitrary properties that reflect the XML attributes/elements which your task accepts.
In the MyEchoTask
example the coded properties can be found in
lines 7 to 11. Give you properties meaningful descriptive names that clearly state
their function within the context. A couple of properties are inherited from the
superclass that must not be declared in the properties part of the code.
For a list of inherited properties (most of them are reserved, so be sure not to
overwrite them with your own) can be found in the "Phing API Reference" in the
docs/api/
directory.
The next block that follows is the class's constructor. It must be present and
call at least the constructor or the parent class. Of course, you can add some
initialization data here. It is recommended that you define
your
prior declared properties here.
As you can see in the XML definition of our task ([see buildfile above] , line 9)
there is an attribute defined with the task itself, namely "message" with a value of
the text string that our task should echo. The task must somehow become aware of
the attribute name and the value. Therefore the setter methods
exist.
For each attribute you want to import to the task's namespace you have to define a method named exactly after the very attribute plus the string "set" prepended. This method accepts exactly one parameter that holds the value of the attribute. Now you can set the a class internal property to the value that is passed via the setter method.
In the setter method you should also perform any casting operations and/or check
if the attribute value is a valid value. If this is not the case, throw a
BuildException
. In some cases, such as when you have three
attributes and at least one of them should be set, you may want to check the
attribute values inside the init() or main() method.
In our example the setter is named setMessage
, because the
XML attribute the echo task accepts is "message". setMessage now takes the string
"Hello World" provided by the parser and sets the value of the internal class
property $strMessage
to "Hello World". It is now available to the
task for further disposal.
There is also another setter named setReverse
.This uses the
StringHelper::toBoolean static function to convert truthy values to a true/false value.
This helps keep our own code nice and simple.
Creator methods allow you to manage nested XML tags in your new Phing Task.
For example, you might be developing a task that would contain a nested "color" XML tag.
In this instance a creator method named createcolor
would be required.
<tag> <color red="..." green="..." blue="..."> </tag>
If the XML for the task and the subtag look like the above, the PHP code for it could look something like the following:
class TagTask extends Task { protected $colors = array(); public function createColor() { $colorObj = new TagColor(); $this->colors[] = $colorObj; return $colorObj; } } class TagColor { public function setRed($value) { } public function setGreen($value) { } public function setBlue($value) { } }
The init
method gets called when the
<taskname>
xml element closes. It must be implemented even
if it does nothing like in the example above. You can do init steps here required to
setup your task object properly. After calling the Init-Method the task object
remains untouched by the parser. Init should not perform operations related somehow
to the action the task performs. An example of using init may be cleaning up the
$strMessage variable in our example (i.e. trim($strMessage)
) or
importing additional workers needed for this task.
The init method should return true or an error object evaluated by the governing logic. If you don't implement init method, phing will shout down with a fatal error.
There is exactly one entry point to execute the task. It is called after
the complete buildfile has been parsed and all targets and tasks have been scheduled
for execution. From this point forward the very implementation of the tasks action
starts. In case of our example a message (imported by the proper setter method) is
Logged to the screen through the system's "Logger" service (the very action this
task is written for). The Log()
method-call in this case accepts
two parameters: a event constant and the message to log.
For the more or less simple cases (as our example) all the logic of the task is coded in the Main() method. However for more complex tasks common sense dictates that particular action should be swapped to smaller, logically contained units of code. The most common way to do this is separating logic into private class methods - and in even more complex tasks in separate libraries.
private function myPrivateMethod() { // definition }