Super-Skinny your Zend Framework Action Controllers (Part One)

Super Skinny Your Controllers

Lose pounds of excess code in 1 easy step!

Inspired by Padraic Brady’s post on Fat Stupid Ugly Controllers and Chris Hartjes’ Fat Models, Skinny Controllers, I’ve been putting my controllers on a crash diet.

Slim controllers are easier to test, encourage the DRY principle and force business logic back into the model layer where it belongs.

In this first article, I’ll be looking at a quick way to de-bloat Zend Framework Action Controllers by using an Action Helper to automatically instatiate models and assign them to properties in the controller and view.

Lather, Rinse, Repeat

Controller actions often perform mundane, repetitive tasks. Checking for required parameters, creating objects and assigning variables (again and again) eats up line after line of code in a typical controller.

Using the address book example, a simple action in our Contact controller might look something like this:

class ContactController extends Zend_Controller_Action
{
    public function viewAction()
    {
	// check that we have a contact ID, otherwise redirect to index
	if ( !$this->_hasParam( 'id' ) || !Contact::exists( $this->_getParam( 'id' ) ) ) {
	    $this->_helper->FlashMessenger( 'No contact specified or contact does not exist.' );
	    $this->_redirect( 'contact' );
	}
	// lookup and instantiate the contact object
	$contact = Contact::find( $this->_getParam( 'id' ) );
 
	// assign contact to view
	$this->view->contact = $contact;
    }
}

A few things are happening here:

  1. First, we check to see if the ‘id’ param exists in the request.
  2. Then we use our model’s static exists() method to check that the contact ID corresponds to a real contact.
  3. The contact object is then looked up using a static finder method.
  4. …and finally we assign the contact to the view.

The exact code will vary depending on your model implementation, but this sort of process is pretty typical in my experience. In many actions, that’s all that’s required: the view then does its thing on the contact object that it’s been given.

Pretty boring stuff and chances are some or all of the above will be repeated in a handful of other actions in the same controller. That’s a “code smell” if ever I smelled one.

By automating these repetitive operations, there’s an opportunity to substantially reduce the size of our controllers without losing any functionality.

The Action Helper

Fortunately, Zend Framework has the perfect tool for this kind of job: an Action Helper.

By tapping into the preDispatch() hook provided and making a few basic assumptions, we can look for models specified in the request, instantiate them and then assign them to properties in both the action controller and the view.

Here’s the basic action helper class, which I’ve called “ModelWatcher”:

<?php
 
class Dog_Zend_Controller_Action_Helper_ModelWatcher extends Zend_Controller_Action_Helper_Abstract
{
    /**
     * @var string
     */
    protected $_controllerName;
 
    /**
     * @return void
     */
    public function preDispatch()
    {
	$request = $this->getRequest();
	$this->_controllerName = $request->getControllerName();
	$this->_view = $this->_actionController->view;
 
	try {
	    $model = $this->_getModel();
 
	    // assign to the controller and view
	    $this->_actionController->{$this->_controllerName} = $model;
	    $this->_view->{$this->_controllerName} = $model;
 
	} catch ( Exception $e ) {
	    return;
	}
    }
 
    /**
     * @return object The instantiated model object
     */
    protected function _getModel()
    {
	$request = $this->getRequest();
 
	if ( NULL === $request->getParam( 'id' ) ) {
	    throw new Exception( "ID parameter not found in request" );
	}
	$id = $request->getParam( 'id' );
 
	// attempt to find and instantiate the model object
	$modelClass = ucfirst( $this->_controllerName );
	$model = $modelClass::find( $id );
 
	return $model;
    }
}

Important: Don’t forget that you’ll need to add the helper to your bootstrap for this to work. Something like this should do the trick:

Zend_Controller_Action_HelperBroker::addHelper( new Dog_Zend_Controller_Action_Helper_ModelWatcher() );

When the helper’s preDispatch() method is called, it checks the request for the “id” parameter and attempts to find and create the model object. In the case of our address book, a request URL like http://www.example.com/contact/id/123 would try to retrieve an object of the class Contact whose ID is 123.

We wrap the call to _getModel() in a try…catch block to deal with two scenarios:

  1. When no ID parameter exists in the request or
  2. No record exists for the given ID

The latter assumes that the find() method will throw an exception if the record is not found.

