Web Development, Zend Framework

Zend Framework – The 1.8.2 Way

When I first use Zend Framework, it was like version 1.5. As of this writing, they already have Zend Framework 1.9.1 and there are so many changes especially on the bootstrapping portion. Class autoloading is also enhanced for performance.

According to the Zend Framework’s quickstart tutorial, you can create a blank project using its command line tool. However, the tool does not work on my current setup. Moreover, I still like to create my custom structure my own or I still enjoy creating individual files one by one for every project over and over again.

Actually, there was a problem with my setup, but I still go for manual setup. Now, we will create our blank application they way it is usually done on Zend Framework 1.8.2 (it is almost the same as 1.9.1).

Basic Structure

The file structure for Zend Framework 1.8.2 and higher is almost the same as it was on 1.5.x but with few changes. My structure for a simple project (no modules) looks like below:

[project_dir]
    [application]
        [configs]
            app.ini
        [controllers]
            [helpers]
            IndexController.php
            ErrorController.php
        [data]
        [forms]
        [layouts]
            [scripts]
                layout.phtml
        [models]
            [DbTable]
        [tmp]
        [views]
            [scripts]
                [index]
                    index.phtml
                [error]
                    error.phtml
        Bootstrap.php
    [library]
        [Zend]
    [public]
        
        
        [images]
        index.php

Perhaps the most notable change is the filename Bootstrap.php where the old version uses bootstrap.php. Let us start with index.php

The index.php

Below is the usual structure I used when creating generic index.php file as main entry point.

