Zend_Db_Table dynamic finders

The recently added Zend_Navigation component uses dynamic finders to find pages e.g. findOneByLabel(‘Home’) to return the first matching page with label Home (and that’s straight from the manual).
It would be nice if Zend_Db_Table could do this too but it can’t. This seems a little inconsistent to me. Why do it for some components and not others?

Anyway, adding it wasn’t that difficult. The first step is to have an (abstract?) class extend Zend_DB_Table and let all your Db-backed models extend that. I’ll call mine App_Db_Table.

<?php

abstract class App_Db_Table extends Zend_Db_Table_Abstract {

	/**
	 * Db Instance
	 */
	protected $_db;

	/**
	 * Call method used to implement the dynamic finders
	 *
	 * @param string
	 * @param array
	 * @return function || void
	 */
	public function __call($method, $args) {
		$watch = array('findBy','findAllBy');
		foreach ($watch as $found) {
			if (stristr($method, $found)) {
				$fields = str_replace($found, '', $method);
				return $this->{'_' . $found}($fields, $args);
			}
		}
		throw new Exception("Call to undefined method App_Db_Table::{$method}()");
	}

	/**
     * Initialize object
     *
     * Called from {@link __construct()} as final step of object instantiation.
     */
    public function init() {
    	$this->_db = Zend_Registry::get('db');
    }

	/**
	 * Find By
	 *
	 * This method only ever returns the first record it finds!
	 *
	 * @param 	string
	 * @param 	array
	 * @return  object|false
	 */
	protected function _findBy($columns, $args) {
		$stmt = $this->_buildQuery($columns, $args);
		return $this->fetchRow($stmt);
	}

	/**
	 * Find All By
	 *
	 * @param 	string
	 * @param 	array
	 * @return	 object|false
	 */
	protected function _findAllBy($columns, $args) {
		$stmt = $this->_buildQuery($columns, $args);
		return $this->fetchAll($stmt);
	}

	/**
	 * Builds the query for the findBy methods
	 *
	 * @param 	string
	 * @param 	array
	 * @return 	Zend_Db_Select
	 */
	protected function _buildQuery($columns, $args) {
		$fields = explode('And', $columns);
		$count = count($fields);

		$where = "{$this->_filterField($fields[0])} = ?";
		$where_args = $args[0];
		unset($args[0]);

		$select = $this->select();
		$select->where($where, $where_args);

		if ($count > 1) {
			array_shift($fields);
			foreach ($fields as $field) {
				$where = "{$this->_filterField($field)} = ?";
				$where_args = array_shift($args);
				$select->where($where, $where_args);
			}
		}
		return $select;
	}

	/**
	 * Converts a camelCased word into an underscored word
	 *
	 * @param 	string
	 * @return  string
	 */
    protected function _underscore($word) {
    	$word = preg_replace('/([A-Z]+)([A-Z])/', '\1_\2', $word);
		return strtolower(preg_replace('/([a-z])([A-Z])/', '\1_\2', $word));
    }

    /**
     * Converts field name to lowercase and if camelcased, converts to underscored
     *
     * @param string
     * @return string
     */
    protected function _filterField($item) {
    	$item = $this->_underscore($item);
    	return strtolower($item);
    }

}

 

Usage

I’ll use a couple of unit tests to drive the class. I’ll assume a User class extends App_Db_Table. The table schema for my test is:

CREATE TABLE `users` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`username` VARCHAR( 32 ) NOT NULL ,
`email` VARCHAR( 64 ) NOT NULL ,
`city` VARCHAR( 32 ) NOT NULL ,
`state` VARCHAR( 32 ) NOT NULL ,
`favourite_colour` VARCHAR( 16 ) NOT NULL
)

And the relevant section of the test class is as shown:

...
/**
 * Add  a user to the users table
 *
 * @param   array   $attributes
 */
private function _createUser($attributes = array()) {
    $details = array(
        'username'  => 'john',
        'email'     => 'john@example.com'
        'city'      => 'Colchester',
        'state'     => 'Essex',
        'favourite_colour' => 'red'
    );
    $params = array_merge($details, $attributes);
    $user = new User();
    $user->createRow($params)->save();
}

/**
 * Test findBy Dynamic finder with one parameter
 */
public function testFindByWithOneParameter() {
	$this->_createUser();
	$finder = new User();

	$found = $finder->findByEmail('john@example.com');
	$this->assertEquals(1, count($found));

	$found = $finder->findByEmail('smith@example.com');
	$this->assertFalse($found);
}

/**
 * Test findBy Dynamic finder with underscored column names
 */
public function testFindByWithUnderscoredColumnNames() {
	$this->_createUser();
	$finder = new User();

	$found = $finder->findByFavouriteColour('red');
	$this->assertEquals(1, count($found));
}

