The Zend Framework Bootstrap made simple (Part 2)

In the first part of the series, you'll remember that we laid the foundation of our custom bootstrap class by creating a custom class directory structure, adding its namespace to the application ini and creating our custom bootstrap file that our application bootstrap will extend from.

After we did that, we put in the first but arguably the most important plugin resource – caching and stored it in the application registry. In this post we're going to be building on that work and adding in three new plugin resources: routing, navigation and databases.

Why these ones?

In my humble opinion, after caching I see these three as arguably the next most important set. Any decent application will have professional routing and user navigation. On top of that, an application will have one or more data sources.

For the sakes of simplicity, we're going to keep it simple here and stick with the default one for PHP web apps – databases. In this post, we're going to be looking specifically at PostgreSQL. Feel free to change it to MySQL, SQLite or Oracle if that's what you prefer, but I have a personal preference for PostgreSQL.


Depending on your experience and needs, you're more than familiar with the standard router in Zend_Controller and you've potentially used more than one of the available route types. Now this post isn't an in-depth coverage of the topic. To find out more, please consult the online documentation.

Like any decent application, you're going to have a number of routes involving a number of route types. These range across static, regex, hostname and rest. Depending on how complex your application is, you could have just a few or you could have quite a number. It's pointless to say just how many I had in one recent app, but the routing table facilitated business pages (about, disclaimer, privacy policy), a user module (forgot and reset password, login, logout), a reporting module and an administration module.

Now every time the application is loaded, the routing table needs to be parsed, validated and built to be able to know if the application can facilitate the request made by the user. I'm sure it's easy to see that over time the routing table could grow quite complex and sizeable. What was once a fairly lightweight structure could quickly become a pretty big bottleneck.

How do we keep it lean?

Well, dependent on your needs, application architecture and skill with the Zend router, this may be more or less of a challenge for you. I'll leave the Zend Router for another day, but regardless of the points just mentioned, there's one clear thing you can do to ease the burden – caching.

Remember the basic mantra of caching: when an object is computationally expensive to generate, minimise the requirement and cache it; where possible. So that's just what we're going to do. We're going to build it as little as possible and grab it from a cache whenever we can.

Now by default, the routing table is configured in application.ini, similar to the sample below:

[bash] ; login route resources.router.routes.login.type = "Zend_Controller_Router_Route" resources.router.routes.login.route = "login" resources.router.routes.login.defaults.module = "user" resources.router.routes.login.defaults.controller = "index" resources.router.routes.login.defaults.action = "login" [/bash]

This is all well and good, but not very cacheable, wouldn't you agree? So what we're going to do is to split the routes out from there, in to a standalone routes.ini. So create a file called routes.ini in the same directory as application.ini.

In it, we're going to &'transfer” 3 routes from an assumed application.ini configuration, which I've listed below: [bash] ; access-denied resources.router.routes.access-denied.type = "Zend_Controller_Router_Route" resources.router.routes.access-denied.route = "accessDenied" resources.router.routes.access-denied.defaults.module = "user" resources.router.routes.access-denied.defaults.controller = "index" resources.router.routes.access-denied.defaults.action = "access-denied"

; logout resources.router.routes.logout.type = "Zend_Controller_Router_Route" resources.router.routes.logout.route = "logout" resources.router.routes.logout.defaults.module = "user" resources.router.routes.logout.defaults.controller = "index" resources.router.routes.logout.defaults.action = "logout"

; login resources.router.routes.login.type = "Zend_Controller_Router_Route" resources.router.routes.login.route = "login" resources.router.routes.login.defaults.module = "user" resources.router.routes.login.defaults.controller = "index" resources.router.routes.login.defaults.action = "login" [/bash]

This is what they would normally look like. Quite simple standard routes that point to actions in the index controller of the user module. Don't worry, this is just for the sakes of a simple example. Please feel free to customise them to your needs.

Now, let's recreate them in the new routes.ini file. Have a look at the versions of the routes below: [bash] [production] ; access-denied routes.access-denied.type = "Zend_Controller_Router_Route" routes.access-denied.route = "accessDenied" routes.access-denied.defaults.module = "user" routes.access-denied.defaults.controller = "index" routes.access-denied.defaults.action = "access-denied"

; logout routes.logout.type = "Zend_Controller_Router_Route" routes.logout.route = "logout" routes.logout.defaults.module = "user" routes.logout.defaults.controller = "index" routes.logout.defaults.action = "logout"

; login routes.login.type = "Zend_Controller_Router_Route" routes.login.route = "login" routes.login.defaults.module = "user" routes.login.defaults.controller = "index" routes.login.defaults.action = "login"

