Skip to content

Migrating applications to 1.8

May 21, 2009

Old applications should still work in 1.8 but some might want to update their applications to use the new 1.8 features such as Zend_Application and Zend_Loader_Autoloader. The reference manual describes how to write a bootstrap for Zend_Application, so head that way for a detailed description of the inner workings of Zend_Application. In this post, I’ll show how one might go about converting their old bootstraps into something usable by Zend_Application.

First step

I’m going to use a bootstrap from the influential tutorial by Pádraic Brady. This is nicked from the fifth part of his ZFBlog tutorial.

<?php

require_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);
    }

}

ZF bootstraps can take any form, but they are essentially all the same. They initialize various components used by the application in a specific sequence. The above bootstrap is enclosed in a static class, but it could easily just have been a sequence of PHP calls in the global namespace. Grouping them into static methods just makes it easier for the developer to see which parts are related so that they can be easily found later if some modifications are needed.

An application which uses the bootstrap above is run by including the bootstrap class and calling Bootstrap::run(). The meat of the bootstrap is the prepare() method, which calls a bunch of methods which initialize specific components.

The first step in converting this into a Zend_Application-compatible bootstrap is to convert all the static initialization methods into protected instance methods:

<?php

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    public static $frontController = null;
    public static $root = '';
    public static $registry = null;

    protected function _initEnvironment()
    {
        error_reporting(E_ALL|E_STRICT);
        ini_set('display_errors', true);
        date_default_timezone_set('Europe/London');
        self::$root = dirname(dirname(__FILE__));
    }

    protected function _initRegistry()
    {
        self::$registry = new Zend_Registry(array(), ArrayObject::ARRAY_AS_PROPS);
        Zend_Registry::setInstance(self::$registry);
    }

    protected function _initConfiguration()
    {
        $config = new Zend_Config_Ini(
            self::$root . '/config/config.ini',
            'general'
        );
        self::$registry->configuration = $config;
    }

    protected function _initFrontController()
    {
        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);

        return self::$frontController; // this is *VERY* important
    }

    protected function _initView()
    {
        $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'
            )
        );
    }

    protected function _initDatabase()
    {
        $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);
    }

    /* this will be taken care of later */
    static public function sendResponse(Zend_Controller_Response_Http $response)
    {
        $response->setHeader('Content-Type', 'text/html; charset=UTF-8', true);
        $response->sendResponse();
    }
}

Some terminology: the _init*() methods are called resource bootstrap methods and the part after the _init is the resource identifier, or simply resource. In addition to bootstrap methods, resources can be initialized with resource plugins which will be shown later.

For the most part, this version is the same as the original bootstrap. The major difference is the absence of the prepare() and run(). They are not needed anymore because their functions are already covered by the base bootstrap class. The application can be run by registering this bootstrap with Zend_Application and then calling bootstrap() and run().

<?php
// public/index.php

set_include_path('../library' . PATH_SEPARATOR . get_include_paths());
// assuming the Zend classes are in the library path as recommended before, although
// I believe this is not recommended anymore

require_once 'Zend/Application.php';
$app = new Zend_Application('dev', array('bootstrap' => '../application/Bootstrap.php'));
$app->bootstrap()->run();

bootstrap() calls the resource bootstrap methods in the order they are defined in the class, then run() invokes the dispatch method of the front controller. The one bootstrap resource that is absolutely required by Zend_Application is the FrontController. The bootstrap method for the FrontController also needs to return the front controller instance. run() would fail otherwise. This is because it checks if the front controller has a controller directory registered for the default module.

You might have noticed that references to Zend_Loader have been removed. The autoloading function of Zend_Loader has been superseded by Zend_Loader_Autoloader which is registered behind the scenes by Zend_Application. It works quite differently now, but there is a way to make it behave like the old autoloader. See this article for more information.

There’s a slight problem with this version. In the original version, the front controller is set to return the response and then the static method sendResponse() sets a custom Content-Type header. This is beyond the scope of the bootstrap. The proper way to handle this is to create a (late-running) controller plugin which pulls the response from the front controller and sets the desired header. I decided that it’s not worth the trouble, so I’ll just remove that bit and disable returnResponse since the run() method doesn’t expect the front controller to return the response. Besides, there’s a good chance that your application returns something other than text/html in some instances (application/json response for AJAX requests for example), so I’m not sure if the original behavior is a good idea after all. It probably makes more sense to set those headers before the dispatch so it could be overridden later. That would be trivial to implement in our new bootstrap, but I’m just totally skipping that part since I don’t believe it’s necessary.

Removing the cruft

Zend_Application and Zend_Application_Bootstrap_Bootstrap provides us with a few methods and several plugins to handle the initialization of the commonly used Zend components.

Zend_Application handles the initialization of the environment. This includes the PHP settings, include paths and autoloader namespaces. We will now remove the _initEnvironment part and off-load it to Zend_Application.

We’ll also clean up the statics a bit. We’ll use the bootstrap’s internal registry in place of the static members. Any _init*() method return value will be stored in a local instance of Zend_Registry. This is just a fancy way of storing stuff in an instance member (Zend_Registry is just an ArrayObject after all). It is wrapped in an object to make the retrieval elsewhere in the application easier. Any stored resource return value can be pulled with $this->getResource('resource') in a resource method, or $this->getInvokeArg('bootstrap')->getResource('resource') in a controller. This makes the registry resource kinda redundant, so we’ll remove that bit as well.

// public/index.php
...
$app = new Zend_Application('development', array(
    'bootstrap' => '../application/Bootstrap.php',
    'phpSettings' => array(
        'error_reporting' => E_ALL | E_STRICT,
        'display_errors' => true,
        'display_startup_errors' => true,
        'date.timezone' => 'Asia/Manila',
    ),
));
$app->bootstrap()->run();
...
// application/Bootstrap.php
<?php

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initPath()
    {
        return realpath(dirname(__FILE__) . '/..');
    }

    protected function _initConfiguration()
    {
        $config = new Zend_Config_Ini(
            $this->getResource('path') . '/application/config/config.ini',
            'general'
        );
        return $config;
    }

    protected function _initFrontController()
    {
        $frontController = Zend_Controller_Front::getInstance();
        $frontController->throwExceptions(true);
        $frontController->setControllerDirectory(
            $this->getResource('path') . '/application/controllers'
        );
        $frontController->setParam('registry', $this->getContainer()); // getContainer() returns the local registry
        return $frontController;
    }

    protected function _initView()
    {
        $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' => $this->getResource('path') . '/application/views/layouts',
                'layout' => 'common'
            )
        );
        return $view;
    }

    protected function _initDatabase()
    {
        $config = $this->getResource('configuration');
        $db = Zend_Db::factory($config->db->adapter, $config->db->toArray());
        $db->query("SET NAMES 'utf8'");
        Zend_Db_Table::setDefaultAdapter($db);
        return $db;
    }
}

We removed the PHP settings from _initEnvironment, and we’re left with the project root path variable which we store in the bootstrap registry. We also renamed the method to _initPath to reflect the changes. The path is pulled in other places in the bootstrap with $this->getResource('path'). This is silly and a bit inefficient. The path only has to be derived once and never changes, so it makes sense to make it a global constant. This is one of the few times a define() is acceptable. Since we’re almost always concerned with the application path rather than the project path, we’ll name the constant APP_PATH and make it point to the application directory under the project path.

// public/index.php
define('APP_PATH', realpath(dirname(__FILE__) . '/../application'));
...
$app = new Zend_Application('dev', array(
    'bootstrap' => APP_PATH . '/Bootstrap.php',
    'phpSettings' => array(
        'error_reporting' => E_ALL | E_STRICT,
        'display_errors' => true,
        'display_startup_errors' => true,
        'date.timezone' => 'Asia/Manila',
    ),
));
$app->bootstrap()->run();

