Drupal Zombie

Dezombifying Drupal developers since 2011

    Braaains!
Eat braaains!
News feed

MVC Z: Model, View, Controller, and Zombies

Submitted by Kieran on

Tags: 

A software design pattern is a design that's used again and again, because it works well. Some patterns show up in Drupal. If you know the pattern, it's easier to understand what Drupal is doing.

One of the best-known patterns is model-view-controller (MVC). It's used in Drupal's entities-bundles-fields stuff, among other places. If you look on the Drupal.Org (DO) page Providing a new entity type, you'll see this line of code:

'controller class' => 'EntityAPIController',

Controller? What's that? DO's documentation assumes you know. But what if you don't?

This article explains how MVC works. Not in Drupal, but with a simpler example.

Design

The components of MVC are:

  • Model - data and code about objects, like customers, orders, and Web pages.
  • View - code that shows object data to users.
  • Controller - code that responds to user requests.

Let's talk about each one, then see how they work together.

Model

The model manages the data and business logic. For example, if the application was online banking, the model would know about customers, accounts, transactions, etc.

The model manages data, such as:

  • Buffy's checking account balance is $1,281.
  • On August 15, 2011, Spike withdrew $666 from his savings account.
  • Xander is male.

The model also has code to implement business rules, such as:

  • Customers cannot deposit a negative amount of money.
  • When a checking account has a balance of less than $2,000, each check transaction costs $1.00.
  • Customers can be male or female, but not both.

Managing data is all the model does. It can read and write data, and apply business rules. It doesn't know how to display data. It doesn't know how to process user commands.

View

A view displays data. It doesn't know how to get the data in the first place. But if you give it data, the view will show the data to the user.

Some applications have many views. For the banking app, there might be a view to show customer data (name, address, email, etc.), a view for each type of transaction, a view for account status, and so on.

Controller

The controller handles user commands, like "change customer name from Aud to Anyanka." It has code to execute each command. The controller doesn't know how to read or write data, or show it to the user. But it can fetch data from the model, and send data to a view.

How MVC works

Here's how the pieces work together:

MVC overview

Figure 1. MVC overview

The user sends a command to the controller (1), like "Show the balance for my checking account." The user might be clicking a link on a Web page, pressing a button on an ATM, or speaking into a telephone. It doesn't matter.

The controller has code for a bunch of commands. It runs the code for "show checking account balance." The code says to the model (2), "Give me the balance for user 95871924's checking account." The model looks in the data store, and returns the balance. The data store isn't part of the MVC model, but it's usually there. Only the model can access the data store directly.

The controller selects the right view for showing account balances. It sends the balance to the view. The view shows the data to the user (3). Could be a Web page, a text message on an ATM, or voice response on a telephone.

Figure 1 is MVC in action. There's a model that manages the data. There are views that show data. There's a controller that responds to user commands. For each command, the controller gets the data it needs from the model, processes it, and sends the results to a view.

This is one version of MVC. There are other versions that link M, V, and C differently.

What's the big?

The main advantage of MVC is code independence. The model can send data to Web apps, native iPhone apps, Java apps, whatever. All of these apps use the same data. You can build the model software once, and reuse it a dozen times.

The model doesn't have to be in the same language as the controller, as long as it uses a standard method to exchange data with the controller. Like JSON, for example. Your model could be in Java, and the controller in Ruby. Views for Web browsers could be in PHP. Views for Android phones could be in Java. You can write in whatever language is best for the platform(s) you're using.

What if the business rules change? For example, the charge per check might go down from $1.00 to $0.50. (Well, it could happen. In some parallel universe.) You just change the code in the model. The controller and views don't care.

What if you want to move your Web app from HTML 4 to HTML 5? You change the appropriate views. The controller and model don't need to change.

The separation of M, V, and C saves money, and it also makes your apps more agile. A new device comes out, like the iPad? You just change the views. You don't need to mess with the other components. (In principle, anyway.)

Example

Let's see how MVC works in practice. This example is simplified, but it'll help explain things.

This is a zombie Web app. It has two displays. The first one lists the zombies in a horde:

Horde display

Figure 2. Horde display

Click Reset to return the data to its original state. Click on the name of a zombie, and you see details about it:

