Today's entry adds a few more layers to our slowly growing blog application. First of all I decided to add an Entries Model and Authors Model to the mix, primarily to get ready for when we can add new entries to our blog. This leads to where we can create new Entries; we add an Administration Module to the application with it's own distinct Layout.
Previously:
Part 4: Setting the Design Stage with Blueprint CSS Framework and Zend_Layout
At the end of Part 5, we'll have Models in place and an ugly looking Administration Panel with exactly one reference to an administrative function. The ugliness is unavoidable for now

. We'll have to start working on
/public/css/style.css to make some custom changes to the default Blueprint CSS styling soon.
I kid you not, I'm installing the finished application on this subdomain at the end of the series so it better look good by then

.
Step 1: Creating a Database Schema
The schema for our blog's database will be aimed at MySQL. We're only starting with two tables to hold entries and authors, which may appear a little suspicious. The suspicion of course is due to the lack of two elements: Authentication and Authorisation. We'll cover both in Part 6 in the near future.
Here's the tables I came up with for now - ensure you have a local database set up, perhaps using phpMyAdmin, so you can just throw in this SQL to create the tables.
CREATE TABLE `entries` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(200) collate utf8_unicode_ci NOT NULL,
`date` timestamp NOT NULL default '0000-00-00 00:00:00',
`author` varchar(60) collate utf8_unicode_ci NOT NULL default 'anonymous',
`author_id` int(11) NOT NULL default '0',
`body` text collate utf8_unicode_ci NOT NULL,
`extended_body` text collate utf8_unicode_ci NOT NULL,
`comment_count` int(4) NOT NULL default '0',
`last_modified` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FULLTEXT KEY `title` (`title`),
FULLTEXT KEY `body` (`body`),
FULLTEXT KEY `extended_body` (`extended_body`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `authors` (
`id` int(11) NOT NULL auto_increment,
`realname` varchar(255) collate utf8_unicode_ci NOT NULL,
`username` varchar(20) collate utf8_unicode_ci NOT NULL,
`password` varchar(64) collate utf8_unicode_ci NOT NULL,
`email` varchar(128) collate utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
We'll likely only use the bare minimum of fields to start with. There are others such as
comment_count in
entries we can't use yet since we don't have a commenting system in place, and likely more just not included above yet. Obviously the input page for entries can be anything from a simple set of form fields, to a three page monstrosity!
If you're curious about why we're using MyISAM for the entries table instead of InnoDB as for authors, the simple reason is that full-text indices are only available on MyISAM tables to enable search functionality using the
MATCH() function in MySQL. We've added full-text indices for the title, body, and extended_body fields.
Step 2: Adding the Models
To interact with our newly created database tables, we're going to need some Models. Back in our previous excursion into explaining Model-View-Controller (MVC) we described that a Model is basically a representation of application state - i.e. data persisted between requests often in a database. The Model for a database in the Zend_Framework is usually captured as a Class inheriting from
Zend_Db_Table.
The Model for our
entries table will be maintained in an
Entries class in
/application/models/Entries.php:
<?php
class Entries extends Zend_Db_Table
{
protected $_name = 'entries';
}
Similarly we will have
/application/models/Authors.php:
<?php
class Authors extends Zend_Db_Table
{
protected $_name = 'authors';
}
It really is that simple for now.
Zend_Db_Table even assumes the primary key is "id" unless otherwise defined. If you really want to know what the fuss about Models is, it's all in the methods you add to this class. For now, it's a blank slate referencing the database's table name. But you can imagine a few business logic rules you could consider adding here (hint: processing of entry bodies before saving using HTMLPurifier). The main idea to keep in mind is that if you're doing something to data from a Model, and it's potentially useable in more than one Controller, then chances are you're better off adding it as a Model method. Why make your Controllers fat and bloated? The more bloat they contain, the harder it becomes to see the application's workflow by reading them!
Step 3: Adding Database Credentials and a Default Database Adapter for Zend_Db
We have our database schema in place along with Models to represent it. Time to do something so our Model can actually interact with the database then

.
The first step is storing our database credentials in an editable file. Create a new file called
config.ini in
/config containing something along the lines of the following (edit for your personal credentials and database name):
[general]
;Database connection settings
db.adapter=PDO_MYSQL
db.host=localhost
db.username=root
db.password=passwd
db.dbname=zfblog
Note: The Subversion repository contains a template of the above file called
config.ini.example. This way I can change my own
config.ini file (which is on the subversion ignore list for its parent directory) without committing its changes to the repository all the time! You will need to manually create a copy if running from Subversion.
By now you probably know the drill, and you're wondering just how far I mangled
/application/Bootstrap.php to integrate database setup...
Not too much really. In the below revised Boostrap file I've shuffled a few things around, added new methods for setting up a Registry, Config file and Database connection. Hopefully it's self explanatory by now... To answer a question raised in the comments previously, I'm using a static class simply because the Bootstrap isn't really responsible for a whole lot other than initialisation and execution - most tutorials fall so far back in time they even use procedural style PHP!

Static methods are also attractive because I do rely on not needing a discrete instance for other reasons - which we're not covering here since I'm not obsessing about Behaviour-Driven Development or TDD in this series.
<?phprequire_once 'Zend/Loader.php';
class Bootstrap
{ public
static $frontController =
null;
public
static $root =
'';
public
static $registry =
null;
public
static function run
() { 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>
));
} public
static function prepare
() { self::
setupEnvironment();
Zend_Loader::
registerAutoload();
self::
setupRegistry();
self::
setupConfiguration();
self::
setupFrontController();
self::
setupView();
self::
setupDatabase();
} public
static function setupFrontController
() { self::
$frontController = Zend_Controller_Front::
getInstance();
self::
$frontController->
throwExceptions(true);
self::
$frontController->
returnResponse(true);
self::
$frontController->
setControllerDirectory( self::
$root .
'/application/controllers' );
self::
$frontController->
setParam('registry', self::
$registry);
} 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();
} public
static function setupRegistry
() { self::
$registry =
new Zend_Registry
(array(), ArrayObject::
ARRAY_AS_PROPS);
Zend_Registry::
setInstance(self::
$registry);
} public
static function setupConfiguration
() { $config =
new Zend_Config_Ini
( self::
$root .
'/config/config.ini',
'general' );
self::
$registry->
configuration =
$config;
} public
static function setupDatabase
() { $config = self::
$registry->
configuration;
$db = Zend_Db::
factory($config->
db->
adapter,
$config->
db->
toArray());
$db->
query("SET NAMES 'utf8'");
self::
$registry->
database =
$db;
Zend_Db_Table::
setDefaultAdapter($db);
} }
The
setupDatabase() method takes the new Zend_Config instance storing our configuration data from config.ini (and which we're now keeping in the Registry for reference) and sets up a connection by passing the configuration data to Zend_Db's
factory() static method. The first act is to use the new connection to ensure we're once again being careful to stick with UTF-8 encoding. Have to be careful with my name afterall - it has one of those weird European slash thingies over the a (we call it the "fada" in Irish Gaelic, the French call it an "acute", and HTML standards refer to it as "á" - outnumbered two to one

).
The connection object is stored to the Registry in case it's required later. It's usually needed as a last resort if we need it for something a Model isn't well suited for. The Registry is passed to the Front Controller in
setupFrontController() as a user parameter. Finally, but not least, we make this new connection the default for Zend_Db_Table - available to all our Models.
It does look like a complicated web of steps, but honestly the code is pretty small for all this.
Step 4: Adding an Administration Module
We have the database, the Model, and the database connection.
Before we jump further, I'm going to make an assumption. Entry submissions will only be allowed from an Administration Module. Once we do have Authorisation implemented we'll seal off access to any such Administration functions, but for now since the blog remains on our private development PC we can leave it openly accessible - until Part 6

.
The Zend Framework allows for all Controllers and Views to be grouped into Modules relatively easily. Up to now we've been putting everything into their default locations without any thought of Modules, so it's high time we added one for Administration.
The first step is to introduce a new directory to our current directory structure called
admin inside
/application. You can also delete the previously suggested
modules directory - we'll keep the directory tree a little flatter than I usually go with, although I can change this if readers prefer. Inside
admin, add both a
controllers and
views directory. The
views directory should in turn have
filters,
helpers,
layouts and
scripts directories.
To allow our application divert requests to the new
admin module, we also need to register its
controllers directory with the Front Controller. This is a quick edit to our Bootstrap file at
/application/Boostrap.php in the
setupFrontController() method:
<?phprequire_once 'Zend/Loader.php';
class Bootstrap
{ public
static $frontController =
null;
public
static $root =
'';
public
static $registry =
null;
public
static function run
() { 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>
));
} public
static function prepare
() { self::
setupEnvironment();
Zend_Loader::
registerAutoload();
self::
setupRegistry();
self::
setupConfiguration();
self::
setupFrontController();
self::
setupView();
self::
setupDatabase();
} public
static function setupFrontController
() { self::
$frontController = Zend_Controller_Front::
getInstance();
self::
$frontController->
throwExceptions(true);
self::
$frontController->
returnResponse(true);
self::
$frontController->
setControllerDirectory( array( 'default' => self::
$root .
'/application/controllers',
'admin' => self::
$root .
'/application/admin/controllers' ) );
self::
$frontController->
setParam('registry', self::
$registry);
} 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();
} public
static function setupRegistry
() { self::
$registry =
new Zend_Registry
(array(), ArrayObject::
ARRAY_AS_PROPS);
Zend_Registry::
setInstance(self::
$registry);
} public
static function setupConfiguration
() { $config =
new Zend_Config_Ini
( self::
$root .
'/config/config.ini',
'general' );
self::
$registry->
configuration =
$config;
} public
static function setupDatabase
() { $config = self::
$registry->
configuration;
$db = Zend_Db::
factory($config->
db->
adapter,
$config->
db->
toArray());
$db->
query("SET NAMES 'utf8'");
self::
$registry->
database =
$db;
Zend_Db_Table::
setDefaultAdapter($db);
} }
Nothing huge has changed. The only odd part perhaps, is that our previous controller directory is now assigned as a "default". This is a special Module key and you should never omit it when setting up Modules since it is the ultimate fallback position for failed requests (after that it's to the ErrorController we'll add in the next Part of this tutorial series).
Our Administration module is going to have two controllers. An Index Controller which will default to displaying a list of available actions (e.g. create new blog entry), and an Entry Controller for adding new Entries (and later editing and deleting them). We'll create a new Index Controller at
/application/admin/controllers/IndexController.php containing the following:
<?php
class Admin_IndexController extends Zend_Controller_Action
{
public function indexAction()
{
}
}
You'll notice that all Controllers not part of the default Module must have their class name prefixed with the Module name followed by an underscore. This merely ensures we have no annoying name conflicts between Modules over common Controller names. The
indexAction method is completely empty - on purpose. The controller has nothing to do here except look pretty for a few microseconds until control passes to the ViewRenderer Action Helper to have the correct View rendered.
The most obvious next step, therefore, is adding a template for our new module's index page. Create
index.phtml at
/application/admin/views/scripts/index/index.phtml and we'll just add, for now, a simple listing of available functions. All one of them.
The first one we need of course, is a URL for adding a new Blog entry. We'll assume we're going to use an Entry Controller within the Admin Module:
<h2>Administration</h2>
<ul>
<li><a href="/admin/entry/add">New Entry</a></li>
</ul>
Note: Please do not forget the leading forward slash in relative URLs! That little character ensures all relative URLs remain relative to the base URL and not just get appended to the current URL (which is all prettied up).
Try opening up our budding Administration Panel using the URL
http://zfblog/admin.
There's one small niggle here, which demonstrates another interesting facet of using Zend_Layout. The index page for Administration has that text filled right column. We don't need that - it's destined to hold Blog related stuff for public consumption. Let's make our Admin module utilise a slightly different layout! We'll add a narrower left column this time for future Administration functions.
Step 5: Adding an Administration Module specific Layout
So, add a new template
admin.phtml to
/application/admin/views/layouts/admin.phtml. This will be an exact duplicate of
/application/views/layouts/common.phtml with a few column changes.
<!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=
"zfExtraLeft" class=
"column span-5">
<!-- We
'll add the style to style.css later -->
<div class="zfMenuLeft" style="margin-top: 3em;">
Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet
</div>
</div>
<div id="zfContent" class="column span-18">
<?php echo $this->layout()->content ?>
</div>
</div>
<div class="block">
<div id="zfFooter" class="span-24">
<p>Copyright © 2008 Pádraic Brady</p>
</div>
</div>
</div>
</body>
</html>
In order to get the Admin Module using this layout, we need to intercept the Zend_Layout object just before an application request is dispatched to any Controller. We can then alter the default configuration we have used in our Bootstrap file. Welcome to the world of the Front Controller Plugin!
A front controller plugin already exists called
Zend_Layout_Controller_Plugin_Layout which contains a postDispatch() method which is where the Layout gets finally rendered. To switch Layouts, we can simply create a new class extending the existing plugin, and add a preDispatch() method to detect when the Admin Module has been requested and replace the Zend_Layout layout name and the path to the Module's
layouts directory.
Front Controllers are very useful in this fashion for performing actions which have a dependency on the Module, Controller or Actions being requested. In Part 6 of this series, we'll use another Front Controller Plugin to implement an Access Control List system for Authorisation using Zend_Acl which uses Module/Controller/Action names to check if the current user is authorised to access them.
To start, create a new directory tree within
/library called
ZFBlog. It will use the standard PEAR Convention directory structure for a class called
ZFBlog_Layout_Controller_Plugin_Layout:
/library
/ZFBlog
/Layout
/Controller
/Plugin
/Layout.php
I suggest keeping an eye on ZFBlog. As you start to develop Zend Framework applications you will likely end up with two distinct types of additional classes. Zend Framework specific extensions and subclasses, and application specific classes. A lot of the time, such quirky additions can be reused in other applications. I've used this Layout plugin more than once