/**
 * Test findBy dynamic finder with more than one parameter
 */
public function testFindByWithMultipleParameters() {
	$this->_createUser();
	$finder = new User();

	$found = $finder->findByStateAndCity('Essex', 'Colchester');
	$this->assertEquals('Essex', $found->state);

	$found = $finder->findByStateAndCity('Essex', 'London');
	$this->assertFalse($found);

	$found = $finder->findByStateAndCity('London', 'Colchester');
	$this->assertFalse($found);
}

/**
 * Test findAllBy Dynamic finder with one parameter
 */
public function testFindAllByWithOneParameter() {
	$this->_createUser();
	$this->_createUser(array(
        'username'  => 'smith',
        'email'     => 'smith@example.com'
    ));

    $finder = new User();

	$found = $finder->findAllByState('Essex');
	$this->assertEquals(2, count($found));

	$found = $finder->findAllByUsername('john');
	$this->assertEquals(1, count($found));

	$found = $finder->findAllByEmail('smith');
	$this->assertEquals(0, count($found));
}
...

Posted in Zend Framework | 3 Comments

Zend Framework deployment with Capistrano

After playing around with Phing for a while I decided to bring the simplicity of Capistrano – which I have been using with my Rails apps – to our Zend Framework project at work. Surprisingly it wasn’t hard at all.
“Capistrano is a tool for automating tasks on one or more remote servers”. That’s straight off their website and this tool rocks.

Capistrano is written in Ruby and needs the ruby library to be installed on the client machine. It does not need ruby on the production or any other target servers and at work we deploy from two machines and needed ruby installed on just the two. However the ruby files for the deployment recipes need to be in source control along with the rest of the code.
The following resources came in useful while we were setting up:

I use Ubuntu at work and the following steps are what we had to do to get Capistrano working on Ubuntu 8.04 but should work on most distros.

Step 1: Installing Ruby and Ruby gems

Install ruby packages

sudo aptitude install ruby1.8-dev ruby1.8 ri1.8 rdoc1.8 irb1.8 libreadline-ruby1.8 libruby1.8 libopenssl-ruby

Create some symlinks from the ruby install to their locations

sudo ln -s /usr/bin/ruby1.8 /usr/bin/ruby
sudo ln -s /usr/bin/ri1.8 /usr/bin/ri
sudo ln -s /usr/bin/rdoc1.8 /usr/bin/rdoc
sudo ln -s /usr/bin/irb1.8 /usr/bin/irb

Make sure subversion is installed

svn --version

If not installed, install it. You could of course use another source control system. Git seems to be the new hotness.

sudo apt-get install subversion

Install rubygems from source

mkdir ~/sources
cd ~/sources
w get http://rubyforge.org/frs/download.php/56227/rubygems-1.3.3.tgz
tar xzvf rubygems-1.3.3.tgz
cd rubygems-1.3.3
sudo ruby setup.rb

Create a symlink for rubygems and update it

sudo ln -s /usr/bin/gem1.8 /usr/bin/gem
sudo gem update
sudo gem update --system

Install capistrano, capistrano multi-stage and echoe gems

sudo gem install capistrano
sudo gem install capistrano-ext
sudo gem install echoe

That’s it. The Ruby environment is all set up.

Step 2: Creating the deploy file

Change to the root directory of your application, create a ‘config’ folder if you dont have one and type

capify .

Capistrano creates two files. A ‘Capfile’ for loading the deploy script and the deploy script itself. Open up the Capfile and remove the line where it tries to load plugins. This is specific to the Rails framework.
The Capfile should now look like this:

load 'deploy' if respond_to?(:namespace)
load 'config/deploy'

Open up the deploy.rb file created in the config folder and edit it for your setup.
The various configuration parameters are available on the Webistrano website. Our sample recipe is further down the page.

Step 3: Add multistage

For deploying to multiple environments, the capistrano multistage extension needs to be added. We usually have a staging, integration and a production stage and I’ll assume that here.
For multi-stage, the capistrano-ext gem needs to be included, the various stages specified and the default stage set. Furthermore, all configuration parameters not common to all stages need to be taken out of the deploy .rb file.

Create a deploy folder in the config directory and add a file for each of your environment. In our case we have production.rb, integration.rb and staging.rb files.
Add the environment specific parameters to these files.
Our sample deploy.rb file looks like:

set :stages, %w(staging integration production)
set :default_stage, "staging"
require 'capistrano/ext/multistage'

set :application, "app"

#############################################################
#	Settings
#############################################################

default_run_options[:pty] = true
set :use_sudo, false

#############################################################
#	Servers
#############################################################

