Sunday, February 26, 2012

Zend Framework Tutorial

Introduction

I find myself constantly bombared with questions from students and co-workers I’ve introduced to the Zend Framework regarding how the different components can come together to form a basic application. I’ve searched, I have found, I have emailed great tutorials, but still the most common questions are posed “What’s should I include in index.php?”, “Should I use Zend_Db_Table?”, “And what about Zend_Form?”

The Scope of This Tutorial

What I’m proposing is an article that can get help you, the reader, get your hands wet by integrating the different components as seamlessly as possible. This article is not about the setup of the zend framework or how the various stand alone components work, but rather how they can be integrated. Neither does it look at the folder structure which is required, but where absolutely necessary I will make highlights. I’m going to assume that you have your .htaccess file in your webroot, index.php created, your include paths configured and Zend Framework installed. For the purpose of this tutorial i’ll be using MySQL and Zend Framework v1.5. This is a funcitonal tutorial which does not focus much on aesthetics. See Figure 1 for an illustration of my directory structure. Please note that Zend recommends that for PHP scripts the closing ?> can be omitted however for clarity in this tutorial I have included them.

The index.php File

Let’s take a lot at index.php, the bootstrap file for the application firstly.
//coder can also set include paths rather than modify php.ini
set_include_path(
get_include_path() . PATH_SEPARATOR .
‘./library’ . PATH_SEPARATOR .
‘./application’ . PATH_SEPARATOR .
‘./application/models’
);
require_once ‘Zend/Cache.php’;
require_once ‘Zend/Config/Ini.php’;
require_once ‘Zend/Controller/Front.php’;
require_once ‘Zend/Db.php’;
require_once ‘Zend/Db/Table.php’;
require_once ‘Zend/Layout.php’;
require_once ‘Zend/Registry.php’;
$config = new Zend_Config_Ini(‘./application/config.ini’, ‘production’);
//db config and connect
$params = array(
‘host’ => $config->database->host,
‘username’ => $config->database->username,
‘password’ => $config->database->password,
‘dbname’ => $config->database->name
);
$db = Zend_Db::factory($config->database->type, $params);
Zend_Db_Table::setDefaultAdapter($db);
//cache
$frontendOptions = array(‘lifetime’ => $config->cache->maxLifetime, ‘automatic_serialization’ => true);
$backendOptions = array(‘cache_dir’ => $config->cache->dir);
$cache = Zend_Cache::factory(‘Core’, ‘File’, $frontendOptions, $backendOptions);
//layout
$options = array(
‘layout’ => ‘default’,
‘layoutPath’ => ‘./application/layouts’,
‘contentKey’ => ‘content’,
);
Zend_Layout::startMvc($options);
//registry set
Zend_Registry::set(‘db’, $db);
Zend_Registry::set(‘cache’, $cache);
Zend_Registry::set(‘config’, $config);
//dispatch
$front = Zend_Controller_Front::getInstance();
$front->addModuleDirectory(‘application/modules’);
$front->dispatch();
?>
We can begin by setting the include path for our library, application and model files in the event we do not wish to make changes to php.ini. This enables us to require what I consider to be the core Zend Framework files from lines 10 to 16. Respectively files have been included which will enable us to perform caching, application wide configurations (handy for database password), routing of request in an Model View Controller environment, connecting to our choice of database, direct access to table information via the table data gateway pattern through inheritance, the ability to easy switch templates and the registry of variables in a manner the global keyword allows.
In line 18 the execution of the statement
$config = new Zend_Config_Ini(‘./application/config.ini’, ‘production’);
will open a connection to a configuration file config.ini which can be formatted by using the nested object property syntax. The second parameter to the constructor is the section of the configuration file which we wish to load, in this case production. The great thing is that because our test environment may be slightly different to production we can inherit and override the necessary variables (For a more detailed discussion on this see the Zend_Config_Ini documentation). So what does the content of our config.ini file look like? See Figure 2. After a successful instantiation what we’ll have $config, an object, loaded with the configuration items now as its properties. In light of this to access the name of the database the programmer will do so through $config->database->name. The depth of the configuration items is not limited to any number, however it will be simple if it remained shallow.
Lines 20 to 28 of index.php looks like this
//db config and connect
$params = array(
‘host’ => $config->database->host,
‘username’ => $config->database->username,
‘password’ => $config->database->password,
‘dbname’ => $config->database->name
);
$db = Zend_Db::factory($config->database->type, $params);
Zend_Db_Table::setDefaultAdapter($db);
A few important things are happening here. In line 21 an associative array of database configuration items have been created. The keys ‘host’, ‘username, ‘password’ and ‘dbname’ are required and will relate to the setting for the database server you have installed. These items were stored in the config.ini file located in the application directory. In Line 27 a factory method was used. The first parameter specifying the desired database type. The documentation for Zend_Db specifies the various adapters which can be used. The second is the database configuration array that was previously set up. For those interested a Zend_Config object can also be passed as a parameter to the factory method. See the documentation for more details.
Line 28 invokes a call to a static method belonging to Zend_Db_Table. To touch a bit on Zend_Db_Table, the class is an object-oriented interface to database tables. It provides methods for many common operations on tables. The base class is extensible, so you can add custom logic. The model which we will be writing later on will need to be aware of the database object being used to retrieve the necessary information. We can do this in various ways, however perharps the most suitable way is to set it in the bootstrap file. As a result we wouldn’t need to do this for various model classes later on.
From line 30 to 33 the cache is set up. The featured code is as follows:
//cache
$frontendOptions = array(‘lifetime’ => $config->cache->maxLifetime, ‘automatic_serialization’ => true);
$backendOptions = array(‘cache_dir’ => $config->cache->dir);
$cache = Zend_Cache::factory(‘Core’, ‘File’, $frontendOptions, $backendOptions);
The frontend and backend configuration items are set up such as the lifetime of each cached item and whether or not serialisation should be automatic (we may need to cache objects such as a list of countries so serialisation becomes important). For the backend configuration options because for the purpose of this tutorial caching is in files rather than against a database the cache directory is set up. One line 33 the cache object is created using the static factory method. There are other configuration options available. For a list see the ZF documentation.
For those interested in having various layouts and how it can be implemented in a MVC environment lines 35 to 42 will be of most interest. The programmer can create an associative array specifying the default layout to use, the path to the layout scripts and the content key. This content will be interpreted as what should be rendered when our view scripts are called. Pretty cool huh? This means that your view scripts will have nothing beside the code for the current page! Line 42 is where Zend_Layout is enabled with the configuration array in a MVC environ.
Zend_Registry is simply a container for storing objects and values in the application space. More than likely you’re going to want access to the database connection, cached object and configuration values later on so setting them in the bootstrap is a good idea. You can add any variable which qualifies using the static set method which takes two parameters, the key to store the variable under and the actual variable.
In a MVC environment the front controller handles the dispatching of work to the appropriate controller. This doesn’t just happen magically. Code is required and various naming conventions must be followed to ensure that this is properly done (we will see how later on in the tutorial). The static method getInstance is used to retrieve the front controller. The programmer must set the module directory as shown in line 49 and then dispatch the work like a traffic cop through a call to the dispatch method.