[staging : production]

[testing : production]

[development : production] [/bash]

You can see that they're a bit simpler (or just shorter). That's because they're not being loaded by the standard resource loader, but by our custom resource plugin. But we're not only taking content out of the default application.ini; we're also adding in some – specifically a cache manager configuration to store the compiled routes object.

In your application.ini, add in the following code near where you added the cache configuration in the previous post: [bash] = Core resources.cachemanager.routesConfig.frontend.customFrontendNaming = false resources.cachemanager.routesConfig.frontend.options.lifetime = false resources.cachemanager.routesConfig.frontend.options.automatic_serialization = true = Memcached resources.cachemanager.routesConfig.backend.automatic_serialization = Memcached resources.cachemanager.routesConfig.backend.customBackendNaming = true = "localhost" resources.cachemanager.routesConfig.backend.options.servers.0.port = 11211 resources.cachemanager.routesConfig.backend.options.servers.0.persistent = true resources.cachemanager.routesConfig.backend.options.servers.0.weight = 1 resources.cachemanager.routesConfig.backend.options.servers.0.timeout = 5 resources.cachemanager.routesConfig.backend.options.servers.0.retry_interval = 15 resources.cachemanager.routesConfig.backend.options.servers.0.status = true resources.cachemanager.routesConfig.frontendBackendAutoload = false [/bash]

Now this time I've elected to go with Memcache, as it's quicker than a filesystem cache. But feel free to adjust it as best suits your needs. Now on to the routes plugin resource. Have a look at the code below and add it to your custom bootstrap you created in the previous post.

[php] /** * A bootstrap resource to make possible caching of application routes */ protected function _initRoutes() { $this->bootstrap(&'frontcontroller'); $front = Zend_Controller_Front::getInstance(); $router = $front->getRouter(); $routeIniFile = APPLICATION_PATH . &'/configs/routes.ini'; $routeCache = &'routesConfig';

// get the routes cache manager object $cacheManager = $this->bootstrap(&'cachemanager')->getResource(&'cachemanager');

if ($cacheManager->hasCache($routeCache)) { // attempt to load the routes config from cache $cache = $cacheManager->getCache($routeCache); if (!is_null($cacheManager) && !is_null($cache)) { $routesConfig = $cache->load(self::CACHE_KEY_ROUTE); } }

// unable to get a cached copy - manually loading it if (empty($routesConfig)) { $routesConfig = new Zend_Config_Ini($routeIniFile, APPLICATION_ENV); $cache->save($routesConfig, self::CACHE_KEY_ROUTE); }

$router->addConfig($routesConfig, &'routes');

} [/php]

This resource plugin attempts to load the cache manager object and then the routes cache object. If the routes cache object is available, it then attempts to load the routes object from the cache. If it's available, then it adds the pre-built routes to the application router. If it's not available in the cache, then the object is built, based on the routes.ini file that we created earlier.

You can see that we're using Zend_Config_Ini to make it pretty simple and easy, passing in the APPLICATION_ENV variable to load the configuration based on our current, operating, environment. Quite simple, wouldn't you say? Given this approach, you'll likely not notice an initial performance improvement, but after a load or two you should begin to see your application perform much nicer.

Now, let's move on to the navigation component. I think that Zend_Navigation is one of the gems of Zend Framework. With it, you can, from one configuration file, create: breadcrumbs, user menus, xml sitemap and links. But the real gem of it is being able to link it in with your applications ACL setup.

Ever written logic over and over to attempt to determine if a user should see a page, execute an action, see a navigation menu item or not? If you have, you'll know that it can be both fiddly and annoying, not to mention time-intensive – depending on how you go about it. Well, with a properly setup ACL configuration, this can become a thing of the past for you.

So, what's the navigation resource plugin look like? Have a look at the code below:

[php] /** * Setup the application navigation. * * Covers support for menus, links, breadcrumbs and is translation enabled * @link * @see _buildNavigationObject() */ protected function _initNavigation() { $this->bootstrap(&'cache'); $cache = $this->getResource(&'cache');

// load it from cache if possible. if (!is_null($cache) && ($navigation = $cache->load(self::CACHE_KEY_NAVIGATION)) === false) { $this->bootstrap(&'layout'); $layout = $this->getResource(&'layout'); $view = $layout->getView(); $config = new Zend_Config_Xml(APPLICATION_PATH . &'/configs/navigation.xml', &'nav'); $acl = Zend_Registry::get(&'acl'); $navigation = new Zend_Navigation($config); $view->navigation($navigation); Zend_View_Helper_Navigation_HelperAbstract::setDefaultAcl($acl); Zend_View_Helper_Navigation_HelperAbstract::setDefaultRole( Common_Controller_Plugin_Acl::DEFAULT_ROLE );

$cache->save($navigation, self::CACHE_KEY_NAVIGATION); }

Zend_Registry::set(&'Zend_Navigation', $navigation);

return $navigation;

} [/php]

