Architecture & DesignFat Models and Skinny Controllers Bring Out the Best in Your...

Fat Models and Skinny Controllers Bring Out the Best in Your MVC Framework

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

In these waning moments of the 21st century’s first decade, it seems an apropos time to look back at the events that shaped the world of web development over the course of the past 10 years. Although one could point to any number of developments, the emergence and widespread acceptance of MVC framework-based development seems to have played a particularly profound role in drastically improving the quality, security, and performance of the code used to power web sites both large and small. If you’ve been using one or several frameworks such as Rails, Django, or the Zend Framework, my guess is you’d be inclined to agree with this assessment.

Framework-based development is so advantageous because it relieves the developer of dozens of tedious decisions such as where to manage configuration data, how to access and manipulate database data, and what templating solution to employ. Employing the concept of convention over configuration, developers are able to instead devote the majority of their time and brain power to creating a powerful web application.

But a framework shouldn’t be considered a panacea; it remains paramount for you to continue exploring and implementing best practices, which will further enhance the maintainability, reusability, and readability of your code. One such best practice involves adhering to a design decision that produces “fat models” and “skinny controllers.” The term “fat” is derived from the idea of packing as much of the data-related logic into the model as possible while maintaining a streamlined, almost barren controller.

While it might seem an obvious strategy to restrict data-specific logic to the application’s models, you’ll often see a rather different approach employed. Let’s take a look at this common yet misguided approach to see where things can go wrong. Although the concepts discussed throughout this tutorial apply to any MVC framework, we’ll use the Zend Framework as the basis for the examples.

Going Forward in Reverse

All too often, developers tend to look at a Zend Framework model as little more than an obligatory file that must be created in order to establish the connection between the controller and underlying database. For instance, after configuring the database connection parameters within the application.ini file, all you need to do is create the following model to begin talking to the book table:

<?php
 
class Default_Model_Book extends Zend_Db_Table_Abstract
{
    protected $_name = 'book';
    protected $_primary = 'id';          
}
 
?> 

Despite the fact that you can extend the model with your own custom methods, it becomes all too easy to instead stuff your controller actions with model-specific logic. For instance, an action responsible for adding a new book to the book table might look like this (I’ll simplify the validation procedures in order to keep this listing relatively short):

01 public function addAction()
02 {
03   if ($this->getRequest()->isPost()) {
04 
05     $book = new Default_Model_Book();
06   
07     // Define a message array
08     $this->view->messages = array();
09 
10     // Title
11     if (! Zend_Validate::is($this->_request->getPost('title'), 'NotEmpty')) {
12       $this->view->messages[] = "Please provide a book title.";
13     }  
14 
15     // Author
16     if (! Zend_Validate::is($this->_request->getPost('author'), 'NotEmpty')) {
17       $this->view->messages[] = "Please provide the author name.";
18     }  
19   
20     if (count($this->view->messages) == 0) {
21   
22       $data = array (
23         'title'  => $this->_request->getPost('title'),
24         'author' => $this->_request->getPost('author')   
25       );      
26   
27       $book->insert($data);
28   
29       $this->view->messages = "The book has been added.";
30   
31     }
32   
33   }
34 }

Let’s review a few key lines in this listing:

  • Lines 11-18 validate the form field data provided by the user. In this case, we’re just checking to ensure that neither form was blank. However, in a real-world situation you should perform additional verification tasks.
  • If line 20 determines that the $messages array is still blank (meaning no errors occurred during the validation process), lines 22-25 create the array used to insert the book data into the database. Line 27 completes the task by using the Zend_Db insert() method to insert the data.

While this action will suffice to complete the desired task, the approach has several drawbacks. Most notably, presuming you plan on also creating an action capable of modifying an existing book stored within the database, you’ll need to create a second action that almost exactly reproduces the code found in the add() action:

public function editAction()
{
  if ($this->getRequest()->isPost()) {
 
    $book = new Default_Model_Book();
  
    // Define a message array
    $this->view->messages = array();
 
    // Title
    if (! Zend_Validate::is($this->_request->getPost('title'), 'NotEmpty')) {
      $this->view->messages[] = "Please provide a book title.";
    }  
 
    // Author
    if (! Zend_Validate::is($this->_request->getPost('author'), 'NotEmpty')) {
      $this->view->messages[] = "Please provide the author name.";
    }  
  
    if (count($this->view->messages) == 0) {
  
      $data = array (
        'title'  => $this->_request->getPost('title'),
        'author' => $this->_request->getPost('author')   
      );      
  
      $where = $book->getAdapter()->quoteInto('id = ?', $bookID);
            
      $book->update($data, $where);
  
      $this->view->messages = "The book has been modified.";
  
    }
  
  }
}

