Chapter 5. A Not So Simple Hello World Tutorial

5.1. Introduction

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!

5.2. Step 1: Creating A New Local Domain

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.

5.3. Step 2: Creating A Project Directory Structure

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.

5.4. Step 3: Implement Application Bootstrapping

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.

5.5. Step 4: The Only Way In, The Index File

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:

  • <?php
  • require 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] Note

The PEAR Convention is a simple one. It enforces a rule where the name of a class reflects its relative path. So Zend_Controller_Front should be a class contained in a file whose path matches the relative path Zend/Controller/Front.php somewhere on your include_path.

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] Note

If you want to gain a tiny bit of speed, it's often a good idea to enter the contents of the .htaccess file into your Apache configuration for that Directory (we configured this Directory when adding a new Virtual Host earlier). Then you can delete the .htaccess file, and Apache won't have to parse it with every request since it's loaded permenantly when Apache starts. You will however need to restart Apache whenever you want to make changes to the rewrite rules.

5.6. Step 5: Adding A Default Controller and View

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:

  • <?php
  • class 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.

  • <?php
  • 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 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.

5.7. Conclusion

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.

Powered by jQuery Powered by Zend Framework Powered by Nginx Http Server