Table of Contents
It's become traditional for programming books to offer the simplest possible example of whatever programming language, framework or library they are covering. Usually, this means doing just enough to print "Hello World" to the screen so you can see how the basic functionality works. This is not really the most realistic example when applied to frameworks but it's as good a starting point as any!
Before we hit the source code, it's generally recommended to develop using an environment a bit closer to what is typical of our production environment. Throughout the book I will develop applications within the document root of a web server rather than a subdirectory. While you can use a subdirectory, it may require a bit more work when setting up URI references across the application, something I will cover in more detail later.
For now, take a look at
Appendix
A: Creating A Local Domain Using Apache Virtual Hosts which details
a simple process for enabling a local domain with its own separate
document root for this example. Using this domain and an Apache Virtual
Host, we can serve the example from the http://helloworld.tld domain on
our development system.
The directory structure
of our example project is next on the list. First, create a new directory
matching the path (excluding the trailing /public
subdirectory part) you previously entered as the Document Root for the
helloworld virtual host. This will be where all our project files will be
saved. So for Ubuntu I could create the directory at
/home/padraic/www/helloworld or under Windows,
C:\projects\helloworld.
There is always a lot of
debate about how to structure the directory layout but a lot of that is
being formalised by the ongoing Zend_Tool effort
led by Ralph Schindler which will result in a stable full command line
tool for creating and manipulating projects. Until it's fully stable and
documented we won't use it here, so that means some manual grunt work in
creating the directory layout by hand.

