Showing posts with label PHP. Show all posts
Showing posts with label PHP. Show all posts

Friday, 30 September 2011

Integrating Phing and Growl

I've been using Phing for more than a year now and it has become central to the way I work. Phing is a task based tool like make and Apache Ant that is used for automating processes. It is primarily aimed at PHP projects, though I suppose you could use it for any project that needs an automated element. Your project has a XML file called build.xml that specifies targets - a target is a job that you want to automate. Each target is built up of a number of tasks. These tasks might be 'run my unit tests' or 'copy these files.'

To execute a target, you enter 'phing targetname' on the command line. Importantly, one target can call other targets, so you can create complex routines that are run with just one command. I use just one command, 'phing ftp' to do all of the follow:
  • Run my Unit tests - if any fail the build process stops - and generate a code coverage report
  • Minify my JavaScript and CSS files
  • Switch off any debugging code in my PHP and remove any comments
  • Remove comments from my HTML
  • Upload the whole project to the staging server

Powerful stuff but the whole process takes a few minutes from start to end. I usually issue the command and then get on with something else while I wait for the files to be FTPed. What I noticed though was that I'd often forget to keep an eye on the terminal window and I'd miss that the process was complete. I wanted a popup notice to tell me that the files had all been transferred.

This is where Growl comes in. Growl is a really neat notifications system for Mac OSX and Windows. It integrates with a large number of applications. I've integrated it with Mac Mail, so I get a nice big popup message in the middle of my primary screen whenever I get an email.

There are a couple of ways to integrate Growl into an application, but for a system like Phing, the most appropriate is to use the command line tool growlnotify. This is an Growl extra - when you download Growl for Mac OSX, for example, it comes with three extra applications including growlnotify.
You call growlnotify from the command line like this:

growlnotify -n "Phing" -m "My message" -t "Phing"

The n parameter (for name) is the name that appears in your Growl preferences, so that you can specifically configure Growl for Phing. The m parameter is obviously the message that will appear in the popup notification and the t parameter sets the title in the popup. The only caveat is that the title parameter must be last.

You can also add a s switch to make the notification sticky.

Integrating this with Phing is actually quite straight forward. Phing already has a task that executes command line programs, so my solution was to extend that task's PHP class. That Phing task is called ExecTask and is located in phing/tasks/system/ExecTask.php. My task that extends this is called GrowlNotifyTask and is located in phing/tasks/mooduino/GrowlNotifyTask.php.

Lets start with your project's build file. Near the top, in side the <project> element, you need to tell Phing about the new task - this line declares the class, tells Phing where to find it, and allows use to start using a <grown> element inside any <target> elements.

<taskdef name="grown" classname="phing.tasks.mooduino.GrowlNotifyTask" />

I wanted to use the new task as follows but have the option to override some of the defaults if I wanted.

<grown message="Copying deployment files into build directory." />

The code for the task implementation is as follows. It's quite simple; it declares five private instance variables, with public setters and getters. These instance variables correspond to the attributes of the <grown> XML element. All but the $message variables have default values, so only the message attribute is required.

The class also has a main() method that pulls together the values into a command strings, and then uses parent methods in the ExecTask class to execute the command.

<?php
require_once 'phing/tasks/system/ExecTask.php';
/**
* A Task for calling the command line tool growlnotify as a phing task.
* 
* @author Michael Hodgins
*/
class GrowlNotifyTask extends ExecTask {

	private $name = 'phing';
	private $sticky = false;
	private $message = '';
	private $identifier = 'phing';
	private $title = 'Build';
	
	public function __construct() {
		parent::__construct();
		$this->setTaskName('grown');
	}
	
	public function init() {
		parent::init();
	}
	
	public function setName($name='phing') {
		$this->name = strval($name);
		return $this;
	}
	
	public function getName() {
		return $this->name;
	}
	
	public function setSticky($sticky=true)	{
		$this->sticky = $sticky ? true : false;
		return $this;
	}
	
	public function isSticky() {
		return $this->sticky;
	}
	
