Building PHP Projects with Phing

Now that I have NetBeans, Zend and XAMPP installed and configured (see this article), I moved on to find a build system for my project. I have used Ant for years and as luck would have it, there is an equivalent for PHP called Phing. The need for such a utility is multifold: document generation, unit testing, packaging your project for distribution, etc.

I decided to go ahead and install Phing using the version of Pear that came with XAMPP. I thought this approach would make it easier to keep my development environment synchronized. I like to copy my XAMPP installation to a USB drive to simplify setup on the other boxes that I use for development. This eliminates the need to remember what packages I have installed as well as having to mange multiple Apache configurations.

Installing Phing through Pear was straightforward:

cd /opt/lampp/bin
sudo ./pear channel-discover pear.phing.info
sudo ./pear install phing/phing

My next immediate need was to install PhpDocumentor. I used the ‘a’ switch to install any optional dependencies that PhpDocumentor may have:

sudo ./pear install -a PhpDocumentor

The installation failed with a memory error which was solved by editing /opt/lampp/etc/php.ini and changing the memory limit to 16MB:

;;;;;;;;;;;;;;;;;;;
; Resource Limits ;
;;;;;;;;;;;;;;;;;;;

max_execution_time = 30     ; Maximum execution time of each script, in seconds
memory_limit = 16M          ; Maximum amount of memory a script may consume (8MB)

Re-running the previous command now worked without incident. Now that I had Phing and PhpDocumentor installed, I outlined what tasks that I wanted to perform with the build system:

  1. Generate API documentation from the comments in my source files
  2. Build a production ready copy of my project with documentation
  3. Create a project archive for distribution
  4. Clean up any temporary files and directories

Now that I had defined what I wanted my build system to do, I needed to create a build properties file. This file will be used to define all the values needed to build my project. These values include items such as project directories, file names, application version information and many others. While these values can be defined in the build file itself using the <property> element, it is generally easier to place these values in a separate file for maintenance reasons.

I opened my NetBeans project and added a new file to the project root named build.properties:

###############################################################################
# PROJECT CONFIGURATION SETTINGS
###############################################################################

# Zend framework location
zend.dir = /opt/phplibs/ZendFramework

###############################################################################
# DO NOT CHANGE ANYTHING BELOW THIS LINE
###############################################################################

# Project settings
app.name = openEPRS

# Project directories
build.dir = ./build
docs.dir = ./docs
log.dir = ./var/logs

# Distribution settings
dist.name = openeprs
dist.version = 0.1.0
dist.dir = ./dist

# Distribution file sets
app.fileset = **/application/**
docs.fileset = **/docs/**
lib.fileset = **/library/**
log.fileset = **/var/**
public.fileset = **/public/**
scripts.fileset = **/scripts/**

# License file
lic.file = ./LICENSE

Now I was ready to create the build file. This is core to all the tasks that I had defined earlier for my project. A valid Phing build file has the following structure:

  1. A document prolog
  2. Exactly one root element called <project>
  3. Several Phing type elements (e.g. <property> , <fileset> , <patternset> etc.)
  4. One or more <target> elements containing built-in or user defined Phing task elements (e.g. <copy> , <tar> , etc)

I added another new file to the project root named build.xml:

<project basedir="." default="dist" name="openEPRS">
    
    <property file="./build.properties">
    
    
    <target name="clean-docs">
        <delete dir="${docs.dir}" failonerror="true" includeemptydirs="true" verbose="false">
    </delete></target>
    <target name="clean-build">
        <delete dir="${build.dir}" failonerror="true" includeemptydirs="true" verbose="false">
    </delete></target>
    <target name="clean-dist">
        <delete dir="${dist.dir}" failonerror="true" includeemptydirs="true" verbose="false">
    </delete></target>
    <target name="clean-logs">
        <delete>
            <fileset dir="${log.dir}">
                <include name="*">
            </include></fileset>
        </delete>
    </target>
    <target depends="clean-docs,clean-logs,clean-build,clean-dist" name="clean">

    
    <target depends="clean-docs" name="phpdoc">
        <phpdoc title="${app.name} Documentation" destdir="${docs.dir}"
          defaultcategoryname="${app.name}"
          defaultpackagename="${app.name}"
          sourcecode="yes"
          output="HTML:Smarty:PHP">
            <fileset dir=".">
                <include name="**/*.php">
                <include name="**/*.pkg">
            </include></include></fileset>
        </phpdoc>
    </target>

    
    <target depends="clean-logs,clean-build,phpdoc" name="build">
        
        <mkdir dir="${build.dir}">
        <copy includeemptydirs="true" todir="${build.dir}">
            <fileset dir=".">
                <include name="${app.fileset}">
                <include name="${docs.fileset}">
                <include name="${lib.fileset}">
                <include name="${log.fileset}">
                <include name="${public.fileset}">
                <include name="${scripts.fileset}">
            </fileset>
        </copy>
        
        <copy file="${build.dir}/application/bootstrap.php" tofile="${build.dir}/application/bootstrap.php.new">
            <filterchain>
                <replaceregexp>
                    <regexp  pattern="s*'/opt/phplibs/ZendFramework/library' . $this->_ps ." replace=""
                        ignoreCase="false"/>
                </replaceregexp>
            </filterchain>
        </copy>

        <move file="${build.dir}/application/bootstrap.php.new" tofile="${build.dir}/application/bootstrap.php"
            overwrite="true"/>
        
        <copy file="${lic.file}" todir="${build.dir}">
        
        <copy includeemptydirs="true" todir="${build.dir}/library">
            <fileset dir="${zend.dir}/library">
                <include name="**/*">
            </fileset>
        </copy>
    </target>

    
    <target depends="clean-dist,build" name="dist">
        <mkdir dir="${dist.dir}">
        <tar basedir="${build.dir}" destfile="./${dist.dir}/${dist.name}-${dist.version}.tar.gz"
            compression="gzip">
        </tar>
    </target>
</project>

The above build file creates API documentation using PhpDocumentor, copies some PHP files from a source location to a target destination, modifies a file using a regular expression through a <filterchain>, creates an archive of these files and cleans up the project directory.

To run a build target from the root directory my project is easy as:

/opt/lampp/bin/phing -f build.xml dist

This is just one small example of what you can do with Phing to help manage the build process of your PHP projects.

Please follow and like us:

Leave a Reply

Your email address will not be published. Required fields are marked *