If the lookup is successful, the newly created object is assigned to the controller and view with a property named after the controller. So with our address book example, you’d access the contact object using $this->contact from within both the controller and the view.

On failure, we do nothing – leaving the action controller to implement its own logic for dealing with non-existent objects.

The Final Weigh-In

All this means we’re left with a significantly slimmer controller action:

class ContactController extends Zend_Controller_Action
{
    public function viewAction()
    {
	// check that we have a contact object
	if ( !isset( $this->contact ) ) {
	    $this->_helper->FlashMessenger( 'No contact specified or contact does not exist.' );
	    $this->_redirect( 'contact' );
	}
    }
}


The controller action now only needs to check that a contact object has been created by the action helper. There’s no need for it to get its hands dirty with the request params, object generation or assigning variables to the view as it’s already been done.

This approach does require an adherence to some naming conventions… primarily that the model class name has to mirror the controller name and the identifying parameter is always passed as “id” in the URL. Provided they aren’t too wacky, it should be reasonably easy to re-jig the code to suit any conventions of your own.

Further Weight Loss

A slightly more flexible approach is to use verbose ID parameters like “contact-id”. This gives you the ability to have model classes which don’t match the controller name and also allows multiple models to be instantiated by the ModelWatcher in one request, both of which can be useful. I use something similar in my own implementation.

You might also want to consider adding other logic in to the ModelWatcher helper like record locking or fine-grained access control. Even if you decide to separate these activities into a separate helper class, you can still use the models inserted into the controller rather than going back to the request parameters again. Just make sure that the ModelWatcher is added to the action helper plugin stack before any helper that relies on it or the models it creates.

Final Thoughts

Hopefully it should be fairly clear that this simple action helper has the potential to dramatically reduce the amount of boiler plate code in your controllers. Eagle-eyed readers might also have noticed that automatically assigning models to the controller is a feature of CakePHP.

Look out for future posts where I’ll be discussing more ways to de-clutter and cut down your controllers.

P.S. Thanks to Rob Allen, Matthew Weier O’Phinney, Pádraic Brady, Wenbert Del Rosario and others for retweeting/linking to my first post :)

Social Bookmarks
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay

Tags: , , , , , ,

8 Responses to “Super-Skinny your Zend Framework Action Controllers (Part One)”

  1. Olagato 09. Mar, 2010 at 5:15 pm #

    Great article!
    Thank you and congratullations.

  2. Bobby 10. Mar, 2010 at 6:50 pm #

    That was really helpful! I will try to use this method as it does make a lot of sense. Thanks!

    • Steve 10. Mar, 2010 at 6:52 pm #

      No problem! :)

      Glad you enjoyed it.

  3. Jason 21. Apr, 2010 at 1:17 am #

    Cool post. Curious, how are you statically instantiating the find() method in the early example? Zend_Db_Table_Abstract::find() is a non-static method, so how are you overriding that behavior?

    Jason

    • Steve 21. Apr, 2010 at 7:02 am #

      Hi Jason – thanks for the comment.

      Ah, the find call was just pseudo code to illustrate the point.

      I only use Zend_Db_Table in very simple apps… for more complex cases, I use a data mapper to split persistence logic from business logic. It’s a bit more involved, but the end result is much cleaner… it’s on the “to-blog” list :)

  4. Abby 02. Dec, 2010 at 1:17 pm #

    I just wanted to say the following thing…. I’m not a programmer, but I was searching some tips about a diet and I digg over your blog. And I read with such intensity the first half, that I really thought was about how to lose some weight. :) )) I even looked for some Zend tricks/book for weight :) ))) I know stupid… I just wanted to share this with you. Best regards and keep it fun working :)

  5. Mike 14. Jan, 2011 at 6:14 am #

    Steve,

    “A slightly more flexible approach is to use verbose ID parameters like “contact-id”. This gives you the ability to have model classes which don’t match the controller name and also allows multiple models to be instantiated by the ModelWatcher in one request, both of which can be useful. I use something similar in my own implementation.”

    Could you provide a sample of this?

    I’m trying to imagine how you would do this without doing a bunch of if logic.

    Thanks.

Trackbacks/Pingbacks

  1. Blimey! I’ve got a blog! | stevehollis.com - 11. Mar, 2010

    [...] How to make your models fatter and your controllers thinner (Part One [...]

Leave a Reply