As with the routes resource plugin, the navigation plugin we attempt to load a cache object to store the compiled navigation object. If it's available and we're able to retrieve a navigation object from it, we simply set it in the registry and also return it.

If it's not available, then, using Zend_Config_Xml, we load the navigation configuration from navigation.xml and create the object from there and then store it in the cache for later use. By setting it in the registry, we're making it available to any other Zend Framework component that uses it and a lot of them look for the key Zend_Navigation to auto load it.

Once again, a very simple resource plugin to create and make available.


Now to the final component of this post – database configuration. There are a lot of operations that database classes, especially Zend_Db, perform. I'm not wanting to weigh in to the pros and cons of using or not using Zend_Db; we can leave that for another day. So let's skip right through to the code.

As with most of the other plugin resources, there are two primary points for configuration: application.ini and the plugin itself.

The core configuration

The core configuration is pretty trivial and quite standard for a normal Zend Framework application. Below, we've configured the adapter to use the PDO PostgreSQL driver connecting to a database on localhost. We've also specified that it is the default table adapter. Please set the username, database name and password to your local configuration.

[bash] ; Database resources.db.adapter = PDO_PGSQL = localhost resources.db.params.username = resources.db.params.password = resources.db.params.dbname = resources.db.isDefaultTableAdapter = true [/bash]

Nothing special here, so on to the resource plugin.

The Plugin

[php] /** * Instantiate the application database resource object * * @return Zend_Db_Adapter * @link */ protected function _initDb() { // Only attempt to cache the metadata if we have a cache available if (!is_null($this->_cache)) { try { Zend_Db_Table_Abstract::setDefaultMetadataCache($this->_cache); } catch(Zend_Db_Table_Exception $e) { print $e->getMessage(); } }

$db = $this->getPluginResource(&'db')->getDbAdapter();

// Set the default fetch mode to object throughout the application $db->setFetchMode(Zend_Db::FETCH_OBJ);

// Force the initial connection to handle error relating to caching etc. try { $db->getConnection(); } catch (Zend_Db_Adapter_Exception $e) { // perhaps a failed login credential, or perhaps the RDBMS is not running } catch (Zend_Exception $e) { // perhaps factory() failed to load the specified Adapter class }

Zend_Db_Table::setDefaultAdapter($db); Zend_Registry::set(&'db', $db);

return $db; } [/php]

As with the other two resource plugins, we've checked to see if a cache is available. If it is, we're using it as the default metadata cache for the database. By default, Zend_Db is a bit too helpful in that it retrieves the table/schema metadata with every request. Well, in a production environment this is not likely to change all that often – so caching this is a good idea.

After that, we retrieve the configuration through the getPluginResource call and also set the default fetch mode. If you don't like returning by object, please change it to best suit your needs. We then attempt to get a connection and catch any exceptions that are thrown – handy if you need to pre-check that a database connection is present.

Finally, we set the object as the default adapter used by Zend_Db in the application, store it in the registry and then return it. Not much to it really. We now will cache the database schema metadata and have provided a database resource automatically to the application.

If you need to use another database or don't even use a database, remove the resource configuration from application.ini and the plugin won't, effectively, run. Simple!

Summing Up

Well, that was a lot to cover in a small space; but now we've covered caching, routes, navigation and database resource plugins and our default bootstrap is starting to look pretty functional I'm sure you'll agree.

In the next and final part in the series, we're going to cover views, pagination and logging. I hope you'll be back for it and get a lot out of it. I also hope that you got a lot out of this post. How would you do it, have you done it, differently? Share your thoughts either in a comment or on twitter.

Till next time.

Matt's Pic About Matthew Setter

Matthew Setter, a passionate Australian, is the founder and chief-editor of Malt Blue. Follow him on Twitter at @maltblue, join in the discussion on Facebook and find out more about him on the team page.

About Matthew

Matthew Setter Matthew Setter is a PHP & Zend Framework specialist. If you're in need of a custom software application, need to migrate an existing legacy application, or want to know your current application's GPA - get in touch.

Want To Be A Zend Framework Guru?

Drop your email in the box below, and get awesome tutorialsjust like this one — straight to your inbox, PLUS exclusive content only available by email.