// application/Bootstrap.php
<?php

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initConfiguration()
    {
        $config = new Zend_Config_Ini(
            APP_PATH . '/config/config.ini',
            'general'
        );
        return $config;
    }

    protected function _initFrontController()
    {
        $frontController = Zend_Controller_Front::getInstance();
        $frontController->throwExceptions(true);
        $frontController->setControllerDirectory(
            APP_PATH . '/controllers'
        );
        $frontController->setParam('registry', $this->getContainer()); // getContainer() returns the local registry
        return $frontController;
    }

    protected function _initView()
    {
        $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' => APP_PATH . '/views/layouts',
                'layout' => 'common'
            )
        );
    }

    protected function _initDatabase()
    {
        $config = $this->getResource('configuration');
        $db = Zend_Db::factory($config->db->adapter, $config->db->toArray());
        $db->query("SET NAMES 'utf8'");
        Zend_Db_Table::setDefaultAdapter($db);
        return $db;
    }
}

Using resource plugins

The above bootstrap is as simple as it could get without using resource plugins. Resource plugins are pre-made components that allow us to bootstrap resources by simply passing an array of options. There are plugins for FrontController, View, Layout, Db, Session and a handful of others. We could simplify the bootstrap even more by utilizing these plugins.

<?php

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initConfiguration()
    {
        $config = new Zend_Config_Ini(
            APP_PATH . '/config/config.ini',
            'general'
        );
        return $config;
    }

    protected function _initResourcePlugins()
    {
        $this->registerPluginResource('Frontcontroller', array(
            'controllerDirectory' => APP_PATH . '/controllers',
            'throwExceptions' => true,
            'registry' => $this->getContainer(),
        ));

        $this->registerPluginResource('View', array(
            'encoding' => 'UTF-8' // this view is pushed into the ViewRenderer behind the scenes
        ));

        $this->registerPluginResource('Layout', array(
            'layoutPath' => APP_PATH . '/views/layouts',
            'layout' => 'common'
        ));

        $config = $this->getResource('configuration');
        $this->registerPluginResource('db', array(
            'adapter' => $config->db->adapter,
            'params' => $config->db->toArray(),
            'charset' => 'utf8',
        ));
    }
}

You can see how resource plugins greatly simplify the bootstrapping for us. Notice that I removed the $db->query("SET NAMES 'utf8'") line. Doing that query in a bootstrap negates the advantage of the lazy connection feature of the database adapter, not to mention that it’s a driver-specific feature. It’s better to leave the charset as a driver option so that it would only be executed during connection and to avoid breaking the drivers which do not support that feature.

There’s a little quirk regarding the first argument of registerPluginResource(). I’m not sure how and where these names are normalized. If the first letter of ‘Frontcontroller’ isn’t capitalized, the bootstrap fails. It seems that it doesn’t override the ‘FrontController’ resource automatically registered during __construct if the capitalization is wrong. However, the case doesn’t matter when retrieving the resource. You could do $this->getResource('fRonTConTroLler') and still get the correct resource. I’m not sure if this inconsistency is by design or an oversight. Anyway, it doesn’t really matter if plugins are registered via an array or config passed to the Zend_Application constructor, which is what you’ll be doing most of the time.

Registering resource plugins through Zend_Application

Resource plugins can also be registered by passing it in an array during the instantiation of Zend_Application. We’ll expand the database parameters for now to remove the dependency on the configuration resource, but we’ll bring it back later. We’ll also remove the ‘registry’ front controller param since it’s impossible to assign during Zend_Application instantiation. If something like that is really needed, it could be assigned in a resource method later.