Zombie display

Figure 3. Zombie display

The Delete link will erase the zombie's data. The Back link goes back to the horde list.

You can try the application. Delete some things. It won't affect other readers.

Model

The model has two parts:

  • Code to manage data for one zombie object.
  • Code to manage a collection (a horde) of zombie objects.

The zombie model

The model is written in a object-oriented programming (OOP) style. Most of Drupal is not OOPy, but important parts of it are. Some modules, like Views, have a lot of OOPiness.

Most MVC work is OOPful. Let's cover some OOP basics, in case this is new to you.

A "class" is like a template for different objects. Here is a class for the zombie model:

Zombie class

Figure 4. Zombie class

The class has walls around it, containing data and code. This class has three properties for each zombie: id, name, and number of limbs. Code on the outside can "set" the data (e. g., "You have 3 limbs") and "get" the data (e. g., "How many limbs?").

Data can only enter and leave the class through gates in the walls. The gates are functions, with names like set_limbs() (send data into the class) and get_limbs() (get data out of the class).

BTW, functions in OOP are called "methods," not functions. Why? Errr..., just because, that's why.

Each gate has a bear guarding it. Actually, the bear is usually an if statement, but bears are more fun to put in a drawing. The bears make sure that the data makes sense. For example, zombies always have from 0 to 4 limbs. Call set_limbs(3) and the bear would say, "OK, you can come in." But call set_limbs(8), and the bear would growl. "Grrrr, you can't come in." It would block the 8 from getting into the object. If the 8 persisted, the bear would eat it.

This concept is called "encapsulation". The class encapsulates data and code, and protects it.

Remember that a class is like a template. You apply the template with statements like this:

$adam = new ModelZombie(1, 'Adam', 4);

This makes an object, based on the class:

The $adam object

Figure 5. The $adam object

This statement:

$master = new ModelZombie(3, 'Master', 3);

would make:

The $master object

Figure 6. The $master object

Here's the code for the zombie model, in the file model-zombie.php. It manages data for a single zombie.

  1. <?php
  2. /**
  3.  * Model of a zombie.
  4.  */
  5. class ModelZombie {
  6.   private $id;
  7.   private $name;
  8.   private $limbs;
  9.  
  10.   /**
  11.    * Construct a zombie object.
  12.    *
  13.    * @param integer $id Zombie id, e. g., 8
  14.    * @param string $name Zombie name, e. g., Professor Walsh
  15.    * @param integer $limbs Number of limbs, from 0 to 4
  16.    */
  17.   public function __construct($id, $name, $limbs) {
  18.     if ( $id > 0 ) {
  19.       $this->id = $id;
  20.     }
  21.     $this->set_name($name);
  22.     $this->set_limbs($limbs);
  23.   }
  24.  
  25.   /**
  26.    * Get the id of the zombie.
  27.    *
  28.    * @return integer Zombie's id, e. g., 8
  29.    */
  30.   public function get_id() {
  31.     return $this->id;
  32.   }
  33.  
  34.   /**
  35.    * Set the zombie's number of limbs, from 0 to 4.
  36.    *
  37.    * @param integer $limbs Number of limbs
  38.    */
  39.   public function set_limbs($limbs = 4) {
  40.     if ( $limbs >= 0 && $limbs <= 4 ) {
  41.       $this->limbs = $limbs;
  42.     }
  43.   }
  44.  
  45.   /**
  46.    * Get the zombie's number of limbs, from 0 to 4.
  47.    *
  48.    * @return integer Number of limbs
  49.    */
  50.   public function get_limbs() {
  51.     return $this->limbs;
  52.   }
  53.  
  54.   /**
  55.    * Set the zombie's name.
  56.    *
  57.    * @param string $name Zombie's name
  58.    */
  59.   public function set_name($name) {
  60.     if ( strlen($name) > 0 ) {
  61.       $this->name = $name;
  62.     }
  63.   }
  64.  
  65.   /**
  66.    * Get the zombie's name.
  67.    *
  68.    * @return string Zombie's name
  69.    */
  70.   public function get_name() {
  71.     return $this->name;
  72.   }
  73.  
  74. }

Figure 7. Zombie class

