Multi-modules and multi-templates application for Zend_Application

At the very beginning I need to introduced everybody of Zend_Application before doing anything. As one of the most important big changes in Zend Framework 1.8, the Zend_Application has introduce two new concepts (new to Zend Framework) – Bootstrap and Resource, which greatly simplifies the construction of applications. It can be said that the Zend_Appication marks the emergence of the genuine maturing of Zend Framework.

 

 

Two new concepts in Zend_Application :

 

1. Bootstrap

 

The bootstrap, familiar to many developers who had tried to used Zend Framework, is aim at packaging the initialization process. I am sure most of you have wrote this (or similar to this) bootstrap class :

 

// Bootstrap.php
class Bootstrap
{
    // ...

    public function initLoader(){...}
    public function initController(){...}
    public function initDb(){...}
    public function initView(){...}
    public function initLayout(){...}
    public function initSession(){...}
    public function initAuth(){...}
    public function initAcl(){...}

    // ...
}

 

In this way your bootstrap has became an monster and hard to handle. But don’t worry, now Zend_Application has been your good friend which makes the construction of application really easy. What you need to do is just create the object of appliation and then make some configurations (usually one .ini).

 

2. Resource

 

Zend_Application_Resource is a new concept from 1.8 in view of the characteristic of php. The spirit it describes is :  Loaded On Demand. Since the every time of php’s running and parsing is a complete process of resource recycling, the question how to minimize the code of each cycle has became one of the most important thing for either php optimization or framework development.

 

In fact we can see resource as a component of Zend Framework, for example Zend_Controller, Zend_Db, Zend_View and so on. And at the same time you can also create your own resource in order to use your own component. For instance the Zend_Application_Resource_Db is one of the default resources which initializes the Zend_Db object and set default adapter :

 

class Zend_Application_Resource_Db extends Zend_Application_Resource_ResourceAbstract
{
    // ...

    // Defined by Zend_Application_Resource_Resource
    public function init()
    {
        // ...

        $this->_db = Zend_Db::factory($adapter, $this->getParams());
        Zend_Db_Table::setDefaultAdapter($db);
        return $db;

        // ...
    }
}

 

Zend Framework 1.8 has provided 10 default resources for the moment :

 

1. Zend_Application_Resource_Db

2. Zend_Application_Resource_Frontcontroller

3. Zend_Application_Resource_Layout

4. Zend_Application_Resource_Locale

5. Zend_Application_Resource_Modules

6. Zend_Application_Resource_Navigation

7. Zend_Application_Resource_Router

8. Zend_Application_Resource_Session

9. Zend_Application_Resource_Translate

10. Zend_Application_Resource_View

 

Hope it will increase in the furture.

 

Here is one simple example to initialize the FrontController resource :

 

// Project root
defined('APPLICATION_PATH')
    || define('APPLICATION_PATH',
              MY_PROJECT_ROOT . '/application');

// Application environment
defined('APPLICATION_ENV')
    || define('APPLICATION_ENV',
              (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV')
                                         : 'production'));

// Zend_Application and setting of FrontController resource
require_once 'Zend/Application.php';
$application = new Zend_Application(
    APPLICATION_ENV,
    array(
        'resources' => array(
            'FrontController' => array(
                'controllerDirectory' => APPLICATION_PATH . '/controllers',
            ),
        ),
    )
);

// Bootstrap resources and run
$application->bootstrap();
$application->run();

 

And we can create our own resource by extending Zend_Application_Resource_ResourceAbstract . Then what we need to do is make some configurations within Application.ini to register our resource to the Bootstrap. After that the function Zend_Application_Bootstrap_Bootstrap::bootstrap($resource) is the one to bootstrap resource whenever needed.

 

Register custom view resource :

 

// Custom view resource
class Kbs_Application_Resource_View extends Zend_Application_Resource_ResourceAbstract
{
    protected $_view;

    // init view
    // @return Zend_View $view
    public function init()
    {
        if (null === $this->_view) {
            $options = $this->getOptions();
            $view = new Zend_View($options);
            ......
            $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper(
                'ViewRenderer'
            );
            $viewRenderer->setView($view);
            $this->_view = $view;
        }        
        return $this->_view;
    }
}

 

And parameters of Zend_Application  :

 

$application = new Zend_Application(APPLICATION_ENV, array(
    // Path and prefix of plugin resources
    'pluginPaths' => array(
        'Kbs_Application_Resource' => 'Kbs/Application/Resource/',
    ),
    'resources' => array(
        'FrontController' => array(...),
        ......
        // View resource
        'View' => array('title' => 'my application'),
    ),
));

 

Of course we can also put them in Application.ini instead :

 

// Application.ini

pluginPaths.Kbs_Application_Resource                    = APPLICATION_PATH "/../library/Kbs/Application/Resource/"
resources.view.title                                    = "my application"
resources.view.encoding                                 = "UTF-8"
 

Then pass it to Zend_Application :

 

$application = new Zend_Application(
    APPLICATION_ENV,
    PROJECT_ROOT . '/Config/Application.ini'
));

 

 


 

As we have already made a preview of Zend_Application and Zend_Application_Resource, let’s start our main subject : how to build multi-modules and multi-templates application with Zend_Application .

 

Zend Framework, which based on MVC design pattern, has provided a complete set of modular design. That’s to say every module in Zend Framework has it’s own MVC structure.

 

But it’s not easy for you to do such things like assigning different templates to different modules or assigning different templates to the same module. We need some configurations and skills when building our structure and application.

 

First, our application’s structure is just as below :

 

 

 

We have two folders – modules and templates under application. And we got two modules – admin and front in each of them. The folder controllers within each module is where we store controllers (e.g. IndexController, UserController). The folders of modules in templates is where we store all different templates. For example we got two templates called default and oceanStyle under front.

 

We then put all our resources under  library/Kbs/Application/Resource/  folder :

 

 

Ok let’s finish our configuration file (Application.ini) :

  

; Application.ini

[production]
;=========== Library path
;includePaths.library                                           = APPLICATION_PATH "/../library/"

;=========== Autoload namespace prefix
autoloadernamespaces.0                                          = "Zend_"
autoloadernamespaces.1                                          = "ZendX_"
autoloadernamespaces.2                                          = "Kbs_"

;=========== php ini setting
phpsettings.date.timezone                                       = "Asia/Shanghai"
phpSettings.display_startup_errors                              = 0
phpSettings.display_errors                                      = 0
phpsettings.error_reporting                                     = 8191

;=========== Bootstrap class and it’s path
bootstrap.path                                                  = APPLICATION_PATH "/Bootstrap.php"
bootstrap.class                                                 = "Bootstrap"

;=========== Plugin resources and their path
pluginPaths.Kbs_Application_Resource                            = APPLICATION_PATH "/../library/Kbs/Application/Resource/"

;=========== Resource frontController
resources.frontController.moduleDirectory                       = APPLICATION_PATH "/modules/"
resources.frontController.moduleControllerDirectoryName         = "controllers"
resources.frontController.defaultModule                         = "front"
resources.frontController.plugins.common                        = "Kbs_Controller_Plugin_Common"
resources.frontController.noErrorHandler                        = 0
resources.frontController.throwExceptions                       = 1
 

;=========== Resource layout
resources.layout.layout                                         = "we use resources.view.params.module.layout instead"
resources.layout.layoutPath                                     = "we use resources.view.params.module.layoutPath instead"

;=========== Resource view
resources.view.title                                            = ""
resources.view.encoding                                         = "UTF-8"
resources.view.helperPathPrefix                                 = "Kbs_View_Helper_"
resources.view.helperPath                                       = "Kbs/View/Helper/"
 

;=========== Params for modules front and admin within resource view (including basepath, prefix, layout, layoutPath)
resources.view.params.front.basePath                            = APPLICATION_PATH "/templates/front/default/"
resources.view.params.front.helperPathPrefix                    = "Kbs_View_Helper_Front_"
resources.view.params.front.helperPath                          = "Kbs/View/Helper/Front/"
resources.view.params.front.layout                              = "frontlayout"
resources.view.params.front.layoutPath                          = APPLICATION_PATH "/templates/front/default/layout/"

resources.view.params.admin.basePath                            = APPLICATION_PATH "/templates/admin/default/"
resources.view.params.admin.helperPathPrefix                    = "Kbs_View_Helper_Admin_"
resources.view.params.admin.helperPath                          = "Kbs/View/Helper/Admin/"
resources.view.params.admin.layout                              = "adminlayout"
resources.view.params.admin.layoutPath                          = APPLICATION_PATH "/templates/admin/default/layout/"
 

;=========== Other params for view
resources.view.params.pathCss                                   = "/public/css/"
resources.view.params.pathImg                                   = "/public/img/"
resources.view.params.pathJs                                    = "/public/js/"
resources.view.params.doctype                                   = "HTML4_STRICT"
resources.view.params.charset                                   = "utf-8"

;=========== Database setting
resources.db.adapter                                            = "pdo_mysql"
resources.db.params.host                                        = "localhost"
resources.db.params.username                                    = "xxx"
resources.db.params.password                                    = "xxx"
resources.db.params.dbname                                      = "xxx"
resources.db.isDefaultTableAdapter                              = true
resources.db.params.driver_options.1002                         = "SET NAMES UTF8;"

;=========== Translation setting
resources.translate.registry_key                                = "Zend_Translate"
resources.translate.adapter                                     = "array"
resources.translate.options.scan                                = "directory"
resources.translate.data.directory                              = APPLICATION_PATH "/languages/"
resources.translate.data.fileExt                                = ".php"

;=========== Locale enabled
resources.locale                                                = true

[testing : production]
phpSettings.display_startup_errors                              = 1
phpSettings.display_errors                                      = 1
phpsettings.error_reporting                                     = 8191
resources.db.params.username                                    = "xxx"
resources.db.params.password                                    = "xxx"
resources.db.params.dbname                                      = "xxx"

[development : production]
phpSettings.display_startup_errors                              = 1
phpSettings.display_errors                                      = 1
phpsettings.error_reporting                                     = 8191
resources.db.params.username                                    = "xxx"
resources.db.params.password                                    = "xxx"
resources.db.params.dbname                                      = "xxx"

 

The two sections testing and development are inherited from production. We can set runtime configurations by putting phpsettings.* as well as .htaccess or php.ini. The bootstrap class and it’s path can be set by bootstrap.* while resources.* is so called resources.

 

These are configurations of my own and may not fit any situation. Make some changes to fit yourself whenever needed.

 

The entry index.php :

 

// Project root
defined('PROJECT_ROOT')
    || define('PROJECT_ROOT',
              realpath(dirname(dirname(__FILE__))));

// Application path
defined('APPLICATION_PATH')
    || define('APPLICATION_PATH',
              PROJECT_ROOT . '/application');

// Application environment
defined('APPLICATION_ENV')
    || define('APPLICATION_ENV',
              (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV')
                                         : 'production'));

// Include paths
set_include_path(implode(PATH_SEPARATOR, array(
    PROJECT_ROOT . '/library'
)));

// Zend_Application
require_once 'Zend/Application.php';

// Create application
$application = new Zend_Application(
    APPLICATION_ENV,
    PROJECT_ROOT . '/library/Kbs/Config/Application.ini'
);

// Throw warnings when development and testing
if ('production' !== APPLICATION_ENV) {
    $application->getAutoloader()->suppressNotFoundWarnings(false);
}

// Bootstrap (only) frontController and run
$application->getBootstrap()->bootstrap('FrontController');
$application->run();

 

Here APPLICATION_ENV is a predefined system environment variable which contain one of these three values – development, testig, production. It means which environment the application is running on.

 

