Control Structures In Phing

2nd June 2013

Phing has a few different tasks and elements that allow you to select paths of code execution depending on what you need to happen in a build file. These are limited to loops and if statements, but a lot of functionality can be covered with just a couple of lines of XML.

Foreach Task

The foreach task iterates over a list of items, and/or fieldsets. For every item found in the loop a phing target will be called with the item in the list passed as a parameter to the target. The foreach target requires the following two attributes.

  • target : This is the phing target to call on each parameter found in the param attribute.
  • param : This is the parameter that will be passed to the target found in the target attribute.

The param attribute can contain either a property or a placeholder for a fileset. The property can be split in the same way as the explode() PHP function to split the string into an array. You can use the delimiter attribute to alter the separator, which defaults to a comma (,), but you must also include a list attribute to let the foreach target know what property to use.

Here is a simple example of a foreach task that loops through a series of numbers and prints them out in turn.

  1. <?xml version="1.0"?>
  2. <project name="foreachtest" default="foreachtest">
  3.  
  4. <target name="foreachtest">
  5. <property name="list" value="1,2,3" />
  6. <foreach list="${list}" param="item" target="itemtarget" delimiter="," />
  7. </target>
  8.  
  9. <target name="itemtarget">
  10. <echo>${item}</echo>
  11. </target>
  12.  
  13. </project>

This produces the following output.

  1. foreachtest > foreachtest:
  2.  
  3. [foreach] Calling Buildfile '/foreach.xml' with target 'itemtarget'
  4.  
  5. foreachtest > itemtarget:
  6.  
  7. [echo] 1
  8. [foreach] Calling Buildfile '/foreach.xml' with target 'itemtarget'
  9.  
  10. foreachtest > itemtarget:
  11.  
  12. [echo] 2
  13. [foreach] Calling Buildfile '/foreach.xml' with target 'itemtarget'
  14.  
  15. foreachtest > itemtarget:
  16.  
  17. [echo] 3
  18.  
  19. BUILD FINISHED
  20.  
  21. Total time: 0.0632 seconds

The foreach target can also include a fileset or a mapper element as a nested argument. If this is the case then the parameter attribute will be used as a placeholder for the output of the nested elements. You can either create a new fileset within the foreach task, or just add a reference to one. The following Phing project creates a fileset and passes this to a foreach target, which prints each filename in turn. The directory I am using here is a common sounds directory found on most Debian/Ubuntu systems and so should produce some sort of result.

  1. <?xml version="1.0"?>
  2. <project name="foreachtest" default="foreachtest">
  3.  
  4. <target name="foreachtest">
  5. <fileset dir="/usr/share/sounds/alsa/" id="thefiles">
  6. <include name="**" />
  7. </fileset>
  8.  
  9. <foreach param="filename" target="foreachfile">
  10. <fileset refid="thefiles" />
  11. </foreach>
  12. </target>
  13.  
  14. <target name="foreachfile">
  15. <echo>${filename}</echo>
  16. </target>
  17. </project>

The idea here is that you would do more than just print out the name of the file. You could change the permissions of the file or perhaps something more involved. The benefits of using this approach mean that you have a target that does a single action on a file that can be used anywhere in your project, not just as the target of the foreach loop.

Conditions

Conditions are nested elements that are used in condition and if tasks to evaluate expressions and need to be understood before those elements can be used. Essentially, a conditional will evaluate something and return true or false. They can be split into comparators that compare two values and modifiers that change the output of a comparison in some way.

None of the comparators work on their own. Instead they are used in the condition and if tasks in order to drive those conditional statements. It is essential to understand these elements before looking at the condition and if elements.

Comparators

This consists of a series of comparison operators that compare one or more values and return a boolean result. The tables in this section were taken directly from the official Phing support documentation on conditions.

equals

This is the basic comparator and essentially compares two strings (provided by the arg1 and arg2 parameters) to one another. If the two strings are identical then the element returns true. The casesensitive parameter can be used to force the comparison to be case or non case sensitive. The trim parameter can also be used to trim whitespace from the arguments before they are compared. The two arguments can also accept properties as arguments. All of the following examples will return true.

  1. <equals arg1="text" arg2="text" />
  2. <equals arg1="TEXT" arg2="text" casesensitive="false" />
  3. <equals arg1="TEXT" arg2=" text " trim="true" casesensitive="false" />
  4.  
  5. <property name="property1" value="TExt" />
  6. <equals arg1="${property1}" arg2="text " trim="true" casesensitive="false" />