Lines 6, 7, and 8 define the data: id, name, and number of limbs. Each one is marked private. This means that code outside the class cannot access the data. At least not directly. It has to go through the setters and getters. They are marked as public (e. g., line 39), so they can be called from the outside.

What about the bears? They're on lines 18, 40, and 60. Grrr!

The method __construct() is on lines 17 to 23. PHP calls this method automatically when a new object is created. So this line:

$adam = new ModelZombie(1, 'Adam', 4);

calls the method. This type of method is called a "constructor."

Notice that the property name has a setter (set_name()) and a getter (get_name()). limbs has a setter (set_limbs()) and a getter (get_limbs()) as well. But id has only a getter (get_id()). Once id is set in the constructor, it cannot be changed. This is an implied bear. There is no way through the wall to id, once the constructor has run.

Here's another zombie object:

$darla = new ModelZombie(18, 'Darla', 4);

If Buffy chopped off Darla's right arm:

$darla->set_limbs(3);

That's how you call a method, with the -> operator. You could also have:

$adam->set_limbs(0);

$darla and $adam are separate objects. They don't interfere with each other.

The code in Figure 7 has something called $this. This $this is the current object. So for:

$darla->set_limbs(3);

$this is $darla. For:

$adam->set_limbs(0);

$this is $adam.

There's something missing. The code in Figure 7 doesn't read or write zombie data from a data store. That's in the other part of the model, the horde.

The horde model

A horde is a collection of zombies objects.

Horde

Figure 8. Horde

The horde model has a wall around it, too. There are methods (gates in the wall), that allow outside code to add zombies, remove zombies, and search for a zombie. The gate at the bottom represents methods that don't pass any zombie objects. The methods let outside code ask something about the horde, like how many zombies are in it.

Here's the code for model-zombie-horde.php:

  1. <?php
  2. /**
  3.  * Model of a zombie horde.
  4.  */
  5. class ModelZombieHorde {
  6.  
  7.   /**
  8.    * Constructor. Sets up horde array if it doesn't exist.
  9.    */
  10.   public function __construct() {
  11.     session_start();
  12.     if ( !isset($_SESSION['horde']) ) {
  13.       $_SESSION['horde'] = array();
  14.     }
  15.   }
  16.  
  17.   /**
  18.    * Get the number of zombies in the horde.
  19.    *
  20.    * @return integer Number of zombies.
  21.    */
  22.   public function get_zombie_count() {
  23.     session_start();
  24.     return sizeof($_SESSION['horde']);
  25.   }
  26.  
  27.   /**
  28.    * Add a zombie to the horde.
  29.    *
  30.    * @param ModelZombie $zombie
  31.    */
  32.   public function add_zombie($zombie) {
  33.     session_start();
  34.     $_SESSION['horde'][$zombie->get_id()] = $zombie;
  35.   }
  36.  
  37.   /**
  38.    * Delete a zombie from the horde.
  39.    *
  40.    * @param integer $id Id of the zombie to delete.
  41.    */
  42.   public function delete_zombie($id) {
  43.     session_start();
  44.     unset($_SESSION['horde'][$id]);
  45.   }
  46.  
  47.   /**
  48.    * Delete all zombies from the horde.
  49.    */
  50.   public function delete_all() {
  51.     session_start();
  52.     $_SESSION['horde'] = array();
  53.   }
  54.  
  55.   /**
  56.    * Find the zombie with the given id.
  57.    *
  58.    * @param integer $id The id of the zombie to find
  59.    * @return ModelZombie Zombie with the id
  60.    */
  61.   public function find_zombie($id) {
  62.     session_start();
  63.     return $_SESSION['horde'][$id];
  64.   }
  65.  
  66.   /**
  67.    * Get array of all zombies in the horde.
  68.    *
  69.    * @return array Zombies
  70.    */
  71.   public function get_horde() {
  72.     session_start();
  73.     return $_SESSION['horde'];
  74.   }
  75.  
  76. }
     

Figure 9. Zombie horde model

Horde data is kept in the session, for this example. Most real apps would use a database, but this model stores horde data in $_SESSION['horde'].

You can add zombies with the add_zombie() method (lines 32 - 35). The zombie's id is used as its key into the horde array:

$_SESSION['horde'][$zombie->get_id()] = $zombie;

Something important: only the ModelZombieHorde class accesses data in the data store. Code outside ModelZombieHorde has to ask ModelZombieHorde to get any data. This means that ModelZombieHorde can switch to storing data in, say, an Oracle database, and no changes to views or the controller would be needed.

Summary so far. We're talking about MVC, a design pattern that separates business data and logic (model) from displays (views) and code that interprets user commands (controller). We've looked at the model, that stores data about zombies and hordes. The model uses OOP to maintain control over the data.

Views

Views display data to the user. That's all they do. They don't know to read data, write it, check business rules, or whatever.

Recall that there are two displays:

Horde display

Figure 2 (again). Horde display

Click on the name of a zombie, and you see details about it:

Zombie display

Figure 3 (again). Zombie display

Let's start with the view code for Figure 3, since it's a little simpler. Here's the file view-show-zombie.php:

  1. <?php
  2.   //View to show one zombie.
  3.   //Requires $zombie to be set to a zombie object.
  4. ?><!doctype html>
  5. <html lang="en">
  6. <head>
  7.   <meta charset="utf-8">
  8.   <title>Zombie</title>
  9. </head>
  10. <body>
  11.   <h1>A Zombie</h1>
  12.   <p>Id: <?php print $zombie->get_id(); ?></p>
  13.   <p>Name: <?php print $zombie->get_name(); ?></p>
  14.   <p>Limbs: <?php print $zombie->get_limbs(); ?></p>
  15.   <p>
  16.     <a href="index.php?cmd=delete-zombie&id=<?php print $zombie->get_id(); ?>">
  17.       Delete
  18.     </a>
  19.   </p>
  20.   <p><a href="index.php?cmd=list">Back</a></p>
  21. </body>
  22. </html>

Figure 10. View code for a zombie

Lines 1 - 3 are documentation. The rest is plain HTML, except where the zombie data is displayed. For example, line 13 is:

<p>Name: <?php print $zombie->get_name(); ?></p>

The code doesn't care where $zombie comes from, as long as it exists, and has a get_name() method.

Here's the code for the view in Figure 2, the horde, in the file view-show-zombie-horde.php:

  1. <?php
  2.   //View to show the horde.
  3.   //Requires $horde to be an array of zombie objects.
  4. ?><!doctype html>
  5. <html lang="en">
  6. <head>
  7.   <meta charset="utf-8">
  8.   <title>Zombie Horde</title>
  9. </head>
  10. <body>
  11.   <h1>Zombie Horde</h1>
  12.   <ul>
  13.   <?php foreach ($horde as $zombie):
  14.     $id = $zombie->get_id();
  15.     $name = $zombie->get_name();
  16.     ?>
  17.     <li>
  18.       <a href="index.php?cmd=show-zombie&id=<?php print $id; ?>">
  19.         <?php print $name; ?>
  20.       </a>
  21.     </li>
  22.   <?php endforeach; ?>
  23.   <p><a href="index.php?cmd=reset">Reset</a></p>
  24.   </ul>
  25. </body>
  26. </html>

Figure 11. View code for the zombie horde

Again, most of it is HTML. The loop from line 13 to 22 goes through an array called $horde, and for each zombie writes HTML for a link to show the zombie details (Figure 3).

The views in Figures 10 and 11 might remind you of something Drupalistic: theme templates. No surprise there. Drupal theme templates serve the same purpose as our views. They separate display from other parts of the app.

Summary. We have a model that manages data and business logic. The model is the only code in the entire application that can access the data store. We have two views, one for each type of Web page we want to make.

Controller

The controller is where it all comes together. It gets commands from the user, and executes them.

Our controller has four commands:

  1. list: Show the horde.
  2. show-zombie id: Show the zombie with the given id.
  3. delete-zombie id: Delete the zombie with the given id.
  4. reset: Initialize the data store. Erases all the zombie objects, and makes some new ones.

Our controller is in the file index.php. This one file handles all of our commands. How does the controller know what command to run? From an argument passed in through the URL. Examples of each command:

  1. index.php?cmd=list
  2. index.php?cmd=show-zombie&id=3
  3. index.php?cmd=delete-zombie&id=2
  4. index.php?cmd=reset