Because of this tedious redundancy, you’ll need to be vigilant when changing your application logic to reflect changes made to the book model in order to ensure both the add() and edit() actions have been properly updated. However, by migrating the data-specific logic into the model, you can not only eliminate this unwanted redundancy, but also greatly reduce the total amount of code you’ll need to maintain.

Beefing Up the Model

We’ll start by moving the model logic completely into the Book model.
What’s more, we’ll consolidate both the insertion and modification tasks within a single class method named save(). This method will take care of the data validation for both tasks, and based upon whether a book ID was included in the POSTed contents, will either execute the Zend_Db’s insert() or update() method:

function save($entry) {
 
  // Define a message array
  $this->messages = array();
 
  // Title
  if (! Zend_Validate::is($entry['title'], 'NotEmpty')) {
    $this->messages[] = "Please provide a book title.";
  }           
 
  // Author
  if (! Zend_Validate::is($entry['author'], 'NotEmpty')) {
    $this->messages[] = "Please provide the author name.";
  }           
 
  // If no errors, insert entry into database
  if (count($this->messages) == 0) {
 
    // Modify an existing entry
    if (isset($entry['book-id'])) {
 
      $book = $this->find($entry['book-id'])->current();
 
      $data = array (
        'title'        => $entry['title'],
        'title'        => $entry['author']   
      );               
 
      $where = $this->getAdapter()->quoteInto('id = ?', $entry['book-id']);
      $id = $this->update($data, $where);
 
      if ($id) {
       return $id;        
      } else {
       $this->messages[] = "The book could not be updated.";
       return $this->messages;
      }               
 
    // Insert a new entry
    } else {
 
      $data = array (
        'title' => $entry['title'],
        'author' => $entry['author']   
      );               
 
      $id = $this->insert($data);
 
      if ($id) {
       return TRUE;        
      } else {
       $this->messages[] = "The book could not be added.";
       return $this->messages;
      }               
 
    }
 
  } else {
    return $this->messages;
  }
 
}

With the data logic moved into the model, the add() and edit() actions can be significantly reduced in size:

public function addAction()
{
 
  if ($this->getRequest()->isPost()) {
 
    $book = new Default_Model_Book();
 
    $result = $book->save($_POST);
   
    if (is_array($result)) {
      $this->view->messages = $result;
    } else {
      $this->view->messages[] = "The book has been added.";
    }
 
  }
   
}
 
public function editAction()
{
 
  $book = new Default_Model_Book();
  
  if ($this->getRequest()->isPost()) {
 
    $result = $book->save($_POST);
   
    if (is_array($result)) {
      $this->view->messages = $result;
    } else {
      $this->view->messages[] = "The book has been modified.";
    }
   
  }
   
}

An added bonus of this approach is you can greatly reduce the amount of coding time involved in creating CRUD operations because, for instance, now you can create the edit() method by simply copying your add() action, rename the copied version as edit(), and slightly modify the return message. In fact, with a bit of clever programming you could reduce the add and edit tasks down to a single action. I’ll leave this task to you but invite you to e-mail me with your ideas!

Fat Models, Skinny Controllers, and Better Code

Fully embracing the MVC pattern by maintaining cleanly separated model and controller logic can go a long way towards producing cleaner, more maintainable code. Try employing this approach in your next project and I guarantee you’ll be satisfied by the outcome!

About the Author

Jason Gilmore is founder of EasyPHPWebsites.com, and author of the popular book, “Easy PHP Websites with the Zend Framework”. Formerly Apress’ open source editor, Jason fostered the development of more than 60 books, along the way helping to transform their open source line into one of the industry’s most respected publishing programs. Over the years he’s authored several other books, including the best-selling Beginning PHP and
MySQL: From Novice to Professional
(currently in its third edition), Beginning PHP and PostgreSQL: From Novice to Professional, and Beginning PHP and Oracle: From Novice to Professional.

Jason is a cofounder and speaker chair of CodeMash, a nonprofit organization tasked with hosting an annual namesake developer’s conference, and was a member of the 2008 MySQL Conference speaker selection board.

Jason has published more than 100 tutorials and articles within prominent publications such as Developer.com,
Linux Magazine, and TechTarget.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories