Zend Framework Tutorial Part 1 (Directory Structure and Bootstrap File)

Note: This tutorial is intended for intermediate PHP users.

Our project for this tutorial, ZF Connections (a social networking site), will be designed in such a way that adding a functionality will be as easy as possible by extending it using a module. This tutorial assumes the reader of familiarity of PHP5 and OOP terminologies.

Our application will be divided into three main modules, admin module, members module, and the default/application module. The first module (admin module) will be used for administrative tasks on our application, the members module will focus more on the members/users of the application and the third one, default/application module, will be focusing on the overall or general part of our application.

Now that we have the basic requirement for our application, let's begin to dig a little deeper. For our admin module, all modules installed must be accessible only through the following url: http://localhost/admin//, as well as the members module must only be accessible in http://localhost/members//. The default/application modules must be accessed regularly as http://localhost//. The problem here is that we must have a directory structure upon which adding a directory under some “modules” directory will automatically create the route/url for us and its controllers and models will be properly included and initialized.

The directory structure I have come up with was like this:

-- [root]
---- _tests
---- application
------ admin
------ default
------ members
------ modules
-------- default
-------- members
-------- admin
------ templates
---- html
---- library
---- resources

Our directory structure has five top-level directories, but only four will be deployed ( _tests, application, html, library, resources ).

_tests directory will contain all of our test cases

application directory will contain our modules (default modules and sub-modules)

html directory will contain all files visible to the public ( images, js, css, view files )

library directory will contain all of the classes that we will be using including the Zend Framework, and other classes as we go along into our tutorial.

resources directory will contain all of our configuration files.

Let's take another look at our application directory.
-- application
---- admin
---- default
---- members
---- modules
------ admin
------ default
------ members
---- templates

The three subdirectories ( admin, defualt, members ) will be our default or predefined modules in our application, “default” is a predefined module in Zend Framework.
The other directory, modules, on which there are three subdirectories on which the three predefined modules are named alike plays a significant role in our application design. Every modules defined on one of the subfolders will be automatically included as a submodule of our predefined module. Consider the follwing module example module placed under modules/members

---- modules
------ members
-------- message
---------- controllers
---------- models
---------- views

The message module upon placing it under the members module will automatically qualify it as a submodule of members module. Whew! The message module can now be accessed through the following url: http://localhost/members/message/ .
Every modules placed under the subdirectories of our predefined module names ( as well as other module names ) under modules directory will also be rerouted correctly.

Placing other inside other module name directory beside our predefined subdirectory is also possible. Consider the following example.

---- modules
------ others
-------- other
---------- controllers
---------- models
---------- views

The follwing module can be accessed through the url:
http://localhost/others/other

The magic(logic) behind accessing and plugging module is in our bootstrap file.
It's time we take a look at our bootstrap file and dissect it part by part later. We will not be talking the classes involved in detail, we will only discuss it in depth as soon as we will be using it extensively or in a major part of the application.

This is our index.php file.

 
<?php
/**
 * Zend Framework Tutorial
 *
 * @author Ronald de Leon
 * @version 1.0
 */
 
error_reporting(E_ALL|E_STRICT);
date_default_timezone_set('Asia/Manila');
 
define('SYSTEM_PATH', dirname(__FILE__));
define('APPLICATION_PATH', dirname(SYSTEM_PATH) . DIRECTORY_SEPARATOR . 'application');
define('RESOURCE_PATH', dirname(SYSTEM_PATH) . DIRECTORY_SEPARATOR . 'resources');
 
// set include path to include files in our library directory on which the
// zend framework is located
set_include_path(get_include_path() . PATH_SEPARATOR
				  . dirname(SYSTEM_PATH) . DIRECTORY_SEPARATOR . 'library'
				);
 
// load Zend_Loader class and make it automatically auto load all of our classes,
// no more long includes/requires and Zend_Loader::loadClass
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();
 
// // setup database config file by loading configuration through our ini file located at resource directory
$dbConfig = new Zend_Config_Ini(RESOURCE_PATH . DIRECTORY_SEPARATOR . 'database-config.ini', 'staging');
 
// setup database resource through our config file
$db = Zend_Db::factory($dbConfig->database);
 
// set db to be used as default default adapter in all of our application models
Zend_Db_Table_Abstract::setDefaultAdapter($db);
 
// set-up layout options to be used for our project
$layoutOptions = array(
	'layoutPath' => APPLICATION_PATH . DIRECTORY_SEPARATOR . 'templates', // our template directory
	'layout'	 => 'front' // our default layout
);
// set layout configuration to Zend_Layout class
Zend_Layout::startMvc($layoutOptions);
 
// define default modules directory
$defaultModules = array(
	'default' => APPLICATION_PATH . DIRECTORY_SEPARATOR . 'default',
	'members' => APPLICATION_PATH . DIRECTORY_SEPARATOR . 'members',
	'admin'   => APPLICATION_PATH . DIRECTORY_SEPARATOR . 'admin'
);
 
// get front controller instance
$controller = Zend_Controller_Front::getInstance();
 
// get router from front controller
$router = $controller->getRouter();
 
// iterate default modules
{
	foreach($defaultModules as $key => $module) {
 
		// add default controller directories
		$controller->addControllerDirectory($module . DIRECTORY_SEPARATOR . 'controllers', $key);
 
		// add models directory to include path
		set_include_path(get_include_path() . PATH_SEPARATOR . $module . DIRECTORY_SEPARATOR . 'models');
 
	}
}
 
$controller->throwExceptions(true); // should be turned on in development time
 
// define modules directory
$moduleFolder = APPLICATION_PATH  . DIRECTORY_SEPARATOR . 'modules';
 
// this is where the code that makes our application extendable through modules
{
	$modules = scandir($moduleFolder); //scan module folder for saved modules (top level modules will be scanned {application, members})
	foreach ($modules as $module) {
 
		if($module[0] == '.') continue; // if search directory starts with a dot, skip directory
 
		$subModulePath = $moduleFolder . DIRECTORY_SEPARATOR . $module; // this will be the submodules directory directly under parent module. e.g. application
		if(is_dir($subModulePath)) {
 
			$subModules = scandir($subModulePath); // scan the submodules directory for specific modules defined
 
			foreach($subModules as $singleModule) { 
 
				if($singleModule[0] == '.') continue; // if search directory starts with a dot, skip directory
 
				$modulePath = $subModulePath . DIRECTORY_SEPARATOR . $singleModule; // this will be the actual module or specific module found
				if(is_dir($modulePath)) {
 
					// add to include path, models directory
					set_include_path(get_include_path() . PATH_SEPARATOR
							. $modulePath . DIRECTORY_SEPARATOR . 'models');
 
					// add controllers to controller directory
					$controller->addControllerDirectory($modulePath . DIRECTORY_SEPARATOR . 'controllers', $singleModule);	
 
					// add to router
					// if module is under application set router to default directory
					if($module == 'default') {
						$router->addRoute('', new Zend_Controller_Router_Route($singleModule));
					} else {
						// if module is under not a member of default module set router to /${module}/{singleModule}
						$router->addRoute($module . '/' . $singleModule, new Zend_Controller_Router_Route($module . '/' . $singleModule,
							array(
								'module'	 => $singleModule,
								'controller' => 'index'
							)
						));
					}
 
				} // end checking {is_dir($modulePath)} 
 
			} // end loop of submodules directory
 
		} // end checking {is_dir($subModulePath)}
 
	} // end looping of modules directory
 
} // end group bracket
 
// run application
$controller->dispatch();
?>
 

The first two lines of code in our framework will be setting some important configuration. Setting error reporting to ALL or STRICT, and setting default timezone to Manila, Philippines.

The next lines defines some paths to be used in accessing certain files in our layout such as the APPLICATION_PATH and RESOURCE_PATH.

The following line loads Zend_Loader and tells the class to automatically load all defined class.

require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

The next lines of code setup our database connection by specifying and loading the configuration thru a database-config.ini file located in our top-level resource directory and loaded it through Zend_Config_Ini to be automatically parse our ini file. The 'staging' part is a way to tell Zend_Config_Ini that we will be using the 'staging' part in the config file. We chose to use Zend_Config_Ini instead of Zend_Config_Xml because the codes are easier to read in an ini file than in an xml file.

We get the db adapter or resource from Zend_Db class by passing our config object. The last part tells that we should always or our db adapter defined here will be used if no adapter is specified. Our DB adapter is of type Zend_Db_Adapter_Pdo_Mysql, it automatically detects to load the particular class by parsing our ini file.

// // setup database config file by loading configuration through our ini file located at resource directory
$dbConfig = new Zend_Config_Ini(RESOURCE_PATH . DIRECTORY_SEPARATOR . 'database-config.ini', 'staging');

// setup database resource through our config file
$db = Zend_Db::factory($dbConfig->database);

// set db to be used as default default adapter in all of our application models
Zend_Db_Table_Abstract::setDefaultAdapter($db);

// filename: database-config.ini
// our database configuration
; Production site configuration data
[production]
database.adapter = pdo_mysql
database.params.host = dbhost
database.params.username = dbuser
database.params.password = dbpassword
database.params.dbname = dbname

; Staging site configuration data inherits from production and
; overrides values as necessary
[staging : production]
database.params.host = localhost
database.params.username = root
database.params.password =
database.params.dbname = zfconnections_development
database.params.profiler = true

The following defines our layout options and set the options to our Zend_Layout class. Upon callinf the startMvc method, our application assumes that we will be using layouts for our application on which the layouts wil be located on the paths specified in our 'layoutPath' and our default layout if none specified will be 'front'.

// set-up layout options to be used for our project
$layoutOptions = array(
'layoutPath' => APPLICATION_PATH . DIRECTORY_SEPARATOR . 'templates', // our template directory
'layout' => 'front' // our default layout
);
// set layout configuration to Zend_Layout class
Zend_Layout::startMvc($layoutOptions);

