Reusable “generic” actions in Zend Framework

Tags:

Sometimes you will need nearly the same functionality in many actions. This can lead to unnecessary code duplication if you aren’t careful, and there’s been a couple of occasions on #zftalk, where people have been asking for a good practice to avoid this.

There are several ways to deal with this, such as moving the code into a separate function, or an action helper. But in this post, I’m going to introduce so called “generic actions” – parametrized, easy to reuse actions – which is an idea similar to django generic views

Generic views

In Django terminology, the closest thing to an action is the view. It’s basically the same thing, just with a different name.

Without going much into how Django works, we can describe generic views as reusable views, which do a task which can be controlled by passing it parameters in the url, or by defining them in the configuration for the route.

This could be easily applied to Zend Framework, as it provides all we need.

Generic actions

Let’s first define a task we want to do in an example app.

/**
 * This action displays a single MyModel record
 */
public function detailAction()
{
	//Get the record
	$record = MyModel::find($this->_getParam('id'));
 
	$this->view->record = $record;
}

So here we see a pretty typical action: It finds a model by ID and assigns it to the view. Let’s imagine the view does something with it, such as displays its properties.

Okay, so what if we have multiple models, and we want to display their details too, on their own pages? We could write the above code, but make it fetch some other model, and so on, but that would repeat the code above.

We could also write it as a generic action…

public function detailAction()
{
	//Get parameters from request	
	$modelClass = $this->_getParam('model');
	$recordId = $this->_getParam('id');
	$viewName = $this->_getParam('view');
 
	//Call the find method on the model class
	$record = call_user_func(array($modelClass, 'find'), $recordId);
	$this->view->record = $record;
 
	//Render the required view
	$this->render($viewName);
}

This version of the code uses more parameters, and does not refer directly to any specific model class. It also tells the controller to render the view specified in the parameter.

Now that we have a generic action, we can re-use it to display any model’s details just by passing the needed parameters.

Using the generic action

The most basic approach to using the generic action we wrote would be to define the parameters in the route configuration:

//This code is in your bootstrap, or where you initialize your routes
$router = Zend_Controller_Front::getInstance()->getRouter();
 
//Let's say our generic action is in default module, ModelsController, detailAction
$router->addRoute('foo-detail', new Zend_Controller_Router_Route_Static(
	'foo',
	array(
		'module' => 'default',
		'action' => 'detail',
		'controller' => 'models',
		'model' => 'FooModel',
		'id' => 12,
		'view' => 'foo'
	)
));

So with the above, we have added a route, which would display the Foo model with ID 12, using the view “foo”.

Since it probably isn’t very useful to have the model ID placed right into the configuration like that, we can also move it to the URL:

$router->addRoute('foo-detail', new Zend_Controller_Router_Route(
	'foo/:id',
	array(
		'module' => 'default',
		'action' => 'detail',
		'controller' => 'models',
		'model' => 'FooModel',
		'view' => 'foo'
	)
));

Now we can pass the ID to the record we want right in the URL, and the generic action still works. We could even allow the user to define the model in the URL, but to be on the safe side, that should not be done without adding an extra check in the action to make sure it actually is a model class and not something else.

We could also add more routes, for example to display BarModels. You simply need to add a new route, and change the model parameter in it to BarModel and that’s it.

Conclusion

Generic actions is a different approach to a common problem. It’s especially useful, when you’re writing code that could be reused across projects.

This is just an example of the good stuff in the django framework. I most definitely recommend checking it out yourself too.

Further reading:
Good habits I learnt from Django