What you need to pay attention is that we only bootstrap frontController resource in order to minimize the resources we boot at the first time. We will leave all others handled by one custom action plugin called Kbs_Controller_Plugin_Common and then loaded on demand by using Zend_Application_Bootstrap_Bootstrap::bootstrap($resource) method.

 

We use plugin resource but not override resource initailization (e.g. Bootstrap::_initView) to keep our bootstrap class clean and neat :

 

// Kbs/Application/Bootstrap.php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    // Do nothing
}

 

Now we need to use a custom action plugin which had already defined in Application.ini to tell FrontController where the template localed :

 

// Custom action plugin named ‘common’

resources.FrontController.plugins.common        = "Zend_Controller_Plugin_Common"

 

// Custom action plugin Kbs_Controller_Plugin_Common
require_once('Zend/Controller/Plugin/Abstract.php');

class Kbs_Controller_Plugin_Common extends Zend_Controller_Plugin_Abstract
{
    // Route shut down action
    public function routeShutdown(Zend_Controller_Request_Abstract $request)
    {
        // Get module name for example admin, front etc.
        $module = $request->getModuleName();

        // Bootstrap object stored in front controller
        $bootstrap = Zend_Controller_Front::getInstance()->getParam('bootstrap');

        // Bootstrap resource view
        $bootstrap->bootstrap('View');
        $view = $bootstrap->getResource('View');
        $moduleParams = $view->$module;

        // Configurations of view
        $view->addBasePath($moduleParams['basePath'])
             ->addHelperPath($moduleParams['helperPath'],
                             $moduleParams['helperPathPrefix']);

        // Configurations of layout
        $bootstrap->bootstrap('Layout');
        $layout = $bootstrap->getResource('Layout');
        $layout->setLayoutPath($moduleParams['layoutPath'])
               ->setLayout($moduleParams['layout']);
    }
}

 

Action plugin is the best method that I can think of since we can easily fetch module name when route shut down. Then we just need to fetch the infos of template (layout and scripts) from Application.ini depending on different modules.

 

Plugin resource Kbs_Application_Resource_View :

 

// Plugin resource view
class Kbs_Application_Resource_View extends Zend_Application_Resource_ResourceAbstract
{
    protected $_view;

    // init view
    public function init()
    {
        if (null === $this->_view) {
            // Options from Application.ini
            $options = $this->getOptions();
            $view = new Zend_View($options);
            if (!empty($options['params'])) {
                foreach ($options['params'] as $key => $value) {
                    $view->$key = $value;
                }
            }

            // ViewRenderer action helper
            $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper(
                'ViewRenderer'
            );

            // Save view object
            $viewRenderer->setView($view);
            $this->_view = $view;
        }        
        return $this->_view;
    }
}

 

Everything is ok now except the contents of layout and scripts. Of course we can leave ttem to our design integration team.

 

In the example above we can easily change our template by replace :

 

resources.view.params.front.basePath            = APPLICATION_PATH "/templates/front/default/"
resources.view.params.front.layoutPath          = APPLICATION_PATH "/templates/front/default/layout"

 

by

 

resources.view.params.front.basePath            = APPLICATION_PATH "/templates/front/oceanStyle/"
resources.view.params.front.layoutPath          = APPLICATION_PATH "/templates/front/oceanStyle/layout"

 

Easy isn’t it? Ok, that’s all for how to build your multi-modules and multi-templates application with Zend_Application. 

 

What I didn’t mention here is Zend/Application/Module which is the third part of Zend_Application to deal with module bootstrap issues. Because it’s said that they will still be improved now and the furture before version 2.0. So let’s leave it now and wait for something better from Zend Framework 1.9.

 

Posted in Zend Framework | Tagged , | 2 Comments
  1. Excellent article! I’ve been playing around with this for a couple of days now, trying to get multiple layouts to load based on UA (iphone, desktop, blackberry, etc). Thanks for saving me a bunch of time.

    • I am so glad that this article can help you! In fact the structure is a bit different between ZF1.x and ZF2 which I will write a new article for on this kind of multiple layouts issues.

      Thx so much, for your first reply on my blog 🙂