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:

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:

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:

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:

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:

Figure 5. The $adam object
This statement:
$master = new ModelZombie(3, 'Master', 3);
would make:

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.
- <?php
- /**
- * Model of a zombie.
- */
- class ModelZombie {
- private $id;
- private $name;
- private $limbs;
- /**
- * Construct a zombie object.
- *
- * @param integer $id Zombie id, e. g., 8
- * @param string $name Zombie name, e. g., Professor Walsh
- * @param integer $limbs Number of limbs, from 0 to 4
- */
- public function __construct($id, $name, $limbs) {
- if ( $id > 0 ) {
- $this->id = $id;
- }
- $this->set_name($name);
- $this->set_limbs($limbs);
- }
- /**
- * Get the id of the zombie.
- *
- * @return integer Zombie's id, e. g., 8
- */
- public function get_id() {
- return $this->id;
- }
- /**
- * Set the zombie's number of limbs, from 0 to 4.
- *
- * @param integer $limbs Number of limbs
- */
- public function set_limbs($limbs = 4) {
- if ( $limbs >= 0 && $limbs <= 4 ) {
- $this->limbs = $limbs;
- }
- }
- /**
- * Get the zombie's number of limbs, from 0 to 4.
- *
- * @return integer Number of limbs
- */
- public function get_limbs() {
- return $this->limbs;
- }
- /**
- * Set the zombie's name.
- *
- * @param string $name Zombie's name
- */
- public function set_name($name) {
- if ( strlen($name) > 0 ) {
- $this->name = $name;
- }
- }
- /**
- * Get the zombie's name.
- *
- * @return string Zombie's name
- */
- public function get_name() {
- return $this->name;
- }
- }
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.

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:
- <?php
- /**
- * Model of a zombie horde.
- */
- class ModelZombieHorde {
- /**
- * Constructor. Sets up horde array if it doesn't exist.
- */
- public function __construct() {
- session_start();
- if ( !isset($_SESSION['horde']) ) {
- $_SESSION['horde'] = array();
- }
- }
- /**
- * Get the number of zombies in the horde.
- *
- * @return integer Number of zombies.
- */
- public function get_zombie_count() {
- session_start();
- return sizeof($_SESSION['horde']);
- }
- /**
- * Add a zombie to the horde.
- *
- * @param ModelZombie $zombie
- */
- public function add_zombie($zombie) {
- session_start();
- $_SESSION['horde'][$zombie->get_id()] = $zombie;
- }
- /**
- * Delete a zombie from the horde.
- *
- * @param integer $id Id of the zombie to delete.
- */
- public function delete_zombie($id) {
- session_start();
- unset($_SESSION['horde'][$id]);
- }
- /**
- * Delete all zombies from the horde.
- */
- public function delete_all() {
- session_start();
- $_SESSION['horde'] = array();
- }
- /**
- * Find the zombie with the given id.
- *
- * @param integer $id The id of the zombie to find
- * @return ModelZombie Zombie with the id
- */
- public function find_zombie($id) {
- session_start();
- return $_SESSION['horde'][$id];
- }
- /**
- * Get array of all zombies in the horde.
- *
- * @return array Zombies
- */
- public function get_horde() {
- session_start();
- return $_SESSION['horde'];
- }
- }
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:

Figure 2 (again). Horde display
Click on the name of a zombie, and you see details about it:

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:
- <?php
- //View to show one zombie.
- //Requires $zombie to be set to a zombie object.
- ?><!doctype html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <title>Zombie</title>
- </head>
- <body>
- <h1>A Zombie</h1>
- <p>Id: <?php print $zombie->get_id(); ?></p>
- <p>Name: <?php print $zombie->get_name(); ?></p>
- <p>Limbs: <?php print $zombie->get_limbs(); ?></p>
- <p>
- <a href="index.php?cmd=delete-zombie&id=<?php print $zombie->get_id(); ?>">
- Delete
- </a>
- </p>
- <p><a href="index.php?cmd=list">Back</a></p>
- </body>
- </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:
- <?php
- //View to show the horde.
- //Requires $horde to be an array of zombie objects.
- ?><!doctype html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <title>Zombie Horde</title>
- </head>
- <body>
- <h1>Zombie Horde</h1>
- <ul>
- <?php foreach ($horde as $zombie):
- $id = $zombie->get_id();
- $name = $zombie->get_name();
- ?>
- <li>
- <a href="index.php?cmd=show-zombie&id=<?php print $id; ?>">
- <?php print $name; ?>
- </a>
- </li>
- <?php endforeach; ?>
- <p><a href="index.php?cmd=reset">Reset</a></p>
- </ul>
- </body>
- </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:
list: Show the horde.show-zombie id: Show the zombie with the given id.delete-zombie id: Delete the zombie with the given id.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:
index.php?cmd=listindex.php?cmd=show-zombie&id=3index.php?cmd=delete-zombie&id=2index.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:
- <?php
- //Controller for the zombie horde app.
- //Include model definitions.
- require_once 'model-zombie.php';
- require_once 'model-zombie-horde.php';
- //Load the horde data.
- $horde = new ModelZombieHorde();
- //Get the command sent in the URL. Defaults to reset.
- $command = $_GET['cmd'] ? $_GET['cmd'] : 'reset';
- //Process the command.
- switch ($command) {
- //Set the horde to its initial state.
- case 'reset':
- //Clear current zombies.
- $horde->delete_all();
- //Add some zombies.
- $horde->add_zombie( new ModelZombie(1, 'Adam', 4) );
- $horde->add_zombie( new ModelZombie(2, 'Master', 3) );
- $horde->add_zombie( new ModelZombie(3, 'Harmony', 0) );
- $horde->add_zombie( new ModelZombie(4, 'Gentleman', 4) );
- $horde->add_zombie( new ModelZombie(5, 'Mayor', 2) );
- //Show the horde.
- header('Location: index.php?cmd=list');
- exit;
- //List all the members of the horde.
- case 'list':
- //Get horde in array.
- $horde = $horde->get_horde();
- //Show horde view.
- require_once 'view-show-zombie-horde.php';
- break;
- //Show a zombie.
- case 'show-zombie':
- //Get zombie id from the URL.
- $id = $_GET['id'];
- //Find the zombie in the horde.
- $zombie = $horde->find_zombie($id);
- //Show the zombie view.
- require_once 'view-show-zombie.php';
- break;
- //Delete a zombie from the horde.
- case 'delete-zombie':
- //Get the zombie id from the URL.
- $id = $_GET['id'];
- //Delete the zombie.
- $horde->delete_zombie($id);
- //Show the horde list.
- header('Location: index.php?cmd=list');
- exit;
- }
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