.
The contents of the Layout.php file could be as simple as:
<?phpclass ZFBlog_Layout_Controller_Plugin_Layout extends Zend_Layout_Controller_Plugin_Layout
{ public
function preDispatch
(Zend_Controller_Request_Abstract
$request) { switch ($request->
getModuleName()) { case 'admin':
$this->_moduleChange
('admin');
} } protected
function _moduleChange
($moduleName) { $this->
getLayout()->
setLayoutPath( dirname(dirname( $this->
getLayout()->
getLayoutPath() )) . DIRECTORY_SEPARATOR .
$moduleName .
'/views/layouts' );
$this->
getLayout()->
setLayout($moduleName);
} }
The above plugin is really simple. Before a request is dispatched we get the Module name from the Request object and check it against our switch statement. If a match is found, we can reset the Zend_Layout layout name and path for that request, or if nothing matches we leave things as they stand.
If you are particularly astute at refactoring, you may feel explicitly mentioning "admin" in the class is a bad move - I'll leave it to readers to search for more flexible solutions handling multiple modules with different layouts.
Now, we have a new layout template and we have a plugin to utilise it when the Admin module is requested. Let's make sure this new class replaces the
Zend_Layout_Controller_Plugin_Layout class referenced by default in Zend_Layout. Guess where that gets done?
The Bootstrap!
Continue reading "An Example Zend Framework Blog Application - Part 5: Creating Models with Zend_Db and adding an Administration Module"