The controller gets the command from the cmd parameter, and does whatever is needed. This means asking the model and views for help.

Here's the code for the controller, index.php:

  1. <?php
  2. //Controller for the zombie horde app.
  3. //Include model definitions.
  4. require_once 'model-zombie.php';
  5. require_once 'model-zombie-horde.php';
  6. //Load the horde data.
  7. $horde = new ModelZombieHorde();
  8. //Get the command sent in the URL. Defaults to reset.
  9. $command = $_GET['cmd'] ? $_GET['cmd'] : 'reset';
  10. //Process the command.
  11. switch ($command) {
  12.   //Set the horde to its initial state.
  13.   case 'reset':
  14.     //Clear current zombies.
  15.     $horde->delete_all();
  16.     //Add some zombies.
  17.     $horde->add_zombie( new ModelZombie(1, 'Adam', 4) );
  18.     $horde->add_zombie( new ModelZombie(2, 'Master', 3) );
  19.     $horde->add_zombie( new ModelZombie(3, 'Harmony', 0) );
  20.     $horde->add_zombie( new ModelZombie(4, 'Gentleman', 4) );
  21.     $horde->add_zombie( new ModelZombie(5, 'Mayor', 2) );
  22.     //Show the horde.
  23.     header('Location: index.php?cmd=list');
  24.     exit;
  25.   //List all the members of the horde.
  26.   case 'list':
  27.     //Get horde in array.
  28.     $horde = $horde->get_horde();
  29.     //Show horde view.
  30.     require_once 'view-show-zombie-horde.php';
  31.     break;
  32.   //Show a zombie.
  33.   case 'show-zombie':
  34.     //Get zombie id from the URL.
  35.     $id = $_GET['id'];
  36.     //Find the zombie in the horde.
  37.     $zombie = $horde->find_zombie($id);
  38.     //Show the zombie view.
  39.     require_once 'view-show-zombie.php';
  40.     break;
  41.   //Delete a zombie from the horde.
  42.   case 'delete-zombie':
  43.     //Get the zombie id from the URL.
  44.     $id = $_GET['id'];
  45.     //Delete the zombie.
  46.     $horde->delete_zombie($id);
  47.     //Show the horde list.
  48.     header('Location: index.php?cmd=list');
  49.     exit;
  50. }

Line 9 gets the command from the URL. If no command is sent, it defaults to reset. Why? So that when you first go to the site (to index.php, with no arguments), the horde is built.

Let's see how the controller executes the show-zombie command (lines 33 - 40). Remember that the command shows data for a single zombie.

First, the controller gets the id of the zombie to show:

$id = $_GET['id'];

Then it asks the model for the zombie with that id:

$zombie = $horde->find_zombie($id);

The controller doesn't care where the model object $horde gets the data. The controller just wants a zombie object.

Finally, the controller runs the right view:

require_once 'view-show-zombie.php';

The view expects to have data in a variable $zombie. The controller has already done that.

The controller doesn't know what sort of view view-show-zombie.php is creating. It's HTML 5 in this case, but it could be plain text, audio response, or anything.

That's all

MVC is a common design pattern. It's used a lot, because it separates data from display from command response. This makes software cheaper to build, and more flexible.

Drupal uses MVC (e. g., the fields code). You'll need to know about the pattern, if you want to get the most from those parts of Drupal.

This article introduces MVC. The example is simple, but it shows the key idea of "separation of concerns." The article also explained a little bit of OOP, in case you needed that.

Hope this helps. Go forth, and resist zombification!

Add a comment

Basic WYSIWYG

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <blockquote> <br> <cite> <code> <dd> <div> <dl> <dt> <em> <h2> <h3> <h4> <h5> <img> <li> <ol> <p> <pre> <span> <strong> <ul>
    Allowed Style properties: border, border-style, border-width, float, height, margin, margin-left, position, text-align, width

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Images
Images for your comment.
Files must be less than 1 MB.
Allowed file types: png gif jpg jpeg.
CAPTCHA
Prove that your are sentient. Code has letters only.
Image CAPTCHA
Enter the characters shown in the image.