Monday, February 2, 2009

Blog with Zend Framework

In the typical blog application, we must have below points.

  1. Authentication for Authors
  2. Authorization to create/edit/delete/read entries
  3. Methods for adding new entries, and modifying them
  4. Publishing entries as RSS and Atom
  5. Permalinks unique for each entry (SEO friendly of course)
  6. Commenting system
  7. Spam detection for new comments
  8. Perhaps, trackback detection

Below tools will be required for this blog application
  1. Zend Framework 1.5
  2. PHPUnit
  3. HTMLPurifier
  4. Blueprint CSS Framework
  5. jQuery

The Model-View-Controller

The Model-View-Controller Pattern (or MVC as it's usually abbreviated) is a general solution to the question of how to separate responsibilities in an application in a highly structured manner. The pattern's name mentions all three separations: Model, View and Controller. Although MVC may seem to be one of those esoteric concepts in programming, it's actually quite a simple concept. You pick some nugget of functionality, determine it's purpose, and assign to one of the three separations, and then to a specific class. Once everything is split correctly, you end up with small pieces which are reusable, centralised, accessible, and fit together into building blocks with an abstract API - working now with abstracted APIs makes incremental changes extremely simple. With everything tidily organised into objects with specific responsibilities the cost of change is normally reduced significantly.

To be clear, the MVC is common as dirt. It is widely used in the Zend Framework, Solar, Symfony, Ruby on Rails, merb, Django, Spring, and countless other frameworks. It is an unescapable concept when adopting a framework for web applications in almost any language.

The Model

The Model is responsible for maintaining state between HTTP requests in a PHP web application. Any data which must be preserved between HTTP requests is destined for the Model segment of your application. This goes for user session data as much as rows in an external database. It also incorporates the rules and restraints governing that data which is referred to as the "business logic".

The View

The View is responsible for generating a user interface for your application. In PHP, this is often narrowly defined as where to put all your presentational HTML. While this is true, it's also the place where you can create a system of dynamically generating HTML, RSS, XML, JSON or indeed anything at all being sent to the client browser or application.

The View is ordinarily organised into template files but it can also simply be echoed from or manipulated by the Controller prior to output. It's essential to remember that the View is not just a file format - it also encompasses any PHP code or parsed tags used to organise, filter, decorate and manipulate the format based on data retrieved from one or more Models (or as is often the case, passed from the Model to the View by the Controller).

The Controller

Controllers are almost deceptively simple in comparison. The primary function of the Controller is to control and delegate. In a typical PHP request to an MVC architecture, the Controller will retrieve user input, supervise the filtering, validation and processing of that input, manage the Model, and finally delegate output generation to the View (optionally passing it one or more Models required to process the current template). The Controller also has a unique difference from other forms of PHP architectural forms since it only requires a single point of entry into the application - almost inevitably index.php.

Controller vs Model

No quick tour of MVC would be complete without a brief mention of at least one extremely common variance in MVC apps. Borrowing a term, it's the idea of a Fat Model and Thin Controller.

A Fat Model, is a Model which takes on as much business logic and data manipulation as you can fit into it. The result is a large body of reusable logic accessible from any Controller. This, in theory, results in a Thin Controller - when all this logic is bundled into the Model behind some suitable APIs, the Controller's average size should be reduced. Less Controller code, less obscuring crud hiding exactly what a Controller is doing.

The opposite is a Thin Model/Fat Controller - business logic is dumped into the Controller which obviously increases its size, and secondly means that code is not reusable (unless you decide to reuse the Controller from another Controller - which is rarely a good idea efficiency wise).

In concert these three segments of an application implement a Model-View-Controller architecture. It's become a widely recognised solution suitable for web applications and it's evident in the majority of the current generation of framework for many programming languages.

In the Zend Framework, these three separations are represented by the Zend_Db, Zend_View and Zend_Controller components. You'll be hearing a lot about these three and the classes they are composed of in the chapters to come! Together these form the backbone of the Zend Framework's MVC architecture and underpin a lot of it's best practices.

The Model-View-Controller was originally used to promote the "separation of concerns" in desktop GUI applications. By separating each concern into an independent layer of the application, it led to a decrease in coupling which in turn made applications easier to design, write, test and maintain. Although GUI applications have turned away from the MVC in recent years, it's proven to be highly effective when applied to web applications.

In the framework this adds a lot of predictable structure since each segment of MVC for any one supported request is segregated into its own group of files. The Controller is represented by Zend_Controller_Front and Zend_Controller_Action subclasses, the Model by subclasses of Zend_Db_Table, and the View by .phtml template files and View Helpers. The Zend Framework manages how each is orchestrated in the big picture, leaving you free to focus on just those groupings without worrying about all the code combining them together.

In a sense it's like building a house where the foundations, walls and internal wiring and plumbing are already in place, and all that's left is the internal decoration and a roof. It may take some time to learn how to decorate and roof the prepared sections but once you have learned how, later houses are finished a lot faster!

Set Up the Project Structure

Let's first create a basic MVC project structure for our application under the directory QuickStart:

zfblog/
---application/
------controllers/
------models/
------views/
---------scripts/
---library/
---public/
The "application" directory is where we place all the components of an application implementing the Model-View-Controller. Inside "application", "controllers", "models" and "views" represent respective locations of controller, model and view source code and templates.

Download & Install ZF

Download the latest version of Zend Framework and extract the contents of the library folder to your project's library directory. You should now find the top-level Zend directory which contains all Zend Framework components under your library directory.

That's it! Zend Framework is now installed and ready to use.

At the end of this step you should have the directory structure in place containing some empty files as follows (create them now, and we'll fill them in later):

/application/Bootstrap.php
/application/controller/IndexController.php
/application/views/scripts/index/index.phtml
/public/index.php
/public/.htaccess

Create a Rewrite Rule

Zend Framework's MVC implementation makes use of the Front Controller pattern. You must therefore rewrite all incoming requests (except those for static resources, which your application need not handle) to a single script that will initialize the FrontController and route the request.
If you're using mod_rewrite for the Apache web server, create the file QuickStart/public/.htaccess with the following contents:

# public/.htaccess

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule .* index.php

This set of rewrite rules specify that if the file exists under the document root directory, it should simply be served as a static resource. Otherwise, the request is for dynamic content and should be rewritten to our index.php script. Since all requests for non-static content will be rewritten to it, the index.php script serves as the entry point to our application.

Create a Bootstrap File

A Bootstrap.php file exists in the "application" directory which represents a class called "Bootstrap". It's purpose is to initialise the Zend Framework, adjust environment settings (for example, timezone and error_reporting level), and otherwise make application specific tweaks and additions before a HTTP request is processed. Most tutorials take an alternative view and put Bootstrap code into index.php. I strongly suggest you avoid this and use an actual class to organise the Bootstrap code - it makes it a lot easier to read if nothing else!


<?
require_once 'Zend/Loader.php';

class Bootstrap
{

public static $frontController = null;

public static function run()
{
self::setupEnvironment();
self::prepare();
$response = self::$frontController->dispatch();
self::sendResponse($response);
}

public static function setupEnvironment()
{
error_reporting(E_ALL|E_STRICT);
ini_set('display_errors', true);
date_default_timezone_set('Europe/London');
}

public static function prepare()
{
self::setupFrontController();
self::setupView();
}

public static function setupFrontController()
{
Zend_Loader::registerAutoload();
self::$frontController = Zend_Controller_Front::getInstance();
self::$frontController->throwExceptions(true);
self::$frontController->returnResponse(true);
self::$frontController->setControllerDirectory(
dirname(__FILE__) . '/controllers'
);
}

public static function setupView()
{
$view = new Zend_View;
$view->setEncoding('UTF-8');
$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer($view);
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
}

public static function sendResponse(Zend_Controller_Response_Http $response)
{
$response->setHeader('Content-Type', 'text/html; charset=UTF-8', true);
$response->sendResponse();
}

}

?>
The Bootstrap class has two main methods here: run() and prepare(). run() executes a full application request, whereas prepare() only sets up (but doesn't execute) the Front Controller. For now let's focus on run() and all the steps our Bootstrap takes when it's called. The prepare() method is something we'll use when applying TDD or BDD to developing controllers, since we then delegate controller execution to the testing framework.

The first thing is to setup the local environment. So here we've set a timezone (as required by PHP5), enabled PHP's display_errors option, and set a fairly stiff error reporting level.

Secondly we prepare the Front Controller. Zend_Controller_Front is an implementation of the Front Controller Design Pattern, which acts as a single point of entry into a Model-View-Controller architected application.

Here, the prepare() stage involves setting up the Front Controller:
  • get an instance of Zend_Controller_Front
  • set it to throw all Exceptions (might want to disable this in production)
  • set it to return a Response object when dispatched
  • tell it where to find the default controllers
The last step of prepare() makes some changes to the View. The reason of this is to show how to make an extremely common change - adding support for UTF-8 encoded output (such as my name!).

/public/index.php

<?
// Update after deployment for location of non-public files
$root = dirname(dirname(__FILE__));

// We're assuming the Zend Framework is already on the include_path
set_include_path(
$root . '/application' . PATH_SEPARATOR
. $root . '/library' . PATH_SEPARATOR
. get_include_path()
);

require_once 'Bootstrap.php';

Bootstrap::run();

?>

Implementing The Index Controller

Before we continue, a quick word on controller/view wiring. Some time back it was decided to make automated rendering the default mode of operation for the Zend Framework. This means Controller will rarely need manual intervention to render a View - instead an Action Helper called the ViewRenderer (which should sound familiar since it used it to add UTF-8 encoding in our Bootstrap!) is called upon. This helper locates a View matching the name of the current Controller and Action and automatically renders it. There are ways to disable this automated rendering as documented in the manual - which is often useful when you want to bypass Zend_View (e.g. maybe you're outputting finalised JSON or XML and no further templating or processing is needed).

If a URL to a Zend Framework application does not contain a controller or action reference, then the Zend Framework uses the default "index" value. Since we only intend requesting to the root URL of "http://localhost/zfblog/public/", we'll need an Index Controller containing an Index Action. Let's create one!

Add a file called "IndexController.php" in application/controllers containing:

<?
class IndexController extends Zend_Controller_Action
{

public function indexAction()
{
$this->view->title = 'Hello, World!';
}

}

?>

All controllers must extend Zend_Controller_Action, since it contains all later referenced internal methods and properties common to all Controllers. If you need to tweak the basic Controller to add new methods or properties you may do so using a subclass of Zend_Controller_Action and then making all application Controllers extend this subclass instead. Be very careful however in subclassing this way - it's useful for adding new properties to the class, but additional methods are often better added as discrete Action Helpers.

Above we've told the View that templates might refer to a variable "title" and given it a value of "Hello, World!". Once the Action method is done, the ViewRenderer will kick in and try to render a template located at "/path/to/application/views/scripts/index/index.phtml".


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="language" content="en" />
<title><?php echo $this->escape($this->title) ?></title>
</head>
<body>
<p>Hello, World!</p>
</body>
</html>

Did it work?

Go ahead and open up your browser. The base url of http://localhost/zfblog/public/ should now do it's thing and return a HTML document containing "Hello, World!".

Adding Blueprint to the Project

You can download the Blueprint CSS framework from http://code.google.com/p/blueprintcss/. It is released under a dual licensing system using a Modified MIT License, or the GNU General Public License. Decompress the archive of the latest 0.7 release (as at time of writing) and return to our current project's public directory.

Inside public create a new directory called css. Into this new directory, copy the decompressed blueprint directory and LICENSE file from the Blueprint distribution. We retain the license so future redistributions of the project (if any) include a copy for reference.

Blueprint contains a default set of reasonable styling. This include the most fantastic of them all - when included correctly into any HTML document it sets all browsers to the same set of defaults. This completely negates the usual pain of Firefox, IE6, IE7 and Safari having sufficiently different styles internally that designs need a ton of small browser specific adjustments by hand. By hand is evil - Blueprint does it without interference.

To complete this step, create an empty CSS file called style.css inside the /public/css directory. We'll include this in our HTML as a point for customising the Blueprint CSS and adding additional styling as needed.

Creating a Test HTML Document applying Blueprint CSS

open up the /application/views/scripts/index/index.phtml template used by our default Index controller and edit to contain the following.


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="language" content="en" />
<title><?php echo $this->escape($this->title) ?></title>
<link rel="stylesheet" href="css/blueprint/screen.css" type="text/css" media="screen, projection">
<link rel="stylesheet" href="css/style.css" type="text/css" media="screen, projection">
<link rel="stylesheet" href="css/blueprint/print.css" type="text/css" media="print">
<!--[if IE]><link rel="stylesheet" href="css/blueprint/ie.css" type="text/css" media="screen, projection"><![endif]-->
</head>
<body>

<div class="container">

<div class="block">
<div id="zfHeader" class="column span-24">
<h1>Amit Chavda</h1>
</div>
</div>

<div class="block">
<div id="zfContent" class="column span-16">
<p>Welcome To My Blog</p>
</div>

<div id="zfExtraRight" class="column span-7">
<p>Welcome To My Blog</p>
</div>
</div>

<div class="block">
<div id="zfFooter" class="span-24">
<p>Copyright &copy; 2008 Pádraic Brady</p>
</div>
</div>

</div>

</body>
</html>

Splitting persistent markup into a Zend_Layout template

Create a new file in /application/views/layouts called common.phtml. What we'll do is copy in the above index.phtml template but delete the sections that are likely to change with different page views - namely the entries.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="language" content="en" />
<title><?php echo $this->escape($this->title) ?></title>
<link rel="stylesheet" href="css/blueprint/screen.css" type="text/css" media="screen, projection">
<link rel="stylesheet" href="css/style.css" type="text/css" media="screen, projection">
<link rel="stylesheet" href="css/blueprint/print.css" type="text/css" media="print">
<!--[if IE]><link rel="stylesheet" href="css/blueprint/ie.css" type="text/css" media="screen, projection"><![endif]-->
</head>
<body>

<div class="container">

<div class="block">
<div id="zfHeader" class="column span-24">
<h1>Lorem Ipsum</h1>
</div>
</div>

<div class="block">
<div id="zfContent" class="column span-16">

<?php echo $this->layout()->content ?>

</div>

</div>

<div class="block">
<div id="zfFooter" class="span-24">
<p>Copyright &copy; 2008 Pádraic Brady</p>
</div>
</div>

</div>

</body>
</html>
With our Layout template prepared, let's remove all those common items from our Action specific template at /application/views/scripts/index/index.phtml:

<h2>One</h2>
<p><?php echo gmdate('D, j M Y H:i:s T', time()) ?><br />
Posted by Pádraic Brady</p>
<p>Content for One</p>
<hr />
<p>Tags: Lorem, Ipsum, Excepteur</p>

<h2>Two</h2>
<p><?php echo gmdate('D, j M Y H:i:s T', time()) ?><br />
Posted by Pádraic Brady</p>
<p>Content for Tow</p>
<hr />
<p>Tags: Lorem, Ipsum, Excepteur</p>

Setting up Zend_Layout for operation

Getting Zend_Layout working is again quite simple. We just need to make a few additions to our Bootstrap file. Specifically to the setupView() static method.
<?php

require_once 'Zend/Loader.php';

class Bootstrap
{

public static $frontController = null;

public static $root = '';

public static function run()
{
self::setupEnvironment();
self::prepare();
$response = self::$frontController->dispatch();
self::sendResponse($response);
}

public static function setupEnvironment()
{
error_reporting(E_ALL|E_STRICT);
ini_set('display_errors', true);
date_default_timezone_set('Europe/London');
self::$root = dirname(dirname(<u>_FILE_</u>));
// FILE constant should have two underscores
// preceeding and following "FILE"
}

public static function prepare()
{
self::setupFrontController();
self::setupView();
}

public static function setupFrontController()
{
Zend_Loader::registerAutoload();
self::$frontController = Zend_Controller_Front::getInstance();
self::$frontController->throwExceptions(true);
self::$frontController->returnResponse(true);
self::$frontController->setControllerDirectory(
self::$root . '/application/controllers'
);
}

public static function setupView()
{
$view = new Zend_View;
$view->setEncoding('UTF-8');
$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer($view);
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
Zend_Layout::startMvc(
array(
'layoutPath' => self::$root . '/application/views/layouts',
'layout' => 'common'
)
);
}

public static function sendResponse(Zend_Controller_Response_Http $response)
{
$response->setHeader('Content-Type', 'text/html; charset=UTF-8', true);
$response->sendResponse();
}

}?>