Saturday, 10 March 2007

Using Phing to automate the generation of class skeletons for the Zend Framework

As I'm not very challenged by doing monotonic and repetitive work, as the building of custom controller and model classes of the Zend Framework is, I crafted an automation solution for these two tasks using Phing.

The tasks I wanted to automate were the creation of custom controller and model class by calling a build file. So I wrote two templates deriving from Zend_Controller_Action and Zend_Db_Table including some common methods used in all my controller/model classes like i.e. getModelInstance() in any custom Zend_Db_Table derivation. I could have also build my own extended base classes and let my custom controllers/models derive from them, but for the sport of it I didn't.

The following code snippet shows one off the templates which are located in the ./application/templates directory and are used by the build file to generate the skeleton classes as they are needed.

<?php

/**
*
* @@Classname@@Contoller class.
*
* @author Raphael Stolt <raphael.stolt@gmx.de>
* @package
* @version $Id$
*/

class @@Classname@@Controller extends Zend_Controller_Action {

public function indexAction() {
...
}

public function init() {
...
}

/**
* Retrieve a model instance to keep/delegate business
* logic/work out of the controller and provide the model
* only to actions that need it.
*/
private function getModelInstance() {
...
}

/**
* Retrieve a view instance holding the default view
* directory for this controller.
*/
private function getViewInstance() {
...
}

/**
* Check if ajax-action is called in an ajax context.
*/
private function isAjaxActionRequest() {
...
}

}
So here is the Phing build file which will generate the custom class skeletons and can be steered by several options. You can pass the class names by calling it on the command line like model={Name,Name2} for 2 models or controller={Name,Name2,Name3} for 3 custom controllers.
<?xml version="1.0" ?>
<project name="recordshelf" basedir="." default="generate" >

<property name="template.directory" value="./templates"/>
<property name="build.directory" value="./builds/"/>
<property name="controller.template" value="${template.directory}/Template_Custom_Action_Controller.php"/>
<property name="controller" value="" override="true"/>

<property name="model.template" value="${template.directory}/Template_Model.php"/>
<property name="model" value="" override="true"/>

<target name="check-and-unravle" description="Checks if all class templates are available">

<!-- Check user input -->
<if>
<and>
<equals arg1="${controller}" arg2="" />
<equals arg1="${model}" arg2="" />
</and>
<then>
<fail message="No custom controller or model name provided!" />
</then>
</if>

<if>
<not>
<equals arg1="${controller}" arg2="" />
</not>
<then>
<property name="controller" value="${controller}" override="true"/>
</then>
</if>

<if>
<not>
<equals arg1="${model}" arg2="" />
</not>
<then>
<property name="model" value="${model}" override="true"/>
</then>
</if>

<!-- Check for existence of class templates -->
<if>
<available file="${controller.template}" />
<then>
<echo message="Using ${controller.template} as template." />
</then>
<else>
<fail message="${controller.template} is not available!" />
</else>
</if>

<if>
<available file="${model.template}" />
<then>
<echo message="Using ${model.template} as template." />
</then>
<else>
<fail message="${model.template} is not available!" />
</else>
</if>

<!-- Unravle user passed properties and call generation targets -->

<foreach list="${controller}" param="controller" target="generate-controllers" />
<foreach list="${model}" param="model" target="generate-models" />

</target>

<target name="generate-controllers" description="Generates a custom controller class from the template class.">

<copy file="${controller.template}" tofile="${build.directory}/${controller}Controller.php"
overwrite="false" >

<filterchain>
<replacetokens begintoken="@@" endtoken="@@">
<token key="Classname" value="${controller}" />
</replacetokens>
</filterchain>

</copy>

</target>

<target name="generate-models" description="Generates a custom model class from the template class.">

<copy file="${model.template}" tofile="${build.directory}/${model}.php" overwrite="false" >

<filterchain>
<replacetokens begintoken="@@" endtoken="@@">
<token key="Classname" value="${model}" />
</replacetokens>
</filterchain>

</copy>

</target>

<target name="generate" depends="check-and-unravle">
<echo message="Class generation to ${build.directory} finished." />
</target>

</project>
If this build file is stored in your applications base directory and Phing is installed you can call it like this on the console.

phing -Dcontroller Login,Shelf -Dmodel Record,User

This will generate the two controller skeleton classes Login and Shelf and further the two model skeleton classes Record and User to the directory stated in the build file.

One outstanding issue of the current build file is that it is possible to pass lowercase Contoller and Model names. So that you might end up with a custom model skeleton like user.php instead of User.php which is against the convention of class naming. But like Unix it currently assumes that you are an responsible user.

1 comment:

kanian said...

Nice idea. I will try and understand your post better. I need to read more on phing. Yet there are a lot of things to automate in Zend Framework app dev.