Actioning Requests

Let’s set up two files, make sure things are in order and then we’ll get to the discussion. Revisit Figure 1 for a second. In the modules directory, as depicted to the left of the illustration, create a folder and name it “default”. In “default” create three folders: one for your controllers, associated models and views. In the “views” folder create a folder named “scripts”. The norm is that in the “scripts” folder you will have folders for your views which relate to the name of the controllers nested in the module. Now that this file structure is set up go to the controllers directory. Create a file call IndexController.php and paste following code into it:
require_once ‘Zend/Controller/Action.php’;
class IndexController extends Zend_Controller_Action {
public function init() {
$this->initView();
}
public function preDispatch() {
}
public function postDispatch() {
}
public function indexAction() {
$this->render();
}
}?>
Now that’s over go to the views directory. Create a file called index.phtml and paste the following code into it:


Page body from view will be rendered. This corresponds to the content key that was set up in the
associative array with the key ‘content’.


Before you fire up your webserver and make a request go to the layouts directory in the application folder. Create a file called default.phtml and paste the following code:







Page header template here




layout()->content; ?>


Page footer template here





The IndexController

Now that’s over make a request. Your browser should display what’s in Figure 3. On the notion that it has, what’s happening is a few components are coming into play. With a focus on the IndexController according to the ZF documentation “Zend_Controller_Action is an abstract class you may use for implementing Action Controllers for use with the Front Controller when building a website based on the Model-View-Controller (MVC) pattern. To use Zend_Controller_Action, you will need to subclass it in your actual action controller classes (or subclass it to create your own base class for action controllers). The most basic operation is to subclass it, and create action methods that correspond to the various actions you wish the controller to handle for your site. Zend_Controller’s routing and dispatch handling will autodiscover any methods ending in ‘Action’ in your class as potential controller actions.”
Most likely you made a request to http://localhost. No module, controller or action was specified. This is where the default module comes in. If no element is specified then requests are sent by “default” to this module. To access the functionality it was necessary to require and inherit from the Zend_Controller_Action which provides us with a bunch of abstract methods and gives us the ability to write our own actions. As a rule we name our actions as methods postfixing it with the “Action” keyword. This class provides us with a few methods which we can hook code into. These are init, preDispatch and postDispatch on lines 5, 9 and 13 respectively. To us init is of interest where we lazy load the view public property which will later on aid in render a view based on the current request action as depicted in line 18. By default, for these elements to work in harmony the directory hierarchy must be adhered to. The hooks preDispatch and postDispatch come in handy when we want to do something before and after each and every action. For example we may wish to test a user against an access control list or the existence of a session for each action of a given module.

