Symfony is based on the classic web design pattern known as the MVC architecture, which consists of three levels:

  • The Model represents the information on which the application operates--its business logic.
  • The View renders the model into a web page suitable for interaction with the user.
  • The Controller responds to user actions and invokes changes on the model or view as appropriate.

Figure 2-1 illustrates the MVC pattern.

The MVC architecture separates the business logic (model) and the presentation (view), resulting in greater maintainability. For instance, if your application should run on both standard web browsers and handheld devices, you just need a new view; you can keep the original controller and model. The controller helps to hide the detail of the protocol used for the request (HTTP, console mode, mail, and so on) from the model and the view. And the model abstracts the logic of the data, which makes the view and the action independent of, for instance, the type of database used by the application.

The MVC pattern

Figure 2.1 The MVC pattern

2.1.1. MVC Layering

To help you understand MVC's advantages, let's see how to convert a basic PHP application to an MVC-architectured application. A list of posts for a weblog application will be a perfect example.

2.1.1.1. Flat Programming

In a flat PHP file, displaying a list of database entries might look like the script presented in Listing 2-1.

Listing 2-1 - A Flat Script

<?php

// Connecting, selecting database
$link = mysql_connect('localhost', 'myuser', 'mypassword');
mysql_select_db('blog_db', $link);

// Performing SQL query
$result = mysql_query('SELECT date, title FROM post', $link);

?>

<html>
  <head>
    <title>List of Posts</title>
  </head>
  <body>
   <h1>List of Posts</h1>
   <table>
     <tr><th>Date</th><th>Title</th></tr>
<?php
// Printing results in HTML
while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
{
echo "\t<tr>\n";
printf("\t\t<td> %s </td>\n", $row['date']);
printf("\t\t<td> %s </td>\n", $row['title']);
echo "\t</tr>\n";
}
?>
    </table>
  </body>
</html>

<?php

// Closing connection
mysql_close($link);

?>

That's quick to write, fast to execute, and impossible to maintain. The following are the major problems with this code:

  • There is no error-checking (what if the connection to the database fails?).
  • HTML and PHP code are mixed, even interwoven together.
  • The code is tied to a MySQL database.

2.1.1.2. Isolating the Presentation

The echo and printf calls in Listing 2-1 make the code difficult to read. Modifying the HTML code to enhance the presentation is a hassle with the current syntax. So the code can be split into two parts. First, the pure PHP code with all the business logic goes in a controller script, as shown in Listing 2-2.

Listing 2-2 - The Controller Part, in index.php

<?php

 // Connecting, selecting database
 $link = mysql_connect('localhost', 'myuser', 'mypassword');
 mysql_select_db('blog_db', $link);

 // Performing SQL query
 $result = mysql_query('SELECT date, title FROM post', $link);

 // Filling up the array for the view
 $posts = array();
 while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
 {
    $posts[] = $row;
 }

 // Closing connection
 mysql_close($link);

 // Requiring the view
 require('view.php');

The HTML code, containing template-like PHP syntax, is stored in a view script, as shown in Listing 2-3.

Listing 2-3 - The View Part, in view.php

<html>
  <head>
    <title>List of Posts</title>
  </head>
  <body>
    <h1>List of Posts</h1>
    <table>
      <tr><th>Date</th><th>Title</th></tr>
    <?php foreach ($posts as $post): ?>
      <tr>
        <td><?php echo $post['date'] ?></td>
        <td><?php echo $post['title'] ?></td>
      </tr>
    <?php endforeach; ?>
    </table>
  </body>
</html>

A good rule of thumb to determine whether the view is clean enough is that it should contain only a minimum amount of PHP code, in order to be understood by an HTML designer without PHP knowledge. The most common statements in views are echo, if/endif, foreach/endforeach, and that's about all. Also, there should not be PHP code echoing HTML tags.

All the logic is moved to the controller script, and contains only pure PHP code, with no HTML inside. As a matter of fact, you should imagine that the same controller could be reused for a totally different presentation, perhaps in a PDF file or an XML structure.

2.1.1.3. Isolating the Data Manipulation

Most of the controller script code is dedicated to data manipulation. But what if you need the list of posts for another controller, say one that would output an RSS feed of the weblog posts? What if you want to keep all the database queries in one place, to avoid code duplication? What if you decide to change the data model so that the post table gets renamed weblog_post? What if you want to switch to PostgreSQL instead of MySQL? In order to make all that possible, you need to remove the data-manipulation code from the controller and put it in another script, called the model, as shown in Listing 2-4.

Listing 2-4 - The Model Part, in model.php

<?php

function getAllPosts()
{
  // Connecting, selecting database
  $link = mysql_connect('localhost', 'myuser', 'mypassword');
  mysql_select_db('blog_db', $link);

  // Performing SQL query
  $result = mysql_query('SELECT date, title FROM post', $link);

  // Filling up the array
  $posts = array();
  while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
  {
     $posts[] = $row;
  }

  // Closing connection
  mysql_close($link);

  return $posts;
}

The revised controller is presented in Listing 2-5.

Listing 2-5 - The Controller Part, Revised, in index.php

<?php

// Requiring the model
require_once('model.php');

// Retrieving the list of posts
$posts = getAllPosts();

// Requiring the view
require('view.php');

The controller becomes easier to read. Its sole task is to get the data from the model and pass it to the view. In more complex applications, the controller also deals with the request, the user session, the authentication, and so on. The use of explicit names for the functions of the model even makes code comments unnecessary in the controller.

The model script is dedicated to data access and can be organized accordingly. All parameters that don't depend on the data layer (like request parameters) must be given by the controller and not accessed directly by the model. The model functions can be easily reused in another controller.

2.1.2. Layer Separation Beyond MVC

So the principle of the MVC architecture is to separate the code into three layers, according to its nature. Data logic code is placed within the model, presentation code within the view, and application logic within the controller.

Other additional design patterns can make the coding experience even easier. The model, view, and controller layers can be further subdivided.

2.1.2.1. Database Abstraction

The model layer can be split into a data access layer and a database abstraction layer. That way, data access functions will not use database-dependent query statements, but call some other functions that will do the queries themselves. If you change your database system later, only the database abstraction layer will need updating.

A sample database abstraction layer is presented in Listing 2-6, followed by an example of a MySQL-specific data access layer in Listing 2-7.

Listing 2-6 - The Database Abstraction Part of the Model

<?php

function open_connection($host, $user, $password)
{
  return mysql_connect($host, $user, $password);
}

function close_connection($link)
{
  mysql_close($link);
}

function query_database($query, $database, $link)
{
  mysql_select_db($database, $link);

  return mysql_query($query, $link);
}

function fetch_results($result)
{
  return mysql_fetch_array($result, MYSQL_ASSOC);
}

Listing 2-7 - The Data Access Part of the Model

function getAllPosts()
{
  // Connecting to database
  $link = open_connection('localhost', 'myuser', 'mypassword');

  // Performing SQL query
  $result = query_database('SELECT date, title FROM post', 'blog_db', $link);

  // Filling up the array
  $posts = array();
  while ($row = fetch_results($result))
  {
     $posts[] = $row;
  }

  // Closing connection
  close_connection($link);

  return $posts;
}

You can check that no database-engine dependent functions can be found in the data access layer, making it database-independent. Additionally, the functions created in the database abstraction layer can be reused for many other model functions that need access to the database.

Note The examples in Listings 2-6 and 2-7 are still not very satisfactory, and there is some work left to do to have a full database abstraction (abstracting the SQL code through a database-independent query builder, moving all functions into a class, and so on). But the purpose of this book is not to show you how to write all that code by hand, and you will see in Chapter 8 that symfony natively does all the abstraction very well.

2.1.2.2. View Elements

The view layer can also benefit from some code separation. A web page often contains consistent elements throughout an application: the page headers, the graphical layout, the footer, and the global navigation. Only the inner part of the page changes. That's why the view is separated into a layout and a template. The layout is usually global to the application, or to a group of pages. The template only puts in shape the variables made available by the controller. Some logic is needed to make these components work together, and this view logic layer will keep the name view. According to these principles, the view part of Listing 2-3 can be separated into three parts, as shown in Listings 2-8, 2-9, and 2-10.

Listing 2-8 - The Template Part of the View, in mytemplate.php

<h1>List of Posts</h1>
<table>
<tr><th>Date</th><th>Title</th></tr>
<?php foreach ($posts as $post): ?>
  <tr>
    <td><?php echo $post['date'] ?></td>
    <td><?php echo $post['title'] ?></td>
  </tr>
<?php endforeach; ?>
</table>

Listing 2-9 - The View Logic Part of the View

<?php

$title = 'List of Posts';
$content = include('mytemplate.php');

Listing 2-10 - The Layout Part of the View

<html>
  <head>
    <title><?php echo $title ?></title>
  </head>
  <body>
    <?php echo $content ?>
  </body>
</html>

2.1.2.3. Action and Front Controller

The controller doesn't do much in the previous example, but in real web applications, the controller has a lot of work. An important part of this work is common to all the controllers of the application. The common tasks include request handling, security handling, loading the application configuration, and similar chores. This is why the controller is often divided into a front controller, which is unique for the whole application, and actions, which contain only the controller code specific to one page.

One of the great advantages of a front controller is that it offers a unique entry point to the whole application. If you ever decide to close the access to the application, you will just need to edit the front controller script. In an application without a front controller, each individual controller would need to be turned off.

2.1.2.4. Object Orientation

All the previous examples use procedural programming. The OOP capabilities of modern languages make the programming even easier, since objects can encapsulate logic, inherit from one another, and provide clean naming conventions.

Implementing an MVC architecture in a language that is not object-oriented raises namespace and code-duplication issues, and the overall code is difficult to read.

Object orientation allows developers to deal with such things as the view object, the controller object, and the model classes, and to transform all the functions in the previous examples into methods. It is a must for MVC architectures.

Tip If you want to learn more about design patterns for web applications in an object-oriented context, read Patterns of Enterprise Application Architecture by Martin Fowler (Addison-Wesley, ISBN: 0-32112-742-0). Code examples in Fowler's book are in Java or C#, but are still quite readable for a PHP developer.

2.1.3. Symfony's MVC Implementation

Hold on a minute. For a single page listing the posts in a weblog, how many components are required? As illustrated in Figure 2-2, we have the following parts:

  • Model layer
    • Database abstraction
    • Data access
  • View layer
    • View
    • Template
    • Layout
  • Controller layer
    • Front controller
    • Action

Seven scripts--a whole lot of files to open and to modify each time you create a new page! However, symfony makes things easy. While taking the best of the MVC architecture, symfony implements it in a way that makes application development fast and painless.

First of all, the front controller and the layout are common to all actions in an application. You can have multiple controllers and layouts, but you need only one of each. The front controller is pure MVC logic component, and you will never need to write a single one, because symfony will generate it for you.

The other good news is that the classes of the model layer are also generated automatically, based on your data structure. This is the job of the Propel library, which provides class skeletons and code generation. If Propel finds foreign key constraints or date fields, it will provide special accessor and mutator methods that will make data manipulation a piece of cake. And the database abstraction is totally invisible to you, because it is handled natively by PHP Data Objects. So if you decide to change your database engine at anytime, you have zero code to refactor. You just need to change one configuration parameter.

And the last thing is that the view logic can be easily translated as a simple configuration file, with no programming needed.

Symfony workflow

Figure 2.2 Symfony workflow

That means that the list of posts described in our example would require only three files to work in symfony, as shown in Listings 2-11, 2-12, and 2-13.

Listing 2-11 - list Action, in myproject/apps/myapp/modules/weblog/actions/actions.class.php

<?php

class weblogActions extends sfActions
{
  public function executeList()
  {
    $this->posts = PostPeer::doSelect(new Criteria());
  }
}

Listing 2-12 - list Template, in myproject/apps/myapp/modules/weblog/templates/listSuccess.php

<?php slot('title', 'List of Posts') ?>

<h1>List of Posts</h1>
<table>
<tr><th>Date</th><th>Title</th></tr>
<?php foreach ($posts as $post): ?>
  <tr>
    <td><?php echo $post->getDate() ?></td>
    <td><?php echo $post->getTitle() ?></td>
  </tr>
<?php endforeach; ?>
</table>

In addition, you will still need to define a layout, as shown in Listing 2-13, but it will be reused many times.

Listing 2-13 - Layout, in myproject/apps/myapp/templates/layout.php

<html>
  <head>
    <title><?php include_slot('title') ?></title>
  </head>
  <body>
    <?php echo $sf_content ?>
  </body>
</html>

And that is really all you need. This is the exact code required to display the very same page as the flat script shown earlier in Listing 2-1. The rest (making all the components work together) is handled by symfony. If you count the lines, you will see that creating the list of posts in an MVC architecture with symfony doesn't require more time or coding than writing a flat file. Nevertheless, it gives you huge advantages, notably clear code organization, reusability, flexibility, and much more fun. And as a bonus, you have XHTML conformance, debug capabilities, easy configuration, database abstraction, smart URL routing, multiple environments, and many more development tools.

2.1.4. Symfony Core Classes

The MVC implementation in symfony uses several classes that you will meet quite often in this book:

  • sfController is the controller class. It decodes the request and hands it to the action.
  • sfRequest stores all the request elements (parameters, cookies, headers, and so on).
  • sfResponse contains the response headers and contents. This is the object that will eventually be converted to an HTML response and be sent to the user.
  • The context (retrieved by sfContext::getInstance()) stores a reference to all the core objects and the current configuration; it is accessible from everywhere.

You will learn more about these objects in Chapter 6.

As you can see, all the symfony classes use the sf prefix, as do the symfony core variables in the templates. This should avoid name collisions with your own classes and variables, and make the core framework classes sociable and easy to recognize.

Note Among the coding standards used in symfony, UpperCamelCase is the standard for class and variable naming. Two exceptions exist: core symfony classes start with sf, which is lowercase, and variables found in templates use the underscore-separated syntax.