set :user, "username"
set :domain, "example.com"
set :port, 22
server domain, :app, :web
role :db, domain, :primary => true

#############################################################
#	Subversion
#############################################################

set :repository,  "http://example.com/svn/#{application}/trunk"
set :scm_username, "svn-user"
set :scm_password, "svn-pass"
set :deploy_via, :export

#############################################################
#	Tasks
#############################################################

after :deploy, 'deploy:cleanup'

# overide Rails specific tasks here.

desc "Do nothing"
deploy.task :restart, :roles => :app do
    # do nothing. php deploys don't need a server restart
end

deploy.task :migrate, :roles => :app do
    # migrate task is rails specific
end

Our staging deploy file sample (APPROOT/config/deploy/staging.rb)

set :deploy_to, "/path/to/staging"

If your staging and production environments are on different servers you obviously need to move the ‘Servers’ section to the individual deploy files.

Step 4: Setup and do initial deploy

The first step is to setup the server for Capistrano. Capistrano has a command to do this but due to the way it works, the application will be deployed to a folder called ‘current’ (really just a symlink to the latest release) within your ‘deploy_to’ directory. This means the document root (for apache virtualhost) will be in /path/to/staging/current/public rather than /path/to/staging/public and your apache (or nginx, etc.) config has to be changed to reflect this.

Commit the files to subversion (insert your SCM here) then type:

cap deploy:setup

This command creates two directories in your deploy_to path, releases and shared. The releases directory stores versions of the application as they are deployed. The”after :deploy, ‘deploy:cleanup’” line in the recipe cleans up this folder by keeping just the latest 5 (by default) releases. The ‘shared’ folder is for storing images and other files that are not part of your application and are therefore not under source control.

To deploy your app, run:

cap deploy

This deploys to the default stage – staging in our case.
To deploy to production you’d type:

cap deploy production

It’s as easy as that. This is obviously not specific to the Zend Framework and can be used with any app.

Posted in Zend Framework | Leave a comment

Zend Framework and the cost of flexibility

I have had to downgrade my copy of the Zend Framework to 1.7.8 after 5 mins of ‘upgrading’ to 1.8.0 to get access to all the promised goodies (Zend Tool, Zend Application, Zend Navigation, etc).

Unfortunately, Zend_Loader::registerAutoload has been deprecated and I got notices all over my screen. Considering we are working on multiple projects in Zend Framework and we all have local copies I didn’t think it was such a good idea recommending this upgrade to the team especially with deadlines looming really close.

However, I don’t think I’ll be missing any of the new additions considering the hassle involved in using them. Why must everything be so complex in ZF? I copied over the CLI tool for  a test run but even that refused to run probably because I had an earlier version of the framework. I really want to love ZF (not that I have a choice, anyway) but with every new feature I think is cool comes the ‘why do I have to do all this to use it?’.

I had a look today at the Zend QuickStart to see if it had been updated to use Zend_Application and it has. However I still can’t figure out how this ‘new’ (more complex) way of setting up my application has any advantage over the ‘old’ way. I think I’ll stick with Jara. Furthermore, the ‘QuickStart’ is not really quick at all. What happened to the good old days when we built a blog in 20 mins without writing any code? Instead what we have is setting up cli tools, writing classes to initialise my app and an introduction to the Data Mapper, the most complex of Martin Fowler’s database access architectural patterns. I thought the quickstart was meant to sell the framework. This isn’t doing a very good job.

I’ll probably feel better tomorrow and get everyone to upgrade  but for now I guessed I’m pissed I can’t use the new stuff. However the truth still remains that ZF is not for the ‘lazy’. To use it well you’ve got to work hard and for a RAD tool that’s an irony.

Posted in Zend Framework | 2 Comments

Rails makes you think you can

I spent a couple of hours last weekend going over a Rails app I wrote almost two years ago. It was the very first Rails application I had written (apart from the follow-the-screencast throwaway ones) and I couldn’t help cringing when I saw the code I got paid good money to write.

That app is still my most ambitious project ever and I’m still shocked I decided to do it in Ruby (a language I barely knew) and Rails (after playing with it for a few weeks) when I had a couple of years experience in PHP and some production apps already running on CodeIgniter. I guess it was the hype.
However, almost two years later and the app is still running smoothly (I had to tweak it a bit over the weekend). I am still not sure I would have taken on the project – an internal stock trading app with a social angle to it (I was freelancing then and working alone) if I had to do it PHP but in retrospect it would probably have been done better in PHP. I’d have had less fun though.

Rails is so deceptively simple it makes you feel you’re a superstar programmer even when you just have a couple of lines of code under your belt. However, after working with it for a while, you start to appreciate how complex it is to get things done when they don’t fall into the Ruby on Rails sweet spot. I wouldn’t have it any other way though. When it rocks, it really rocks.