// public/index.php
<?php
define('APP_PATH', realpath(dirname(__FILE__) . '/../application'));
set_include_path(realpath(APP_PATH . '/../library') . PATH_SEPARATOR . get_include_paths());
require_once 'Zend/Application.php';
$app = new Zend_Application('dev', array(
        'bootstrap' => APP_PATH . '/Bootstrap.php',
        'phpSettings' => array(
            'display_errors' => true,
            'display_startup_errors' => true,
            'error_reporting' => E_ALL|E_STRICT,
            'date.timezone' => 'Asia/Manila',
        ),
        'resources' => array(
            'frontController' => array(
                'controllerDirectory' => APP_PATH . '/controllers',
                'throwExceptions' => true
            ),
            'view' => array(
                'encoding' => 'UTF-8'
            ),
            'layout' => array(
                'layoutPath' => APP_PATH . '/views/layouts',
                'layout' => 'common'
            ),
            'db' => array(
                'adapter' => 'Pdo_Mysql',
                'params' => array(
                    'dbname' => 'foo',
                    'username' => 'user',
                    'password' => 'pass123',
                    'host' => 'localhost',
                    'charset' => 'utf8'
                ),
            )
        )
    ));
$app->bootstrap()->run();

// application/Bootstrap.php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    public function _initFrontRegistry()
    {
        $front = $this->bootstrap('frontController')->getResource('frontController');
        $front->setParam('registry', $this->getContainer());
    }
}

We’ve made some compromises along the way, but isn’t it nice to see the original bootstrap reduced to this? If the registry param isn’t needed, we could even make the bootstrap completely empty! And if the bootstrap is empty, we could just delete it altogether and remove the ‘bootstrap’ option in the Zend_Application constructor options.

A note when mixing resource methods and plugins: you should avoid naming your methods after a plugin. The methods take precedence over plugins, so all the configuration you passed to the Zend_Application constructor will be ignored if you have a resource method with the same name.

Passing a Zend_Config object to Zend_Application

We could do one final refinement to our bootstrap. Instead of passing an array as the second argument, we could pass a Zend_Config object or a path to an ini or xml file. There’s nothing wrong with arrays, and they’re undeniably more powerful than ini or xml. Isolating the config into its own file just makes the index.php a bit more readable and maintainable.

// public/index.php
<?php
define('APP_PATH', realpath(dirname(__FILE__) . '/../application'));

set_include_path(realpath(APP_PATH . '/../library') . PATH_SEPARATOR . get_include_paths());

require_once 'Zend/Application.php';
$app = new Zend_Application('dev', APP_PATH . '/config/config.ini');
$app->bootstrap()->run();

/*
; application/config/config.ini
[dev]
bootstrap = APP_PATH "/Bootstrap.php"

phpSettings.display_errors = on
phpSettings.display_startup_errors = on
phpSettings.error_reporting = E_ALL|E_STRICT
phpSettings.date.timezone = "Asia/Manila"

resources.frontController.controllerDirectory = APP_PATH "/controllers"
resources.frontController.throwExceptions = on
resources.view.encoding = "UTF-8"
resources.layout.layoutPath = APP_PATH "/views/layouts"
resources.layout.layout = "common"
resources.db.adapter = "Pdo_Mysql"
resources.db.params.host = "localhost"
resources.db.params.dbname = "foo"
resources.db.params.username = "user"
resources.db.params.password = "password"
resources.db.params.charset = "utf8"
*/

Zend_Application parses the section of the ini file named after the first argument (dev in this case) and converts it into an array. It then proceeds with the bootstrapping as before.

Our bootstrap now looks a lot like the bootstrap generated by the zf command. If you’re starting a new project in ZF 1.8, I recommend setting up Zend_Tool. The zf command is a huge time saver as it could automatically generate the directory structure and other boilerplate code for you. See this article for more information.

I haven’t touched on the modules resource plugin and the module resource autoloader yet. IMO, these are the real killer features of ZF 1.8. In a future post, I’ll show how the modules plugin can be used to make modular ZF applications.

Advertisements
One Comment leave one →
  1. October 16, 2009 11:11 pm

    Zend_Application is really powerful thing! I like it!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: