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.


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.

Routing

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:

; 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"

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:

; 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"

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:

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

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:

resources.cachemanager.routesConfig.frontend.name = Core
resources.cachemanager.routesConfig.frontend.customFrontendNaming = false
resources.cachemanager.routesConfig.frontend.options.lifetime = false
resources.cachemanager.routesConfig.frontend.options.automatic_serialization = true
resources.cachemanager.routesConfig.backend.name = Memcached
resources.cachemanager.routesConfig.backend.automatic_serialization = Memcached
resources.cachemanager.routesConfig.backend.customBackendNaming = true
resources.cachemanager.routesConfig.backend.options.servers.0.host = "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

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.

/**
* 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');

}

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:

/**
* Setup the application navigation.
*
* Covers support for menus, links, breadcrumbs and is translation enabled
* @link http://framework.zend.com/manual/en/zend.navigation.html
* @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;

}

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.

Databases

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.

; Database
resources.db.adapter = PDO_PGSQL
resources.db.params.host = localhost
resources.db.params.username = <your username>
resources.db.params.password = <your password>
resources.db.params.dbname = <your database>
resources.db.isDefaultTableAdapter = true

Nothing special here, so on to the resource plugin.

The Plugin

/**
* Instantiate the application database resource object
*
* @return Zend\_Db\_Adapter
* @link http://framework.zend.com/manual/en/zend.db.html
*/
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;
}

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.


You might also be interested in these tutorials too...

Wed, Nov 9, 2011

The Zend Framework Bootstrap made simple (Part 3)

Ok, we’ve established that with the Zend Framework, we need to do a bit more work than some of the other frameworks to get up to speed - but that’s not necessarily a bad thing - right?! But it can be a bit tedious and it’s something as professional developers, we want to automate away. So we’ve been addressing in this series how to do just that with a custom, extendable bootstrap class.

In the first part of the series we laid the foundation of our custom bootstrap class by creating a custom class directory structure, adding its namespace to the application ini and modifying the default application bootstrap so that it extends from it and had a look at the first component - caching.

Then, in the second part of the series, we built on the foundation laid in part one by creating plugin resources for the routing table, application navigation and the database connections - these being some of the most common tasks, usually, associated with a web-based application.

In this, the third and final part of the series, I’m going to finish up by looking at application placeholders and surprise, no not logging as originally promised, but pagination. As an added extra, we’re going to be using a key feature of Zend Application to make it a breeze.

Thu, Oct 27, 2011

The Zend Framework Bootstrap Made Simple (Part 1)

When you’re creating a new project with the Zend Framework, unlike other frameworks, you need to do more legwork. This isn’t necessarily a bad thing, but it can sure slow you down when you’re trying to plough through a project.

I really enjoy using it, as it has a very well structured approach – and I like structure – it clearly lays out a file-system structure for modules, controllers, actions, forms, models and so on. It has good, but basic, tooling, allowing for modest project initialisation. But despite all this, it still requires a healthy investment on our part to get a proper foundation in place to use it productively.

In a recent project I encountered this situation and felt that I mustn’t be the only one to do so. As I plan to keep using Zend Framework I want to work around this situation and get as much productivity out of it as possible right from the get go. But how to do this?

Bootstrapping

Well the primary focus for me is bootstrapping. It provides the majority of the core services that every project needs, from routing, data source connections, authentication, authorisation, navigation, caching and so on. So it stands to reason that it’s a good place to start. So I want to cover what should go in to a good working bootstrap.

Tue, Nov 15, 2011

Rename uploaded files with Zend Framework

Recently I was asked how to rename a file with the Zend Framework that used a Zend Form and Zend File element. They key requirement was that it should not be a hack or a kludged solution. So I thought I’d write a quick post to provide a simple example on how it was achieved.


Want more tutorials like this?

If so, enter your email address in the field below and click subscribe.

You can unsubscribe at any time by clicking the link in the footer of the emails you'll receive. Here's my privacy policy, if you'd like to know more. I use Mailchimp to send emails. You can learn more about their privacy practices here.

Join the discussion

comments powered by Disqus