I currently use PHP (and the Zend Framework) at work and I have come to appreciate the flexibility of ZF but I wouldn’t recommend the framework for programming beginners. Using a lot of the components requires a good grasp of programming concepts and compared to Rails where components are written for specific use cases, with ZF, you have to come up with yours. I am currently still trying to figure out where I can use Zend_Navigation in the recently released version of ZF. I know it will come in useful but I just have to tweak and flesh it out to make it work for me and that pretty much sums up the ZF-Rails differences quite well. One makes you think you can without even trying while with the other, you know you can but you’ll just have to work at it.

Posted in Rails | 3 Comments

Finally… passenger for nginx

I just spent last weekend setting up a new slice at SliceHost (my bestest host ever) for a couple of Rails apps only to read this post announcing the release of Phusion Passenger for Nginx.

Considering I only needed the slice for Rails (No php and the DB is on another slice), I could have gone with Nginx except that I’ve been spoilt by Passenger’s upload-and-go deployment and being able to squeeze more apps into a small VPS (the inactive ones shut down and release memory). Now hopefully, it’s the best of both worlds.

I’ll still have to learn Nginx quirks considering I’ve always used Apache but the promise of faster page serving and a lower memory footprint is motivation enough.

Kudos to the guys at Phusion for their awesome products (I also use Ruby Enterprise). Congrats on your anniversary and a lot more power to your elbow!

On a related note, I learnt the hard way that Rails 2.3 and an old version of Passenger don’t mix very well.
Even after updating the passenger gem I couldn’t get Rails to load. The solution is to re-install the passenger module (passenger-install-apache2-module) and update your apache config file. Hope that helps someone as I spent a bit too long trying to fix it.

Posted in Rails | 1 Comment

Jazz up your phpunit test results

I use RedGreen when working on rails projects and have gotten used to the visual feedback I get when tests pass or fail. It has definitely helped with my Red-Green-Refactor flow.

I just found out phpunit supports colors in tests and it’s built-in as well. No gem installation required. Sweet!
Turning on colours is done either with a command line argument or by adding an attribute to the phpunit XML configuration file.

Command Line:

phpunit --colors testfile.php

XML File:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true">
	<testsuite name="All Tests">
		<directory>./</directory>
	</testsuite>
</phpunit>
  • No Colours:
    No colour
  • Passing Tests – with colours:
    Passing Tests
  • Failing Tests – with colours:
    Failing Tests
  • Skipped or Incomplete Tests – with colours:
    Skipped Tests
Posted in PHP | Leave a comment

Jara Base – a base Zend Framework app

I recently had to set up a Zend Framework project at a friend’s house and realized how reliant I am on Zend Studio to set up a fresh project. Meet Jara Base – the ZF starter app and my solution to this minor issue. It’s essentially a slightly modified version of the Zend Studio project structure with the following variations/additions:

  • Added a test helper to the tests folder.
  • Added a static route plugin for handling static site pages.

Usage

Adding static pages

Most applications have pages for static content e.g. an about page, a contact page, etc. I prefer my static pages without the controller name and the ‘page’ route removes the need to add the controller name to the url. Jara Base already includes an about page with a corresponding view and this page is accessed with ‘http://www.example.com/about’ rather than ‘http://www.example.com/index/about’.

To add a static page, add an action to the ‘Index’ controller of the ‘Default’ module. Add a corresponding view and the page will be available at ‘http://www.example.com/:action’.
Static routes with the same name as your controller actions are created by default.
To disable static routes, comment out (or delete) the static routes line in the plugins section of the application config file (application.ini).

Posted in Zend Framework | 3 Comments

Zend Framework titbits

There are a couple of Zend Framework snippets and how-tos I find myself googling over and over again. I have decided to maintain this list and keep adding to it as I go along.

  • Bypassing Zend_Db prepared statements:For complex queries, you may have to write the query out by hand and we recently had to do that. However Zend_Db insists on preparing ALL statements and a PDO bug forced the need to bypass this by accessing the connection object directly.
    To do that run:

    $result = $db->getConnection()->exec('SELECT * FROM users');

    More in the documentation

  • Disable Zend_Layout
    public function ajaxAction() {
    // disable layouts for this action:
    $this->_helper->layout->disableLayout();
    ...
    }
    
  • Disable Zend_ViewRenderer
    public function processingAction() {
    $this->_helper->layout->disableLayout();
    
    // disable the view for this action. e.g. actions that redirect after processing
    $this->_helper->viewRenderer->setNoRender();
    ...
    }
    
Posted in Zend Framework | Leave a comment