In the codes below, we define our default modules in an array so that we could iterate overt it at a later time. Instantiate our fromt controller by calling its static method getInstance(), retrieve the router object from our front controller and iterate our default module directory so that its 'controllers' directory will be added in our front controller's controller directory and our models directory will be added in our include_path.

// define default modules directory
$defaultModules = array(
'default' => APPLICATION_PATH . DIRECTORY_SEPARATOR . 'default',
'members' => APPLICATION_PATH . DIRECTORY_SEPARATOR . 'members',
'admin' => APPLICATION_PATH . DIRECTORY_SEPARATOR . 'admin'
);

// get front controller instance
$controller = Zend_Controller_Front::getInstance();

// get router from front controller
$router = $controller->getRouter();

// iterate default modules
{
foreach($defaultModules as $key => $module) {

// add default controller directories
$controller->addControllerDirectory($module . DIRECTORY_SEPARATOR . 'controllers', $key);

// add models directory to include path
set_include_path(get_include_path() . PATH_SEPARATOR . $module . DIRECTORY_SEPARATOR . 'models');

}
}

The code below tells that we will be throwing some exceptions.
And it SHOULD be turned off on our live site.

$controller->throwExceptions(true); // should be turned on in development time

The last code is lies the nitty gritty details of our application's secret to its extensible modular design.
The first part tells us where the modules are located.

// define modules directory
$moduleFolder = APPLICATION_PATH . DIRECTORY_SEPARATOR . 'modules';

this is the part where we scan the modules directory for any directories under it, except if it starts with a dot (.)

The comments on the codes will be enough to tell what the application is doing.

// this is where the code that makes our application extendable through modules
{
$modules = scandir($moduleFolder); //scan module folder for saved modules (top level modules will be scanned {application, members})
foreach ($modules as $module) {

if($module[0] == '.') continue; // if search directory starts with a dot, skip directory

$subModulePath = $moduleFolder . DIRECTORY_SEPARATOR . $module; // this will be the submodules directory directly under parent module. e.g. application
if(is_dir($subModulePath)) {

$subModules = scandir($subModulePath); // scan the submodules directory for specific modules defined

foreach($subModules as $singleModule) {

if($singleModule[0] == '.') continue; // if search directory starts with a dot, skip directory

$modulePath = $subModulePath . DIRECTORY_SEPARATOR . $singleModule; // this will be the actual module or specific module found
if(is_dir($modulePath)) {

// add to include path, models directory
set_include_path(get_include_path() . PATH_SEPARATOR
. $modulePath . DIRECTORY_SEPARATOR . 'models');

// add controllers to controller directory
$controller->addControllerDirectory($modulePath . DIRECTORY_SEPARATOR . 'controllers', $singleModule);

// add to router
// if module is under application set router to default directory
if($module == 'default') {
$router->addRoute('', new Zend_Controller_Router_Route($singleModule));
} else {
// if module is under not a member of default module set router to /${module}/{singleModule}
$router->addRoute($module . '/' . $singleModule, new Zend_Controller_Router_Route($module . '/' . $singleModule,
array(
'module' => $singleModule,
'controller' => 'index'
)
));
}

} // end checking {is_dir($modulePath)}

} // end loop of submodules directory

} // end checking {is_dir($subModulePath)}

} // end looping of modules directory

} // end group bracket

// run application
$controller->dispatch();

That's it! Now we have a base design for our application. Next topic we will be discussing on ZF Core components, particularly the MVC components. We will be building the front-end of our application, No database access yet, but we have already defined here our database configuration so theres not much of a problem when we will be using some database access. Our front end development of the application will be focusing on the Zend_Action, Zend_Layout, Zend_View and the our Zend_Form as well on the Request and probably the Response object.

See yah! :-p

5 Responses to “Zend Framework Tutorial Part 1 (Directory Structure and Bootstrap File)”

  1. Marc Veeneman Says:

    In index.php you use dirname(SYSTEM_PATH) incorrectly. Each instance of dirname(SYSTEM_PATH) should be changed to SYSTEM_PATH.

    Thank you very much for this. It’s a big help to see how multiple modules can be handled.

  2. Ronald de Leon Says:

    thanks for the info.. i’ll try to fixed the code, but nonetheless, it still works.. :D

  3. Durus Says:

    Hi
    Thanks for showing how submodules works. I have one question. If i have a module named blog and a submodule named admin in blog.
    What should i name the controller class ?
    I have tried with
    class Admin_Blog_IndexController extends Zend_Controller_Action
    class Blog_Admin_IndexController extends Zend_Controller_Action
    but it does not work.

  4. Tomiwa Adefokun Says:

    This is very exciting, maybe in latter lectures, I’ll understand why we have the modules directory that duplicates all the modules… I’ll appreciate it if the full lecture can be mailed to me at:

  5. Tomiwa Adefokun Says:

    Thanks for this piece of work. I’ll appreciate it if the full tutorial can be mailed to me, at tomiwa.adefokun@profenicscorp.com

    Thanks.

Leave a Reply