Reusable “generic” actions in Zend Framework

December 27, 2008 – 9:56 am 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

Share this:
  1. 7 Responses to “Reusable “generic” actions in Zend Framework”

  2. interesting approach

    i usually make generic controllers in a folder outside my modules directory witch i extend in my application controllers.

    By solomongaby on Dec 27, 2008

  3. Generic views are a real time saver in Django. Especially the object list view is great – throw in a model name, and a paginator if you’d like pagination, and it’ll make creating paginated lists of models really easy.

    By Jani Hartikainen on Dec 27, 2008

  4. hello jani,

    i don’t really understand what the question is in this case :-) how would a use case of this generic controller action (in ZF terminology) look like? Is this some java leaned thing generics like you can have a controller action for any type of model?

    second, can you not override this model parameter? i am not into the specificies of the router parameter defaults, but this looks like it could be a huge security flaw.

    By beberlei on Dec 28, 2008

  5. Well, the use cases are similar as to what you’d see generic views used for in Django: reusable actions for common tasks so you won’t need to rewrite and/or repeat yourself.

    For example, paginated list of objects, object detail view, an action which just renders a specific view etc.

    It doesn’t have to use the models, but that’s just a good example of something where you might need to repeat similar code all over the place.

    As for the parameter in the request – no, it shouldn’t be an issue. To be on the safe side, you might want to make sure the model names come from the route params and not urls, of course.

    By Jani Hartikainen on Dec 28, 2008

  6. Just never ever change the `foo/:id` to `foo/:id/*` and it should be safe.

    By Harro on Dec 28, 2008

  7. The way i handle this is to set a regex route like ‘foo/(\d)+$’ and based on the url the controller method decides which model and view is going to be used.

    Same model functions don’t equal same views, i think this is what makes the proposed solution inflexible.

    By david on Jan 1, 2009

  8. The solution can be fully tailored to what you need. Personally I’ve found that for example listing records from the DB and paginating them is a very common task, which could be done like this.

    By Jani Hartikainen on Jan 1, 2009

Post a Comment

You can use some HTML (a, em, strong, etc.). If you want to post code, use <pre lang="PHP">code here</pre> (you can replace PHP with the language you are posting)