<?php
	/*
	 * Define the necessary constants to be used in the system
	 * Then set the library path
	 * 
	 * For production release, set the include path in php.ini
	 * instead of setting it dynamically
	 */
	define('PUBLIC_WEB_PATH', substr($_SERVER&#91;'SCRIPT_NAME'&#93;, 0, strrpos($_SERVER&#91;'SCRIPT_NAME'&#93;, '/')));
	define('INDEX_PATH', str_replace('\\', '/', dirname(__FILE__)));
	define('APPLICATION_PATH', str_replace('\\', '/', realpath(INDEX_PATH . '/../application')));
	define('LIBRARY_PATH', str_replace('\\', '/', realpath(INDEX_PATH . '/../library')));
	set_include_path(
			LIBRARY_PATH
			. PATH_SEPARATOR
			. get_include_path()
		);
	
	/**
	 * Salt for general hashing (security)
	 */
	define('GENERIC_SALT', 'asdDSasd4asdAd1GH4sdWsd1');
	
	/**
	 * Define application environment
	 */
	if( !defined('APPLICATION_ENV') )
	{
	    define('APPLICATION_ENV', 'production');
	}
	/** Zend_Application */
	require_once 'Zend/Application.php';  
	
	// Create application, bootstrap, and run
	$application = new Zend_Application(
	    APPLICATION_ENV,
	    APPLICATION_PATH . '/configs/app.ini'
	);
	$application->bootstrap()->run();

I usually initialize paths using global constants so that I can use them throughout the project. For performance issue, you can make these constants as string literal when in production so avoid too much overhead of setting the include path.

What the index.php did is to initialize Zend_Application and pass the configuration file (app.ini). The bootstrap() call calls the necessary bootstrap operations involving the app.ini and its settings. Finally, the run() call runs the MVC and returns the response to the client.

Configuration File (INI)

Next, we are going to take a look at app.ini:

[production]

# PHP Settings
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
phpSettings.date.timezone = "Asia/Manila"

# General Bootstap
bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
bootstrap.class = "Bootstrap"

# Resources
includePaths.library = APPLICATION_PATH "/../library"
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
resources.frontController.moduleDirectory = APPLICATION_PATH
resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts"
resources.view[] =
resources.db.adapter = "pdo_mysql"
resources.db.params.host = "localhost"
resources.db.params.username = "db_user"
resources.db.params.password = "db_password"
resources.db.params.dbname = "project_db"
resources.db.params.driver_options.1002 = "SET NAMES UTF8"
resources.db.params.profiler = true
resources.db.params.pdotype	= "mysql"
resources.db.isDefaultTableAdapter = true

session.idlelimit = 1800

barcode.printer = "SATO GT408e"
barcode.horizontal_offset = 400
barcode.copy_per_lot_number = 3

[staging : production]

[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1

[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1

Most of the values on the ini file are self-explanatory. It sets the error/warning display settings for production, development, staging and testing mode. It sets the bootstrap file which we will discuss later on.

The ini file also initializes the database connectivity therefore we don’t need to do it on our bootstrap. Other settings are project specific. As you’ve noticed, the APPLICATION_PATH constant is used here. Zend Framework’s ini classes support the defined constants on ini file. Most of the lines should be used as is for most projects.

The line:

resources.frontController.moduleDirectory = APPLICATION_PATH

defines the module directory. In this example, I haven’t use any modules yet and there is an upcoming post regarding modules in Zend Framework. So you can safely leave it as is.

Bootstrap.php – Zend Application’s Bootstrap

As you’ve noticed, we are just following the flow from index.php to the very detail of the Zend Framework’s MVC. The next thing processed is the Bootstrap.php file:

<?php
/**
 * General Bootrsapping Class
 * @author Lysender
 *
 */
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{	
	/**
	 * Initialize Zend Autoloading
	 * 
	 */

    protected function _initAutoload()
    {
        $autoloader = new Zend_Application_Module_Autoloader(array(
            'namespace' => 'Default',
            'basePath'  => APPLICATION_PATH,
        ));
        return $autoloader;
    }
    
    protected function _initCustomAutoloader()
    {
    	$autoloader = Zend_Loader_Autoloader::getInstance();
    	$autoloader->registerNamespace('MyLib_');
    	$autoloader->registerNamespace('Spreadsheet_');
    	$autoloader->registerNamespace('OLE_');
    	$autoloader->registerNamespace('PEAR_');
    }
    
    /**
     * Initialized MVC and Layouts
     * 
     */
    
    protected function _initPresentation()
    {
        $this->bootstrap('view');
        $view = $this->getResource('view');
        $view->doctype('XHTML1_STRICT');
        $view->headMeta()->setHttpEquiv('Content-Type', 'text/html; charset=utf-8');
		$view->headTitle("Title Prefix | ");
    }
    
    /**
     * Initialized Helpers
     * 
     */
    protected function _initActionHelpers()
    {
    	Zend_Controller_Action_HelperBroker::addPath(
    		APPLICATION_PATH . "/controllers/helpers"
    	);
    }
    
    /**
     * Initialized Routes
     * No routes as of this moment
     */
    protected function _initRoutes()
    {
    	//$config = new Zend_Config_Ini (
    		//APPLICATION_PATH . '/configs/routes.ini', 'production'
    	//);
    	//$frontController = Zend_Controller_Front::getInstance();
    	//$router = $frontController->getRouter();
    	//$router->addConfig( $config, 'routes' );
    }
    
    /**
     * Setup Caching for Table Metadata
     * 
     */
    protected function _initMetadataCache()
    {
		//Stores within 24 hours
		$frontendOptions = array( 
		                    'lifetime'                  => 86400, 
		                    'automatic_serialization'   => true 
		                    ); 
		$backendOptions  = array( 
		                     'cache_dir'                => APPLICATION_PATH . '/tmp' 
		                    ); 
		$cache = Zend_Cache::factory( 
		                    'Core', 
		                    'File', 
		                    $frontendOptions, 
		                    $backendOptions 
		                    );
		//Cache table metadata
		Zend_Db_Table_Abstract::setDefaultMetadataCache($cache);
    }
    
    /**
     * Setup General Caching
     * 
     */
    protected function _initCache()
    {
		//General caching (Stores within 30 minutes)
		$frontendOptions = array( 
		                    'lifetime'                  => 18000, 
		                    'automatic_serialization'   => true 
		                    ); 
		$backendOptions  = array( 
		                    'cache_dir'                => APPLICATION_PATH . '/tmp' 
		                    ); 
		$cache = Zend_Cache::factory( 
		                    'Core', 
		                    'File', 
		                    $frontendOptions, 
		                    $backendOptions 
		                    ); 
		//save to registry
		$registry = Zend_Registry::getInstance();
		$registry->set('cache', $cache);
    }
    
    /**
     * Initializes the plugin loader cache
     * 
     */
    protected function _initPluginLoaderCache()
    {
		$classFileIncCache = APPLICATION_PATH .  '/data/pluginLoaderCache.php'; 
		if (file_exists($classFileIncCache))
		{ 
		    include_once $classFileIncCache; 
		} 
		Zend_Loader_PluginLoader::setIncludeFileCache($classFileIncCache); 
    }
    
    /**
     * Initializes Messages and Web Config
     * 
     */
    protected function _initMessages()
    {
		//web config
		$config = new Zend_Config_Ini(
		    APPLICATION_PATH . '/configs/app.ini', 
		    APPLICATION_ENV
		);
		
		//save to registry
		$registry = Zend_Registry::getInstance();
		$registry->set('config', $config);
		unset($config);
    }
    
    protected function _initSession()
    {
		Zend_Locale::setDefault('en_US');
		Zend_Session::start();
    }
    
}//end class

Most of the project specific initialization takes place at Bootstrap.php. As you can see, each protected function starts with _init prefix. All functions prefixed with _init are processed by Zend_Application’s Bootstrap when called. For example:

protected function _initFoo();
protected function _initBar();

In index.php, the

$bootstrap->bootstrap(‘foo’);

will call _initFoo() and

$bootstrap->bootstrap(array(‘foo’, ‘bar’));

will call them _initFoo() and _initBar() both. However, to call all _init* functions, it is simply:

$bootstrap->bootstrap();

The function _initAutoload() will initialize autoloading for classes within the Default module, that is, all classes that starts with Default_ will be loaded via its respective locations on the module. For example, Default_Model_User will be loaded from application/models/User.php and Default_Model_DbTable_User will be loaded from application/models/DbTable/User.php. Exception to this rule are the controllers within the default module. application/controllers doesn’t need the prefix Default_ since it is already assumed that they belong to default module when no prefix is defined.

The function _initCustomAutoloader() initializes custom autoloader for other third party libraries such as PEAR (in my example).

I think the rest are self-explanatory. Sorry but I’m a bit lazy now. Anyway, when you read the code, the code itself explains clearly what it is for.

Layout

Since I have used MVC here, let’s take a look first to our layout script before jumping into IndexController.

<?php echo $this->doctype(); ?>
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<meta http-equiv="Content-language" content="ja" />
	<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
	<?php echo $this->headMeta(); ?>
	<?php echo $this->headTitle(); ?>
	
	<!-- jQuery this is required -->
	<script type="text/javascript" src="<?php echo PUBLIC_WEB_PATH; ?>/js/jquery-1.3.1.min.js"></script>
	
	<link href="<?php echo PUBLIC_WEB_PATH; ?>/css/reset.css" media="screen" rel="stylesheet" type="text/css" />
	<link href="<?php echo PUBLIC_WEB_PATH; ?>/css/default.css" media="screen" rel="stylesheet" type="text/css" />
    
	<?php echo $this->headLink(); ?>

</head> 
<body>
<?php echo $this->layout()->content; ?>

<?php echo $this->headScript(); ?>
</body>
</html>

As you can see, it is simply an XHTML strict doctype page, with some default CSS and JS already included. If you are a Zend Framework developer, you already know about this stuff.

Index Page – Controller and View

Below is how the IndexController.php file looks like:

<?php
class IndexController extends Zend_Controller_Action
{	
    public function indexAction() 
    {
		$this->view->headTitle('Page Title');
		
		$this->view->headLink()->appendStylesheet(PUBLIC_WEB_PATH . '/css/style.css');
		$this->view->headScript()->appendFile(PUBLIC_WEB_PATH . '/js/script.js');
    }
}//end class

It just put the head title and some custom CSS and JS for the page, the one that is not included in the layout. The index.phtml file looks like this:

<p>This is the main page.</p>

Simple, because it is just a blank project. In real web application, it could be your front page or your login page perhaps.

Error Trapping – The Error Controller

We also have our generic error page, thus >ErrorController.php and error.phtml. Below is the generic ErrorController.php:

<?php 

class ErrorController extends Zend_Controller_Action
{

    public function errorAction()
    {
        $errors = $this->_getParam('error_handler');
        
        switch ($errors->type) { 
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
        
                // 404 error -- controller or action not found
                $this->getResponse()->setHttpResponseCode(404);
                $this->view->message = 'Page not found';
                break;
            default:
                // application error 
                $this->getResponse()->setHttpResponseCode(500);
                $this->view->message = 'Application error';
                break;
        }
        
        $this->view->exception = $errors->exception;
        $this->view->request   = $errors->request;
        $this->view->env = APPLICATION_ENV;
    }
}

It is always safe to leave it as is. However, you may want to put more details or being nice to your user so you may add some other features. It is up to you. The error.phtml page looks like this:

<h1>An error occurred</h1> 
<h2><?php echo $this->message; ?></h2> 

<?php if ('development' == $this->env): ?> 
  
<h3>Exception information:</h3> 
<p> 
	<b>Message:</b> <?php echo $this->exception->getMessage(); ?> 
</p> 

<h3>Stack trace:</h3> 
<pre><?php echo $this->exception->getTraceAsString(); ?> 
</pre>

<h3>Request Parameters:</h3> 
<pre><?php var_dump($this->request->getParams()); ?> 
</pre>
<?php endif ?>

That’s it folks. I’m sorry if I can’t elaborate more on this. This blog assumes you know the basics otherwise, it will be only me who would understand what I’m talking about.

As always, the purpose of this blog is for my future reference so you can’t really rely much of the detailed explanation.

Until the next version of Zend Framework!

Leave a reply

Your email address will not be published. Required fields are marked *