Attribute Description Required
arg1 First string to test. Yes
arg2 Second string to test. Yes
casesensitive Perform a case sensitive comparison. Default is true. No
trim Trim whitespace from arguments before comparing them. Default is false. No
isset

The isset element returns true if a property has been set within the project scope. This is useful for checking to see if certain properties have been assigned before trying to use them. If the property hasn't been set then a property prompt or even a failure event can be used to act accordingly.

  1. <property name="property1" value="text" />
  2. <isset property="property1" />
Attribute Description Required
property The name of the property to test. Yes
contains

This element tests to see if one string contains another string. The string attribute is the string to search in and the substring attribute is the string to search for. If the string attribute contains substring then the condition will return true. The casesensitive attribute is a boolean value that can be used to turn on or off case sensitivity, the default being on. All of the following examples return true.

  1. <contains string="some string" substring="me str" />
  2. <contains string="SOME string" substring="me str" casesensitive="false" />
  3. <contains string="abcdef" substring="a" />
  4. <contains string="aBcDe" substring="aB" />
  5. <contains string="aBcDe" substring="ab" casesensitive="false" />
Attribute Description Required
string The string to search in. Yes
substring The string to search for. Yes
casesensitive Perform a case sensitive comparision. Default is true. No
istrue

This tests to see if a string evaluates to true. This uses standard PHP boolean string casting so any non-empty string will equate to true, whereas values like 'false', 0 or empty strings equate to false. All of the following examples will return true.

  1. <istrue value="1" />
  2. <istrue value="true" />
  3. <istrue value="yes" />
  4. <istrue value=" " />
Attribute Description Required
value value to test Yes
isfalse

This is essentially the negative of istrue, but will return true if the value is false. All of the following examples will return true.

  1. <isfalse value="0" />
  2. <isfalse value="false" />
  3. <isfalse value="" />
Attribute Description Required
value value to test Yes
os

This tests to see if an operating system is of a given type. The element takes one attribute of family which can have one of three values. For Microsoft Windows based systems this is 'windows', for Apple Macintosh systems this is 'mac' and for Unix-like operating systems this is 'unix'. For example, if the family attribute is 'unix' and the script was being run on Ubuntu then this would return true. This is a pretty good way of running different functionality on different platforms and the options for each system are pretty simple.

<os family="unix" />
Attribute Description Required
family The name of the operating system family to expect. Yes
referenceexists

The referenceexists condition tests to see if a specified reference exists. A reference is a Phing type that references files, which includes filset, filelist, filterchains, chains, and file mappers.

The following will return true because the filset reference 'someid' exists.

  1. <fileset id="someid">
  2. <include name="*.php" />
  3. </fileset>
  4.  
  5. <referenceexists ref="someid" />
Attribute Description Required
ref reference to test for Yes
available

The available comparison is used to test to see if a file or directory exists. The file attribute details the file or directory name to be looked for. This can be swapped for the resource attribute to look for a specific path or the extension attribute to find a specific extension. The type attribute is used to differentiate between files and directories and the filepath attribute dictates which path should be used to look for the file.

This comparator works in the same way as the available task with the exception that the property and value attributes do nothing and will be ignored. It is a good way of double checking that things have worked correctly during the build. If the primary function of the build file is to create a compressed file then a check should be done to make sure the compressed file exists.

Using the following should always return true as this will find the build file.

<available extension="xml" />

The following will detect the /home directory on a Unix based system.

<available file="/home" type="dir" />

The following will return true if the the file index.php is within the directory /some/path.

<available file="index.php" type="file" filepath="/some/path/" />
Name Type Description Default Required
file String File/directory to check existence. n/a Yes (or resource or extension)
resource String Path of the resource to look for. n/a Yes (or file or extension)
extension String Name of the extension to look for. n/a Yes (or file or resource)
type String (file|dir) Determines if AvailableTask should look for a file or a directory at the position set by file. If empty, it checks for either file or directory. n/a No
filepath String The path to use when looking up file. n/a No
followSymlinks Boolean Whether to dereference symbolic links when looking up file. false No

Modifiers

Modifiers are used to modify the result of a comparison in some way. They do not compare things themsevles but they can be used to group together multiple comparisons or to negate a comparison. None of the comparison modifiers have any arguments.

not

This is the simplest form of modifier and will just negate the result of the comparison. This is a good way of testing to see if a property is set and acting accordingly using the isset comparison. The not comparison modifier cannot accept more than one condition, but it is possible to nest multiple comparisons within another comparison modifier and negate that. The following example will negate the result of the istrue comparison of false, which essentially returns true.

  1. <not>
  2. <istrue value="false" />
  3. </not>

The following is an example of nesting multiple comparisons into an and comparison modifier and then negating the result using the not modifier. In this case the result of the and comparison is false, but this negates and so the overall result is true.

  1. <not>
  2. <and>
  3. <istrue value="false" />
  4. <istrue value="0" />
  5. </and>
  6. </not>
and

The and comparison modifier compares the result of multiple nested comparisons. The result is true if all of the inner comparisons are equal to true. The conditions will be evaluated in the order they appear in the build file. As soon as a condition is evaluated to false then no other conditions are evaluated in the rest of the and element. The following is an example of an and element comparing multiple nested comparison elements to return true.

  1. <property name="property1" value="text" />
  2.  
  3. <and>
  4. <equals arg1="text" arg2="text" />
  5. <isset property="property1" />
  6. <contains string="some string" substring="me str" />
  7. <istrue value="1" />
  8. <isfalse value="0" />
  9. </and>
or

This comparison modifier will evaluate multiple nested comparisons and return true if at least one of those conditions is true. The comparisons will be evaluated in the order they are found in the build file. When a true value is found the element will return true immediately and no other comparisons will be evaluated. The following example will evaluate the nested conditions and return true because at least one of them returns a true value.

  1. <or>
  2. <equals arg1="text" arg2="TEXT" />
  3. <contains string="some string" substring="me str" />
  4. </or>

Condition Task

The condition task is used to set a property if a condition is true. By default this property is set to be a boolean (i.e. the result of the comparison) but this can be changed by using the value attribute. The condition task must have exactly one nested comparison, although comparison modifiers (such as and) can be used when multiple comparisons need to be done. If the condition is not met then the property is not set. Here are a few examples of the condition task in action.

The following example sets the property 'textisthesame' to be true, based on the output of the equals comparison.

  1. <condition property="textisthesame">
  2. <equals arg1="text" arg2="text" />
  3. </condition>

The following example sets the property 'sametextandmac' to be true, based on the output of both the equals comparison and the build being run on a Mac computer.

  1. <condition property="sametextandmac">
  2. <and>
  3. <equals arg1="text" arg2="text" />
  4. <os family="mac" />
  5. </and>
  6. </condition>

The following example sets the property 'textisthesame' to be the string 'text' due to the value attribute being used in the condition task.

  1. <condition property="textisthesame" value="text">
  2. <equals arg1="text" arg2="text" />
  3. </condition>

The following example will not set the property because the result of the equals comparison is false. If you try to use this property elsewhere in the build file you will just print the name of the property.

  1. <condition property="textisthesame">
  2. <equals arg1="text" arg2="notthesame" />
  3. </condition>
Name Type Description Default Required
property String The name of the property to set. n/a Yes
value String The value to set the property to. Defaults to "true". true No

If Task

http://www.phing.info/docs/guide/stable/chapters/appendixes/AppendixB-C…

The if task is a bit more complex than the condition task in that it is able to direct the flow of the execution of the build file. It works in much the same way that an if statement is used in normal programming, although it can be a little difficult to understand at first due to the nested XML syntax. The if task has no attributes and on it's own doesn't really do all that much, the real work is done by a comparison and the then, else, and elseif child elements. These child elements direct the flow of the build file depending on the outcome of the if comparison. The then and else child elements must be used only once within an if statement.

The elseif element acts like a sub if task and so must also have it's own comparison along with an then or elseif elements. The only exception here is that the elseif element must not contain an else element. It is also possible to nest if tasks to create complex comparisons. Here are a few examples of the if statement in action.

The following if task compares two properties and prints a message if they are the same.

  1. <property name="text1" value="text" />
  2. <property name="text2" value="text" />
  3.  
  4. <if>
  5. <equals arg1="${text1}" arg2="${text2}" />
  6. <then>
  7. <echo message="The two strings are the same." />
  8. </then>
  9. </if>

The following is an example of using the and condition modifier in an if task. This just tests to see if two values are false and prints a message if they are.

  1. <if>
  2. <and>
  3. <isfalse value="false" />
  4. <isfalse value="0" />
  5. </and>
  6. <then>
  7. <echo message="The two values are false." />
  8. </then>
  9. </if>

The following if task will print a message depending on whether or not it is run on Windows.

  1. <if>
  2. <os family="windows" />
  3. <then>
  4. <echo>You are running this build file on Windows.</echo>
  5. </then>
  6. <else>
  7. <echo>You are not running this build file on Windows</echo>
  8. </else>
  9. </if>

The following if task will detect what operating system the build file is being run on and print a message. Notice that the elseif element here contains it's own comparison and then child element in order to compare and act on the comparison.

  1. <if>
  2. <os family="windows" />
  3. <then>
  4. <echo message="This build file is being run on Windows.'" />
  5. </then>
  6.  
  7. <elseif>
  8. <os family="mac" />
  9. <then>
  10. <echo message="This build file is being run on OSX.'" />
  11. </then>
  12. </elseif>
  13.  
  14. <else>
  15. <echo message="This build file is being run on Linux.'" />
  16. </else>
  17. </if>

The following if statement sets a property with a default value if the property doesn't exist. This is a good way of allowing properties to be passed to build files but to also have them available if the property isn't present.

  1. <if>
  2. <not>
  3. <isset property="docs_path" />
  4. </not>
  5. <then>
  6. <resolvepath propertyName="docs_path" file="/some/static/path" />
  7. </then>
  8. </if>

The following is an example taken from a Drupal build file that puts the apc.php file into the correct place for it to be used by the system. It is an example of using an if statement with a then, elseif, and else element.

On some platforms the acp.php file is kept as a compressed 'gz' file that must be extracted. This file exists then the file is copied, to the destination directory before being extracted. The copied compressed file is then deleted as it is not needed. If the apc.php file is present then it is just copied into the correct place. Finally, the build file will announce if it is unable to find the file on the current system.

  1. <if>
  2. <available file="/usr/share/doc/php-apc/apc.php.gz" />
  3. <then>
  4. <echo>Compressed apc file found, extracting.</echo>
  5. <mkdir dir="/docroot/sites/all/libraries/APC/" />
  6. <copy file="/usr/share/doc/php-apc/apc.php.gz" tofile="/docroot/sites/all/libraries/APC/apc.php.gz" overwrite="true" />
  7. <exec command="gunzip apc.php.gz" dir="/docroot/sites/all/libraries/APC/" />
  8. <copy todir="${make_path}/docroot/sites/all/libraries/APC/">
  9. <mapper type="glob" from="apc.php" to="apc.php.inc"/>
  10. <fileset dir="/docroot/sites/all/libraries/APC/">
  11. <include name="apc.php" />
  12. </fileset>
  13. </copy>
  14. <delete file="/docroot/sites/all/libraries/APC/apc.php" />
  15. </then>
  16.  
  17. <elseif>
  18. <available file="/usr/share/doc/php-apc/apc.php" />
  19. <then>
  20. <echo>Uncompressed apc file found, copying</echo>
  21. <mkdir dir="/docroot/sites/all/libraries/APC/" />
  22. <copy file="/usr/share/doc/php-apc/apc.php" tofile="/docroot/sites/all/libraries/APC/apc.php.inc" overwrite="true" />
  23. </then>
  24. </elseif>
  25.  
  26. <else>
  27. <echo>APC not installed on this system, skipping APC setup</echo>
  28. </else>
  29. </if>

The following is a silly example that shows the syntax used when nesting if tasks but it useful to see how nesting works. This example will print 'True' because each if comparison will be true.

  1. <if>
  2. <istrue value="true" />
  3. <then>
  4. <if>
  5. <istrue value="true" />
  6. <then>
  7. <if>
  8. <istrue value="true" />
  9. <then>
  10. <echo>True</echo>
  11. </then>
  12. </if>
  13. </then>
  14. </if>
  15. </then>
  16. </if>

 

Add new comment

The content of this field is kept private and will not be shown publicly.