	public function setMessage($message='') {
		$this->message = strval($message);
		return $this;
	}
	
	public function getMessage() {
		return $this->message;
	}
	
	public function setIdentifier($identifer='phing') {
		$this->identifier = strval($identifier);
		return $this;
	}
	
	public function getIdentifier() {
		return $this->identifier;
	}
	
	public function setTitle($title='Build') {
		$this->title = strval($title);
		return $this;
	}
	
	public function getTitle() {
		return $this->title;
	}
	
	public function main() {
		$cmd = sprintf(
			"growlnotify %s-n '%s' -d '%s' -m '%s' -t '%s'",
			$this->isSticky() ? '-s ' : '',
			$this->getName(),
			$this->getIdentifier(),
			$this->getMessage(),
			$this->getTitle()
		);
		$this->setCommand($cmd);
		$this->setEscape(false);
		parent::main();
	}
}

Saturday, 31 July 2010

Using the PHP Smarty Template Engine with the Zend Framework

This post is about how to use the Smarty Templating Engine together with the Zend Framework. I'm not going to discuss why you'd want to do this; I'm only going to show how easy this is to do.

There are a number of articles already on the web showing how to use Smarty and Zend Framework (for example, this one by Andrea Belvedere and this one on Zend's devzone) but they usually have two problems

  1. They are usually quite old and out of date.
  2. They don't cover using both Zend_View and Zend_Layout.
In this article, I've going to correct these two points. This article uses Zend Framework 1.10 and shows how to use Smarty for both the view and the layout.

Setting up


I'm going to assume that you're already comfortable setting up a new Zend Framework project using the Zend Tool, zf. If you're not, here is a good video showing how to do this in Netbeans 6.9. I'm also going to assume that you already have a working PHP development environment.

First thing is to download the Smarty library and copy it into the library directory in your project. When I did this, I renamed the Smarty directory to remove the version number, so that the Smarty code is actually in 'library/Smarty/libs/'.

For Smarty to work, it needs two additional directories, 'cache' and 'templates_c', on the top level of your project (on the same level as the directories called application, library and so on). These directories need to be readable and writeable to by PHP.

Next, we need to put some configuration values into the application's config file. In Zend Framework 1.10, this is a file call application.ini in the 'application/config' directory. Paste the following under [production]
smarty.dir = APPLICATION_PATH "/../library/Smarty/libs/"
smarty.template_dir = APPLICATION_PATH "/views/scripts/"
smarty.compile_dir = APPLICATION_PATH "/../templates_c"
smarty.config_dir = APPLICATION_PATH "/configs"
smarty.cache_dir = APPLICATION_PATH "/../cache"
smarty.caching = 0
smarty.compile_check = true 
These lines will be used to tell the Smarty engine where to find things. In the .ini file, APPLICATION_PATH refers to the directory called application, where your controllers, views and models reside, so 'APPLICATION_PATH "/../templates_c"' means start at that directory, go up one level, and then find a directory called 'templates_c'.

Bootstrap and Zend_View


In older versions of the Zend Framework, it was normal to put bootstrapping code into the index.php file in the public directory but this is no longer the recommended approach. In Zend Framework 1.10, your bootstrapping code goes into the class Bootstrap in the application directory. We need to override the _initView() method in the otherwise empty Bootstrap class.
protected function _initView() {
 require_once 'Smarty_View.php';
 $view = new Smarty_View($this->getOption('smarty'));
 $viewRender = Zend_Controller_Action_HelperBroker::getStaticHelper(
  'ViewRenderer'
 );
 $viewRender->setView($view);
 $viewRender->setViewSuffix('phtml');
 Zend_Controller_Action_HelperBroker::addHelper($viewRender);
 return $view;
}
This method simply changes the class of object used to represent the application's view. Normally, it would be Zend_View, but we're saying use something called Smarty_View instead. The values that we've already put into the application.ini file are put into Smarty_View's constructor.

Smarty_View extends Zend_View_Abstract; there are many versions of this on the web (including in the official Zend Framework documentation) but this version is based on Andrea Belvedere's version.
<?php

class Smarty_View extends Zend_View_Abstract {

  private $_smarty;

  public function __construct($data) {
    parent::__construct($data);
    require_once $data['dir'] . "Smarty.class.php";

    $this->_smarty = new Smarty();
    $this->_smarty->template_dir = $data['template_dir'];
    $this->_smarty->compile_dir = $data['compile_dir'];
    $this->_smarty->config_dir = $data['config_dir'];
    $this->_smarty->cache_dir = $data['cache_dir'];
    $this->_smarty->caching = $data['caching'];
    $this->_smarty->compile_check = $data['compile_check'];
  }

  public function getEngine() {
    return $this->_smarty;
  }

  public function __set($key, $val) {
    $this->_smarty->assign($key, $val);
  }

  public function __get($key) {
    return $this->_smarty->get_template_vars($key);
  }

  public function __isset($key) {
    return $this->_smarty->get_template_vars($key) != null;
  }

  public function __unset($key) {
    $this->_smarty->clear_assign($key);
  }

  public function assign($spec, $value=null) {
    if (is_array($spec)) {
      $this->_smarty->assign($spec);
      return;
    }
    $this->_smarty->assign($spec, $value);
  }

  public function clearVars() {
    $this->_smarty->clear_all_assign();
  }

  public function render($name) {
    return $this->_smarty->fetch(strtolower($name));
  }

  public function _run() {
    
  }

}
This class simply maps the Zend_View_Abstract methods to the Smarty equivalents and sets up up the Smarty engine. We can test this set up with the following code. Create an action with the following code
$this->view->entries = array('moo', 'dave', 'fred', 'andy', 'jo');
$this->view->moo = 'Moo';
and a view template with the code
{$moo|strtoupper} says:

<ol>
 {foreach from=$entries item=entry}
 <li class="{cycle values="odd,even"}">{$entry} - {$entry|strlen}</li>
 {/foreach}
</ol>

Zend_Layout


The previous set up is all that is needed to start using Smarty with Zend_View.Unfortunately, if you're also using Zend_Layout, this will now be broken. If you're not using Zend_Layout, 'zf enable layout' is the Zend Tool command to switch this feature on.

There are two problems we need to overcome. Firstly, Smarty can't find the layout.phtml file. This is because we've told Smarty to look for template files in 'application/views/scripts/' but the layout template is in 'application/layouts/scripts/'. This is actually very easy to correct. A little documented feature of Smarty is that you can give it an array of directories to search, and it will search each in turn until it finds the correct file.

Add the following configuration to your application.ini file.
smarty.layout_dir = APPLICATION_PATH "/layouts/scripts"
Then change the following line in Smarty_View from
$this->_smarty->template_dir = $data['template_dir'];
to
$this->_smarty->template_dir = array($data['template_dir'], $data['layout_dir']);
The second issue to resolve is a small conflict between the way Zend_View and Smarty are implemented; They both expect to execute the view template inside their own scope. In vanilla layout template, the keyword $this would point to the Zend_View object, but now it must point to the Smarty engine. This means that we can't use the normal syntax to access the Zend_Layout object in the layout template.

The fix for this is, again, quite simple. We simply need to add the Zend_View and Zend_Layout objects to the Smarty object as template variables with the following two lines in the Smarty_View constructor.
$this->assign('_view', $this);
$this->assign('_layout', $this->layout());
Now, in the layout template, where we would normally use
<?php $this->layout()->content ?>
we instead use
{$_layout->content}
and where we might previously use
<?php $this->headLink() ?>
we use instead
{$_view->headLink()}

That's all there is to it. You now can use the Smarty template engine while still being able to use Zend_View, Zend_Layout and helpers.

Lastly, notice the line 'smarty.caching = 0' in the config file. This switches off Smarty's static caching system which saves a copy of the static HTML output into the cache directory; this is fine for a development environment but you might want to consider turning it on in production.