The View and Layout

Zend_Layout takes advantage of various extension points when used with the MVC components. Therefore when Zend_Layout::startMvc($options) was executed on line 42 of index.php a layout object was registered that could render the layout with content once dispatch was completed and furthermore an action helper was registered with the action controller we subclassed. The latter part allows us to change, or disable layouts from within the context of our controllers in the event we require something different for particular pages (naturally we would have other layout scripts).
The index.phtml page will contain the code we desire for a given page. In this case the homepage is merely a paragraph with some content.

Zend_Form

Let’s assume we want to create a form where users can register. Creating forms that can be easily validated is a breeze with Zend_Form. There are a lot of different approaches to how forms can be created, in terms of delegating functionality. Some people choose to create classes that subclass zend form. However I have found that once the number of forms I have grow the number of classes increase exponentially. Instead I choose to have a form class for each controller that includes the creation of forms through calls to static methods. This way I can easily include the functionality within the models folder for a given module, using naming conventions that are consistent with those of controllers and still in the end have a Zend_Form object returned for easy manipulation. Instead I postfix the name of the class with the word “Form”. Therefore in the models directory any forms I have relating to the IndexController will be found in the file IndexForm.php. So let’s create this class and then add code to the controller that will make rendering it possible.
require_once ‘Zend/Form.php’;
class IndexForm {
public static function registerForm() {
require_once ‘Zend/Form/Element/Text.php’;
require_once ‘Zend/Form/Element/Password.php’;
require_once ‘Zend/Form/Element/Radio.php’;
require_once ‘Zend/Form/Element/Submit.php’;
$form = new Zend_Form();
$form->setAction(‘/default/index/save’)
->setMethod(‘post’)
->setAttrib(‘class’, ‘editor’);
$email = new Zend_Form_Element_Text(‘email’);
$password = new Zend_Form_Element_Password(‘password’);
$gender = new Zend_Form_Element_Radio(‘gender’);
$email->addValidator(‘EmailAddress’)
->setRequired(true)
->addFilter(‘StringToLower’)
->setLabel(‘Email:’);
$password->setRequired(true)
->setLabel(‘Password:’);
$gender->setLabel(‘Gender:’)
->setSeparator(‘ ‘)
->addMultiOption(‘m’, ‘Male’)
->addMultiOption(‘f’, ‘Female’)
->removeDecorator(‘Errors’);
$form->addElement($email)
->addElement($password)
->addElement($gender)
->addElement(new Zend_Form_Element_Submit(‘Submit’));
return $form;
}
}
?>
?>
The next great thing about having these static methods is that we can require only the files we need for a given form. Personally I prefer to create my forms using code, maybe being from a Java background, so if you do let’s discuss the various elements. Zend_Form must be required. This class is the equivalent of a container onto which components can be added. The static method registerForm is my little convention insomuch that it corresponds with the name of the action. This is not absolutely necessary. From lines 8 to 17 I require the files representing XHTML elements of interest to the form. In our case, text boxes, password fields, radio buttons and the submit buttons. Line 15 to 17 sets up basic properties of interest to any form. As a parameter of setAction the module, controller, action we will like to post to is specified. Because these methods utilize the fluent interface (meaning at the end of the method call ‘this’ object is returned we can use the syntatical structure. Sometimes we may wish to set other attributes of the form. For this, the setAttrib method can be used which takes two parameters: the name of the attribute and the value we would like to set it to. Line 19 to 21 creates elements of the given type. The name we pass to the constructor will be interpreted as the name of the element when the XHTML form is rendered.
Filters and Validators can be added to these elements that we have created. There are various ways of adding validators. The qualifying name of the validator can be specified as a string parameter to the addValidator method. The same goes for filters. Another way would be to pass a reference to an named or anonymous instance of the class. For the email element, the email address validator is added. Similarly to Zend_Form these elements implement the fluent interface and enabling the shorthand access to the methods. To set the display label of the element the setLabel method is called. The programmer also has the option to specify whether or not the given field is required through passing of a boolean value to the method setRequired.
The means of setting validators and filters is consistent among elements. The only additionaly item I would like to highlight is for the cases of multioption elements such as the radio button. By default each radio button will be produced on a new line along with the label when the form is rendered. If you wish, you can modify this be making a call to setSeparator as illustrated on line 32. In the end it is best to return this form object. This comes in handy when we wish to set the values of an already created form as an example consider populating an edit form of a person. The setValue method can be used to accomplish just this.
Let us now create an action that can render this form.
public function registerAction() {
require_once ‘./application/modules/default/models/IndexForm.php’;
$this->view->form = IndexForm::registerForm();
$this->render();
}
I’m assuming you copied the action onto line 21. On line 23 the static method is called, creating and returning the form for storage in the already initialized view object. It will be necessary to create a register.phtml script. Once that is done paste the single line of code into the page
form?>
A request to /default/index/register should yield the results shown in Figure 4. If you’ve been successful so far you can view source to see what the form looks like. For ease i’ve listed include some CSS code that can be used to structure the form how “we know it”. I’m assuming that you will copy the code into a default.css file located in the css folder contained in the webroot.

CSS Code

body, li, td, input, select, p {
font-family: Verdana;
font-size: 11px;
line-height: 15px;
}
form {
margin: 0px;
padding: 0px;
}
form.editor dt, dd {
padding: 3px;
}
form.editor dt {
float: left;
width: 150px;
text-align: right;
padding-right: 0px;
}
form.editor dt label{
font-weight: bold;
}
form.editor dd {
margin-left: 158px;
}
form.editor .errors {
list-style: none;
padding: 0px;
/*background: #fff7d7;*/
padding: 5px;
margin: 2px;
}
form.editor .errors li {
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 11px;
color: #ff0000;
text-align: left;
}
Include the following line into register.phtml and refresh:

Good to go? I hope so!

Validating Our Form

When the page is refreshed you should see the results denoted in Figure 5. You will recall on line 17 of the class IndexForm we specified the class attribute. Line 15 of this same class dictates that a post will be to the save action of the default controller. This is where validation and database access becomes important so we’ll tackle the two areas sequentially. Create a save action in the index controller of the default module. This time before I show the code let’s discuss the sequence of events.
1. We have to get back the Zend_Form object through a call to the same method that created the form.
2. We have to test the form to see if it is valid against the superglobal $_POST.
3. If the form is not valid we have to store the form in the view object and render the register script.
4. If the form is valid we have to proceed to save the values in the database.
We’ll extend saveAction as we go along. Let’s cater for steps 1 to 3 first of all:
public function saveAction() {
require_once ‘./application/modules/default/models/IndexForm.php’;
$form = IndexForm::registerForm();
//handle if form is invalid
if (!$form->isValid($_POST)) {
$this->view->form = $form;
$this->render(‘register’);
return;
}
}
Firstly we require and get the form object. Zend_Form has a method isValid that takes an associative array. If the form is invalid when checked against the validators we specified in IndexForm the method returns false. In this case we can simply register form with the view object and render the register.phtml script. We must return the method thereafter.
Catering for step 4 means we require a model file that can do the work easily for us. Let’s take a look at Zend_Db_Table.

Zend_DB_Table

Before zend_Db_Table I found myself wearied by creating model classes complete with getters and setters for each relation I had in my database and fetchList and fetchRow methods which not only accessed some database connection, but also created and populated objects. This would become messier and messier each time I had an object composed of another, because with OOP there is no foreign key, but rather a collection of objects. I was confused as to whether or not I should automatically load up the next object and eventually went as far as having a lazy load parameter. So to me Zend_Db_Table is a blessing not in disguise, but simply a blessing. I now have the ability to subclass this beauty of a class, specify the name of the table the class should map to, its primary key and provide a database object which was already done through a call to the static method Zend_Db_Table::setDefaultAdapter($db); on line 28 of index.php. So now that’s done and doesn’t need to happen again let’s create a database table in our zend database and a corresponding class. The code for the sql is as follows below.
CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL auto_increment,
`email` varchar(100) NOT NULL,
`password` varchar(32) NOT NULL,
`gender` char(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
In the models directory create a file User.php and paste the following code:
class User extends Zend_Db_Table_Abstract {
protected $_name = ‘user’;
protected $_primary = ‘id’;
}
?>
There are more options that you can set up such as a reference map between classes and database entities, which can make fetching parent rows and dependent rowsets easier. For the purpose of this tutorial I wouldn’t go so far. We’ll incorporate the use of the User class to save a record to the database.

Back To The Controller

Assuming our validation went okay, we have to cater for the form that must be saved. Let’s look at the modified saveAction and then discuss the elements (I’m assuming that this code is pasted into your editor from page 28 of the IndexController therefore line numbers in such manner:
public function saveAction() {
require_once ‘./application/modules/default/models/IndexForm.php’;
$form = IndexForm::registerForm();
//handle if form is invalid
if (!$form->isValid($_POST)) {
$this->view->form = $form;
$this->render(‘register’);
return;
}
//get form values
$v = $form->getValues();
//include model
require_once ‘models/User.php’;
$user = new User();
$data = array(
‘email’ => $v['email'],
‘password’ => $v['password'],
‘gender’ => $v['gender']
);
$user->insert($data);
$this->render();
}
If the boolean expression on line 33 returned true, control would have jumped to line 40 where the filtered values of the form would have been retrieved using the method getValues. To prevent hacking sql together for the insert the programmer can
create an associative array which maps to the database field and through an instantiated User object make a call to the insert method passing the array as a parameter as shown on line 51. What joy! The insert method handles quoting of any data in the array therefore this doesn’t need to be explicitly done. Thereafter a call to the render method will display a script congratulating the user on their registration. As a small test create a such phtml script. Remember the rules and conventions!
It will be a good idea in such actions as the one above to test whether the request was indeed a post. This can be done via a call to the isPost method of the request object and is accessible through the action controller through $this->getRequest(). Therefore testing whether something is a post can be done as follows if (!$this->getRequest()->isPost()). If the expression results in false the programmer can forward the script to a preferred location.

Back to Zend_DB_Table

Let’s add a list action to IndexController.php. I’m assuming that the below code is pasted starting from line 55 of IndexController.php:
public function listAction() {
//include model
require_once ‘models/User.php’;
$user = new User();
$userInfo = $user->info();
//print_r($userInfo); exit;
$this->view->rsUserList = $user->fetchAll(null, ‘email’);
$this->render();
}
The model is included and an object created. While line 59 is of no consequence to this particular action it demonstrates a method that is crucial to development. The info method returns an array of information specific to the database table to which it has been mapped. That means you can access the name of the database table (handy if you can’t decide on a standard for naming tables in the office) and mappings of database fields to columns (just ensure that the fields never change position if you choose to use an index to access fields rather than their names). Pressing on, line 62 makes a call to an instance method fetchAll. All parameters of this method are optional. The first can be a string which specifies a where clause. The second is the field which you wish to sort the table by. This can be a comma separated list postfixed by “asc” or “desc” depending on the order you wish to sort by. It is important to note that a list of objects are returned from this method therefore fields are accessed using the -> notation. For programmers wishing an array of results a subsequent call can be made to the toArray() method. For example:
$user->fetchAll()->toArray();
The list is registered with the view object so it can be accessed from list.phtml, for which the code is pasted below:
if (count($this->rsUserList) > 0) {
echo ‘
’;
echo ‘’;
echo ‘’;
echo ‘’;
echo ‘’;
foreach ($this->rsUserList as $rsURow) {
echo ‘
’;
echo ‘’;
echo ‘’;
echo ‘’;
}
echo ‘
EmailGender
’.$rsURow->email.’’.strtoupper($rsURow->gender).’
’;
}
else {
echo ‘No records found
’;
}
?>
You may be wondering why in our phtml scripts our registered variables aren’t being accessed as $this->view->rsUserList. It’s because the script is being rendered within the context of the default view object therefore elements must be accessed via the this keyword. The script is straightforward at this point performing a count on the number of records returned. If it is greater than zero then the list of objects are traversed. The name of the instance variables will map to the name of the fields in the database making this approach ideal for data driven applications. Figure 6 shows what the formatted results would look like.

Putting Zend_Registry and Zend_Cache Into Perspective

Take a look at the following action which I want you to paste starting from line 66 of the IndexController:
public function cachelistAction() {
$db = Zend_Registry::get(‘db’);
$cache = Zend_Registry::get(‘cache’);
require_once ‘models/User.php’;
$user = new User();
$userInfo = $user->info();
$tblUser = $userInfo['name'];
if (!$result = $cache->load(‘userlist’)) {
$select = $db->select();
$select->from($tblUser);
$result = $db->fetchAll($select);
$cache->save($result, ‘userlist’);
}
$this->view->userList = $result;
$this->render();
}
On line 67 and 68 we reclaim our global variables which we registered with Zend_Registry. To return the values simply specify the key as a parameter to the get method. By now line 70 to 73 should be familiar to us. According to the Zend Framework documentation
“There are three key concepts in Zend_Cache. One is the unique indentifier (a string) that is used to identify cache records. The second one is the ‘lifetime’ directive as seen in the examples; it defines for how long the cached resource is considered ‘fresh’. The third key concept is conditional execution so that parts of your code can be skipped entirely, boosting performance.”
In our case on line 75 this is the “userlist” which we attempt to load. If the cache does not have such a record, the variable $result will be null and the code within the if statement executed. The lifetime directive was already specified on line 31 of index.php. In our example a registered user list will not be fresh after every registration so a lifespan of 2 hours may be unrealistic. This is merely for the purpose of demonstration. Therefore line 76 to 79 will include code that can get and cache the user list. Another feature of Zend_Db is demonstrated. The database object can be used to create a select object specific to the adapter specified in the beginning. This select object is ideal when forming SQL statements based on logic for example joining to or ordering tables based on some sort parameter specified in a get request. Notice that on line 77 the literal name of the table is not used but rather the info derived from the user class. Through a call to fetchAll which can take a sql string or select object, the results are returned and then cached using the save method. At this point the key is specified and data. Remember we configured the cache to automatically serialise our results so it’s okay even though an array of results will be returned. Results are also automatically unserialized. You can follow the code in list.phtml and attempt to write a view script for cachelist.phtml. It’s very easy!

Conclusion

I hope this to be a neat introduction to those out there who may wish to set up an application which maximizes the use of what I would call the Zend Framework “base components”. Utilizing the few classes I have discussed in this tutorial you can create sophisticated applications in 1/4 of the time you may be doing so in right now (I speak from experience). As always feel free to comment or email me. I’ll be more than happy to contribute. Click here to download my source files

3 comments:

  1. It is very clear and understandable the value material.I always choose the example.you offer a get codding in which i will use in my individual venture.thank you.

    ReplyDelete
  2. I just want to appreciate for this tutorial which is really helpful especially for those who are searching this type of information for a while including me. Thanks and keep it up!

    ReplyDelete