Archive for the ‘Zend Framework’ Category

Zend Framework Tutorial Part 2 ( UI Components with Site Layout, Site Navigations and Forms )

Tuesday, April 8th, 2008

In this part of our tutorial we will be building our application's front end ( default module ), constructing the links, creating and validating a form and using FlashMessenger to form errors.

Let's take a look at some of the pages in our application.

ZF Tutorial Part2 - Home

ZF Tutorial Part2 - Login

“Home” and “About Site” just displays a static page and thus we haven't included “About Site” page in our screenshots. The other two, “Register” and “Login”, includes a form for the user to fill-up and for us to validate.

Our directory structure for our default module will be like the screenshot below.

ZF Tutorial Part2 - Directory Structure

The uniformity of our pages look was done through our use of Zend_Layout, one of the most useful feature, in building a web application.

In our previous post, we declared in pur bootstrap file that we will be using MVC for our aplication and defined the directory our template files. So, Let's take a look at the important part our template file ( templates/front.phtml ), the full source code of our working applicatio can be downloaded at the end of our tutorial.

1.) The code below creates the title tag in our html part ( including the title tag itself ). The prepend method tells that we will insert the string “ZF Connections” in or every page and adds a separator of “::”. We could see the effect of this on the screenshots displayed above.

 
$this->headTitle()->prepend('ZF Connections');
$this->headTitle()->setSeparator(' :: ');
echo $this->headTitle();
 

2.) The next code below includes the css files in our application. It will insert all of the necessary attributes for a valid inclusion of css file.

 
echo $this->headLink()
->appendStylesheet($this->baseUrl . '/styles/blueprint/screen.css', 'screen')
->appendStylesheet($this->baseUrl . '/styles/blueprint/print.css', 'print')
->appendStylesheet($this->baseUrl . '/styles/zf.css', 'screen')
 

3.) The folowing just includes the necessary files located at the same directory as our template file.

 
<?php echo $this->partial('front-top-lang.phtml') ?>
<?php echo $this->partial('front-header.phtml') ?>
<?php echo $this->partial('front-footer.phtml') ?>
 

4.) The following is a little bit different than the previous one, for it wil loop the content of the parameter passed.

 
<?php echo $this->partialLoop('front-navigation.phtml', $this->urls) ?>
 
// font-navigation.phtml contains the following
<a href="<?php echo $this->url ?>"><?php echo $this->name ?></a> |
 
// and urls is declared in the basecontroller as
private function getLinks() {
$url = $this->getHelper('Url');
return array(
array('name' => 'Home',
'url' => $url->url(array('controller' => ''))),
array('name' => 'About Site',
'url' => $url->url(array('controller' => 'about-site'))),
array('name' => 'Register',
'url' => $url->url(array('controller' => 'register'))),
array('name' => 'Login',
'url' => $url->url(array('controller' => 'login')))
);
}
 
//and set on the init method
$this->view->urls = $this->getLinks();
 

5.) The most important and the simplest part of our layout will be displaying the actual content of the controller called.

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

We have seen the layout.phtml and now its time to delve into our controlelr classes.

We will discuss the abstract BaseController class upon which all of our controllers on our default module extends upon. BaseController does the work of initializing our FlashMessenger, setting the baseUrl on the view, setting the the layout and links, and other methods.

We have three protected methods on our BaseController class, 1.) validateForm which accepts a Zend_Form instance as its first parameter and the array of data to validate upon, 2.) redirectToController which accepts a controller name and an optional action name as its second parameter, and 3.) addSucessMessage which takes a stirng parameter as message and store it in our flashMessenger.

We have also another helper controller namely the FlashMessagesController. It's only job is to fetch all the messages stored on our FlashMessenger and display it as well as parse upon which kind of message is stored ( success, notice, or error ). We will be including this action through one of view's helper, namely action helper ( nice name ).

Its only method, indexAction, contains the following code:

 
if($this->_flashMessenger->hasMessages()) {
$display_messages = array();
$messages = $this->_flashMessenger->getMessages();
foreach($messages as $message) {
if(substr($message, 0, 4) == 'type') {
$type = explode('=', $message, 2);
$this->view->messages_type = $type[1];
} else {
$display_messages[] = $message;
}
}
$this->view->messages      = $display_messages;
}
 
 
// and this will be the phtml file for displaying our message
<?php if($this->messages && is_array($this->messages)): ?>
<div class="<?php echo $this->messages_type ?>" style="width:350px" align="left">
<?php if(isset($this->messages[1])): ?>
<?php echo $this->formErrors($this->messages) ?>
<?php else: ?>
<?php echo $this->messages[0] ?>
<?php endif; ?>
</div>
 
<?php endif; ?>
 

Our IndexController and AboutSiteController are pretty straightforward and just displays their properly configured view file. We have configured these controller files to load the view file in a separate directory, instead of their default ones. The code looks like the following:

 
// IndexController ( indexAction )
$this->render('home', null, true);
 
// AboutSiteController ( indexAction )
$this->render('about-site', null, true);
 

This line of code tells us that we will load the about-site.phtml in top directory of views folder, meaning not under any controller specific subdirectory ( the true argument on render particulary says that the view does not have a controller ). The second argument (null ), simply tells that we will be including all the segment of the response object.

Now we'll have to take a look at our RegisterController. RegisterController does some other things beside displaying the register page. In this controller, we will creae a Zend_Form to be included in the view area, validates our form after submission and displays messages based on the result of validation ( success or error ).

We first take a look at the creation of form defined on the getForm() method on our RegisterController.

 
$form = new Zend_Form();
$form->setAction($this->_request->getBaseUrl() . '/'
. $this->_request->getControllerName() . '/signup')
->setMethod('post');
 
$email = $form->createElement('text', 'email', array('class' => 'text'));
$email->addValidator('EmailAddress')
->setRequired(true)
->addFilter('StringToLower')
->addFilter('StringTrim')
->setLabel('Email Address');
 
$submit = $form->createElement('button', ' Submit ', array('type' => 'submit', 'class' => 'button'));
 
$form->addElement($email)
->addElement($submit);
 
return $form;
 

Creating of Zend_Form is pretty straightforwad, the trivial part is the creation of its action. We didn't hard-code the action of our form so that our form will be more flexible and we won't be changing any the form's properties.

Our email element was created through the form's createElement method. We have created a input element with type of text, with name of email and a class of text.
We have added a validator for the element, EmailAddress, or we could have written the validation just like the following instead:
$email->addValidator(new Zend_Validate_EmailAddress());

And we have added some filters, StringToLower and StringTrim, which are quite straightforward. And lastly we have added a label to the element.

The next element on our form is the submit button. we have used button element with type of submit.

After the two elements were created, we have added the two elements back on the form for display.

Our signupAction which do the validation of our form is as simple as the following code:

 
if(!$this->_request->isPost()) {
return $this->redirectToController('register');
}
 
if(!$this->validateForm($this->getForm(), $_POST)) {
return $this->redirectToController('register');
}
 
$message  = '
 
Thank you for joining ZF Connections
 
';
$message .= '
 
Your login credentials will be submitted in the email that you have provided.
 
';
$this->addSuccessMessage($message);
return $this->redirectToController('register');
 

First we, checked is submitted via post method. The next checking is actually checking of forms for theie errors. If the form does not have any errors we just display a message ( for now ). All of the following cases will end up redirecting to the “register” controller.

The validateForm method on our BaseController is defined as follows:

 
protected function validateForm(Zend_Form $form, array $data) {
if(!$form->isValid($data)) {
foreach($form->getMessages() as $field => $message) {
foreach($message as $error) {
$this->_flashMessenger->addMessage(strtoupper($field) . ' ' . $error);
}
}
$this->_flashMessenger->addMessage('type=error');
return false;
}
return true;
}
 

The method checks the form and add any errors generated and add it to our flashMessenger.

LoginController is almost the same as RegisterController, the only difference is that LoginController has an additional element on its form which is the password element. You can download our working application below and take a look on how we have glued together our application.

Our next topic will be creating a Model for database interaction and finishing up our registration and login. We will be using more ZF classese like Zend_Auth, Zend_Acl, Zend_Log and other classes we may need as we code.

See you next time and happy coding! =)
Download Files Here ( Zend Framework is not included in the zip file dure to file limitation ):
ZF Tutorial Part2 - Files

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

Friday, April 4th, 2008

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

Learning Zend Framework

Thursday, April 3rd, 2008

For the next few days, I would be writing a guide in using the Zend Framework in creating a simple Social Networking Site.
I will be blogging about how to create the site from scratch and help you and myself in learning the Zend Framework and creating a project is the best way to do it. By the way, we'll be doing the project the agile way! =)

The project will be called ZF Connections, an online community with user profiles, messaging, posting of ads and blogs.

Next post, we will be discussing our directory structure, and how we could separate our modules in their own directories and our bootstrap file.