Application Directory Layout
Here's my suggested directory layout. This is by no means compulsory and you may vary this according to your preferences on other applications. For the purposes of our example, we only need to use a subset of these to start with.
As you can see, all
Controller and View source code will be stored within the
/application directory. Controllers and Views all
have framework specific loading conventions so it makes sense to follow
this convention. Here we also store any Modules which are basically
discrete collections of Controllers and Views (and optionally Models and
application specific non-reusable helpers). You may be used to seeing an
/application/models directory here from the
documentation. I do not use this at present since Models are so incredibly
diverse in how they are implemented that there exists no one single Model
loading convention. Therefore, I maintain Models as part of my generic
application library, /library, along with any other
classes specific to this application. It is still possible to store Models
in /application/models. You may prefer to maintain
Models within a more specifically named location but remember that every
new location storing source code may become another entry on your PHP
include_path. Later in the book we'll work with Models that
may be stored as traditionally noted in the Reference Guide, since the
introduction of Zend_Loader_Autoload allows us to
be more flexible in this regard.
Everything else exists
at the parent directory level, and all future application source code
outside of Controllers, Views and non-reusable classes will exist within
either the /library or /vendor
directory. The library directory is where I store any general classes used
within this application. The vendor directory is generally for
non-specific classes or third-party libraries I wish to use. The
differences between the two are completely arbitrary - I just like keep my
own classes and third party libs separate. I also keep the Zend Framework
itself here unless it's already installed on the server in a more central
location and accessible from the include_path.
The remaining
directories are no mystery. The config directory holds any configuration
files. Following emerging standards, this may also be located at
/application/configs. The data directory allows for
the storage of data as files. These could be caches or other information.
The public directory is where all files accessible by a visitor to our
website will reside. The scripts directory holds any general use scripts
such as tasks to run using cron.
Bootstrapping is when we
setup the initial environment, configuration and Zend Framework
initialisation of our application. It's not a difficult file to write but
it can grow ever larger as your application grows in complexity. To manage
this I strongly suggest implementing it as a class with bitesize methods.
Breaking it up does wonders for your sanity. Later in the book we'll
introduce the Zend Framework's solution to this complexity,
Zend_Application, which was introduced with Zend
Framework 1.8.
Since the bootstrap is a
class, the obvious location to maintain it is in
/library. Sure, you can put it in
/application, but that's another include path (unless
you are using Zend_Application which uses the
absolute path to the class) and if we keep sticking classes all over the
place we'll end up with the include_path from Hell! So far, and assuming
you are not using Zend_Loader_Autoloader, managing
Models in /library along with our bootstrap class
removes two entries on the include_path. We'll store the new bootstrap as
an application specific file under the ZFExt namespace, meaning it will be
stored as /library/ZFExt/Bootstrap.php.
<?php/*** When storing the ZF within /vendor, use an absolute path.*/require_once 'Zend/Loader.php';class ZFExt_Bootstrap{public static $root = '';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_startup_errors', true);ini_set('display_errors', true);date_default_timezone_set('Europe/London');self::$root = realpath('..');define('APP_ROOT', self::$root);spl_autoload_register(array(__CLASS__, 'autoload'));}public static function autoload($class) {include str_replace('_', '/', $class) . '.php';return $class;}public static function prepare(){self::setupFrontController();self::setupView();}public static function setupFrontController(){self::$frontController = Zend_Controller_Front::getInstance();self::$frontController->throwExceptions(true);self::$frontController->returnResponse(true);self::$frontController->setControllerDirectory(realpath(self::$root . '/application/controllers'));$response = new Zend_Controller_Response_Http;$response->setHeader('Content-Type','text/html; charset=UTF-8', true);self::$frontController->setResponse($response);}public static function setupView(){$view = new Zend_View;$view->setEncoding('UTF-8');$view->doctype('XHTML1_STRICT');$view->headMeta()->appendHttpEquiv('Content-Type','text/html;charset=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->sendResponse();}}
Well, you may now realise why this chapter is the Not So Simple Tutorial!
Presumably you're in the
business of learning right now however, so the bootstrap above is far more
complex than the minimal example you'll find in the Reference Guide. What
I have done above is two-fold. I've structured the bootstrap as a class
with discrete methods for different bootstrap tasks and stages. Secondly,
I've setup default assumptions for any application output by modifying
settings on Zend_View (which generates application
output using templates) and
Zend_Controller_Response_Http (which handles
headers and the mechanics of actually sending responses back to a
client).
The initial setup stages
are fairly simple to see. Our environment setup which in the future should
be configuration driven, sets up PHP for the application with some
configuration values, error reporting level, timezone information (not
needed if defined in php.ini), and an autoloader function so we skip using
require_once in our source code. The custom
autoloader is in contrast to the more common reliance on
Zend_Loader, but it's a lot leaner than
Zend_Loader's implementation which performs a lot
of, usually, unnecessary tasks (see Appendix
B). The current environment setup is geared towards development so
errors will be displayed.
The next step, within
the prepare() method, is setting up the Front
Controller. As discussed, in Model-View-Controller there is usually a
single entry point into the application, a point through which all
requests must pass. For the Zend Framework, this is the Front Controller
defined by the Zend_Controller_Front class. Zend's
Front Controller class is a singleton (its most annoying "feature" -
singletons should be avoided when not necessary). In the
setupFrontController() method we set some flags
to ensure it returns a Response object instead of simply echoing it beyond
our control, and that it allows Exceptions to be visibly thrown instead of
searching for an ErrorController which at this
point does not exist. The next important step is telling the Front
Controller where to find our application's collection of Controllers and
Views.
The final step where we
create a discrete instance of the Request object, i.e.
Zend_Controller_Http_Response, exists to set a
default Content-Type header for application responses. This ensures that,
unless otherwise specified, all output will automatically send clients a
Content-Type header for text/html with a character set of UTF-8. This is
simply good practice to avoid any possible confusion where we don't set
this explicitly in the application. If this is not set, the default
Content-Type set in php.ini may be used instead.
Once the environment and
Front Controller are configured, the final task is adding similar defaults
to our Views. At present, Zend_View, in stark
contrast to other components, uses a default character set of ISO-8859-1
(as do View Helpers) which simply won't do if you are going to output
multibyte characters (like the one in my name!). This is something of an
annoyance and a long standing bug preserved to avoid backwards
compatibility problems with older Zend Framework versions. To change the
default, we should instantiate a new instance of
Zend_View, set a more appropriate default character
encoding of UTF-8, and implant this altered View object into the
ViewRenderer Action Helper. This helper is used automatically by all
Controllers to contain and manage Views, so by explicitly setting a View
object to use we avoid having an unmodified View instantiated instead.
Under the presumption we will output HTML by default, it's also strongly
recommended to set a default HTML DOCTYPE since this setting feeds into
other components (Zend_Form, for example) when they are being rendered.
Again, this is an annoyance if you forget it since you can have forms
being generated under a different doctype, for example. Obviously, we'd
prefer that anything rendered to HTML follows the application View's
preferred DOCTYPE.
As you can see, writing
a Bootstrap is a bit like waging war. It never survives first contact, and
you will continually find yourself adding paths, configurations and other
tweaks over time. The above presents one possible baseline but in the next
chapter we'll meet Zend_Application which allows
for a more standard approach.
We have a Bootstrap in
place but it's only useful if we execute it somewhere. As we covered
previously, all Model-View-Controller (MVC) applications have a single
point of entry into an application. In the case of PHP, this is invariably
a file called index.php, the Index file.
Since it's loaded
automatically by nearly every web server supporting PHP, if the path to no
other file is given, it's the place where our application is first loaded.
It's also the place where we may make any manual PHP include_path changes
needed for the local server environment (other than those paths our
bootstrap can autoload by design). I don't intend breaking the PEAR
Convention however without good cause, so the only thing we must do in
index.php is run the Bootstrap!
Create index.php in your
project's /public directory containing:
<?phprequire realpath('..') . '/library/ZFExt/Bootstrap.php';ZFExt_Bootstrap::run();
If you are using libraries with non-conventional include paths, you would need to add those include paths here or configure them from the bootstrap class. For the moment though, everything we are using can be autoloaded by the bootstrap.
|
Note |
|---|---|
|
The PEAR Convention is
a simple one. It enforces a rule where the name of a class reflects its
relative path. So |
We have one small
problem with our theoretical single entry point - it defies how HTTP works
in practice. How can we communicate the details of where in the
application we are trying to reach, if the only file available is
index.php? We need to be able to use a URL which, by
default, follows the form
http://domain.tld/controllername/actionname so the
application's Router can call the correct action method on the correct
Controller class. On any web server this is doomed to failure, because
they will believe we are trying the reach an actual filepath matching the
URL's path element, i.e. the directory
controllername/actionname/. To get around this, we
need to pass the URL path to index.php, and ensure
the web server does understand we are indeed always requesting through
index.php.
Luckily web servers offer a feature called URL Rewriting which
allows us to transform all URLs (matching certain criteria) into a new URL
which is mapped to index.php to create a final URL
more along the lines of
http://domain.tld/index.php/controllername/actionname.
Obviously this criteria must exclude all resource URLs we don't want
passed to the application as a mere parameter like our stylesheets,
javascript and images. These must always be served directly.
Assuming you're using Apache as your web server, you may need to turn on URL Rewriting by enabling the rewrite module. This module is disabled by default on many newly installed Apache instances. This involves editing Apache's configuration file, or on Ubuntu simply by using the a2enmod command (which is pretty handy).
If you are using Ubuntu try this:
sudo a2enmod rewrite
The command simply plays with a symlink and adds the rewrite configuration file so it's autoloaded as part of Apache's overall configuration. You need to reload Apache for any module changes to take effect which is performed on Ubuntu using:
sudo /etc/init.d/apache2 reload
If you need to manually edit the configuration file, you normally just need to add something like the following line (reload Apache afterwards):
LoadModule rewrite_module modules/mod_rewrite.so
For those not using
Apache, please consult the Reference Guide section on
Zend_Controller to find instructions specific to
your setup.
To make use of URL
Rewriting, add the following file called .htaccess to
/public:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ /index.php [NC,L]
If you intend hosting your application within a subdirectory, you
should also add a RewriteBase line to ensure the rules
exclude this portion of the URL, and only take the path element after that
point when rewriting it onto index.php.
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteBase /subdirectory/
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ /index.php [NC,L]
The
.htaccess file sets up some rewrite rules which
ensure all requests are mapped onto our application's index file when
appropriate. It's designed to map all URLs except those dictated by the
preceding collection of rewrite conditions - namely if the URL refers to a
file (with size greater than zero), directory or symbolic link. This makes
sure your javascript, css, and other non-PHP files can be served directly
even if centralised elsewhere and referenced only by symbolic
links.
|
Note |
|---|---|
|
If you want to gain a
tiny bit of speed, it's often a good idea to enter the contents of the
|
When we were discussing Model-View-Controller (MVC), we noted that the Controller was the part responsible for the wiring of applications allowing user input to be mapped to the Model and Models mapped to the View as needed. The Controller is therefore one of the first components you'll deal with for every new feature you add to an application.
In the Zend Framework,
all Controllers are ultimately subclasses of
Zend_Controller_Action. This base class enforces a
few default conventions which you should keep in mind. For example, it was
decided to make automated rendering of Views the default behaviour of all
Controllers. This means you don't need to worry about manually ordering
View templates to render - instead an Action Helper called the View
Renderer
(Zend_Controller_Action_Helper_ViewRenderer) is
called upon. In our previous bootstrap class we adjusted the default
behaviour further by ensuring the View Renderer utilises a
Zend_View instance which has a default character
encoding of UTF-8 (rather than ISO-8859-1). There are ways to disable
automated View rendering as documented in the Reference Guide which is
often useful when you need to bypass Zend_View (e.g. to output final JSON
or XML documents which aren't template based and don't need further
processing).
Determining which
Controller to use is the job of the Router
(Zend_Controller_Router_Route and related classes)
which uses the value of the URL string which contains the Controller
classname and method to be used, or which maps to a configured Route which
supplies the Controller and Action names to default to. In the absence of
any such information, the Router will assume a default "index" value for
each. Since we only intend requesting to the root URL of http://helloworld.tld, we'll need
an Index Controller containing an Index Action, i.e. a Controller which
can service the Router's defaults which assumes the URL is actually
identical to http://helloworld.tld/index/index
Here's the source code for
/application/controllers/IndexController.php:
<?phpclass IndexController extends Zend_Controller_Action{public function init(){}public function indexAction(){}}
The
init() method can be ignored since it's only
needed for setup tasks specific to this Controller, and we have none of
those. The more relevant indexAction() method has
no content, which is fine, since we only need content if interacting with
a Model or some other collection of classes. Right now, there is none of
that. All that this does really is act as a stub method which tells the
ViewRenderer Action Helper to render a View with a similar name.
To explain the mapping,
an Index Controller with an indexAction() method
maps to a View template located at
/application/views/scripts/index/index.phtml. So
ErrorController::indexAction() would map to
/application/views/scripts/error/index.phtml and so
forth. It's a simple convention. Let's create a template for
IndexController::indexAction() located at
/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>
In Zend Framework,
templates work by virtue of the fact they are included into
Zend_View's object scope. So all properties and
methods accessible by Zend_View, are accessible
from the templates using $this (to reference the current
Zend_View object whose variable scope
applies)
This is a perfectly
valid template that works but something we will meet in greater detail
later is the concept of View Helpers. In brief, a View Helper encapulates
commonly used presentation related tasks in helper classes. For example,
are we going to add the Doctype string to dozens of templates? If we do
that, and it changes, we'll have dozens of templates to edit. No, we're
not. Zend_View has a helper which we used in the
bootstrap to set a default Doctype. The same way that we used another
helper in the bootstrap to append a http-equiv header with a default
Content-Type to the head section's meta information. Using these two
helpers, which can be echoed out directly since they indirectly implement
PHP's magic __string() method, we can reduce the
template to:
<?php echo $this->doctype() ?><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head><?php echo $this->headMeta() ?><meta name="language" content="en" /><title><?php echo $this->escape($this->title) ?></title></head><body><p>Hello, World!</p></body></html>
Even now, we still have "en" in three different places that we could also automate with a View Helper to ensure they are updated depending on our content's language. This demonstrates why View Helpers are useful - we can offload tasks concerning changes to the View to them so they are reusable many times across different Views. It's not important to understand View Helpers in full just yet, you really only need to know that they exist!
Now also edit
IndexController.php to pass the title of the page
(referenced in the view script as $this->title) to the
View.
<?phpclass 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 by 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 to prevent the development of a parent Controller class
that is little more than a hard to maintain heap of loosely related
spaghetti strands.
Our new Action method ignores rendering; it's done automatically as discussed earlier using the View Renderer that's enabled by default. All we need to do is set the View data we need. You can add almost anything to a View in this manner, since it just becomes a property in a View template. So feel free to add Objects and Arrays. If using Smarty or PHPTAL, this might be different. This flows back to the concept of the View using Models discussed earlier - a Model is an object, usually, so there's no reason to restrict Views to mere arrays when it's more convenient to simply pass in an object.
Above we've told the
View that templates might refer to a local public class property "title"
and given it a value of "Hello, World!". Once the Action method is done,
the View Renderer will kick in and try to render a template located at
/application/views/scripts/index/index.phtml.
Inside the view script
we also escape the title on the basis that it could be unsafe. It's pretty
much obligatory to escape everything in this manner if being output as
HTML. Internally, Zend_View uses
htmlspecialchars() by default and passes it the
current instance's character encoding setting. It's also one of those
little annoyances - in Zend Framework 2.0 this escaping will be done
automatically without the need to continually call the
escape() method.
In this chapter we've covered a relatively simple (aha!) example of how the Zend Framework works when building an application. It also emphasises the importance of the bootstrapping since this is where nearly all our setup happens, including the setting of defaults and any configuration work needed for Zend Framework components is centered. Throughout this book, managing custom plugins, action helpers and other similar non-standard Zend Framework classes occurs in the Bootstrap since this is the most logical place to drive their configuration and inclusion in an application.
In the next chapter
we'll cover one obvious omission to anyone keeping up to the date with
recent Zend Framework versions: how to manage this bootstrap complexity
using Zend_Application, before moving on to
explaining how we can handle application errors. Shortly after, we'll
start delving into an actual application since I desperately need a
replacement for my blog and it so happens to be a simple application where
we can explore other Zend Framework components in greater depth.


