Showing posts with label Zend framework. Show all posts
Showing posts with label Zend framework. Show all posts

Thursday, July 14, 2011

zend framework _forward utility method

From the official document:"_forward($action, $controller = null, $module = null, array $params = null): perform another action. If called in preDispatch(), the currently requested action will be skipped in favor of the new one. Otherwise, after the current action is processed, the action requested in _forward() will be executed."

I mention this method here because it seems many people haven't noticed the last sentence: " Otherwise, after the current action is processed, the action requested in _forward() will be executed". And i have seen several times people write codes like this in action methods:

if ($condition) {
                $this->_forward($action);
}
//still do some tasks here
$this->doSomething();

It is easy to assume that once we need to forward a request, the rest of the code won't get executed. Unfortunately, this is not necessarily true. _forward() is different from _redirect(). So it is better that you design your code carefully to ensure that once you need to forward the request, the rest of the code won't get executed.

Monday, March 16, 2009

Zend framework source code learning - Zend_Loader

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

The above two lines can save us from those long reqire_once list on top of php files. Let's have a look at the registerAutoload().

The method definition is public static function registerAutoload($class = 'Zend_Loader', $enabled = true). If we don't pass any parameters, the default class is Zend_Loader, and $enable is true, which allows the function to call spl_autoload_register.

if (!function_exists('spl_autoload_register')) {
require_once 'Zend/Exception.php';
throw new Zend_Exception('spl_autoload does not exist in this PHP installation');
}
It checks if spl_autoload_register function exists, because the magic autoload relies on this function.

self::loadClass($class);
loadClass() is the sole method of Zend_Loader. It looks through the path and directory, based on the $class name, and tries to load the class file. For example, if the $class is 'Zend_Log_Writer_Stream', it looks through 'Zend/Log/Writer/' and tries to load Stream.php file.

$methods = get_class_methods($class);
if (!in_array('autoload', (array) $methods)) {
require_once 'Zend/Exception.php';
throw new Zend_Exception("The class \"$class\" does not have an autoload() method");
}
spl_autoload_register(array($class, 'autoload'));
The codes check if the class possesses autoload method and tries to register the method.

Zend_Load defines and autoload method, which actually calls the loadClass().

So that is how Zend_Load works. If we call a class not defined, it automatically searchs the class file by its class name in include_path, and load the class file if it is found.

I havn't done the test by myself. But some articles on internet point out that Zend_Loader::registerAutoload() is bad in performance. It is quite possible, based on its code. So the developer must find a balance between speed and convenience.

Zend framework source code learning - Zend_Registry

Zend_Registry extends ArrayObject, which is a class in Standard PHP Library(SPL).
Let's take a look at its set method:
public static function set($index, $value)
{
$instance = self::getInstance();
$instance->offsetSet($index, $value);
}
We can see, although it is a static method, it implements a singleton inside: $instance = self::getInstance().

offsetSet($index, $value) is a method of ArrayObject. It will set the value at the specified $index as $value.

Now let's check through get method:
public static function get($index)
{
$instance = self::getInstance();

if (!$instance->offsetExists($index)) {
require_once 'Zend/Exception.php';
throw new Zend_Exception("No entry is registered for key '$index'");
}

return $instance->offsetGet($index);
}
It gets a singleton instance and tries to call offsetGet($index), which is another ArrayObject method to return the value at $index. If the specified $index doesn't exist, it throws an exception.

We use Zend_Registry to store the resources that must be accessed through the whole web site, such as database resource.

Sunday, March 15, 2009

Zend framework source code learning - Zend_Config

Zend_Config class

To understand this class, we only need to look through its 3 methods.
The first is, naturally, its constructor.

public function __construct(array $array, $allowModifications = false)
{
$this->_allowModifications = (boolean) $allowModifications;
$this->_loadedSection = null;
$this->_index = 0;
$this->_data = array();
foreach ($array as $key => $value) {
if (is_array($value)) {
$this->_data[$key] = new self($value, $this->_allowModifications);
} else {
$this->_data[$key] = $value;
}
}
$this->_count = count($this->_data);
}
Many developers prefer using arrays in php file to save config data, such as $option['path'] = ''. From this constructor, we know that to load the config data, we can instantiate a Zend_Config class in this way: $config = new Zend_Config(require 'config.php');

Zend_Config use getter to retrieve config data. It provide magic method __get as well. However, __get also call the getter.

public function get($name, $default = null)
{
$result = $default;
if (array_key_exists($name, $this->_data)) {
$result = $this->_data[$name];
}
return $result;
}

public function __get($name)
{
return $this->get($name);
}

So, to get the value of $option['path'], we can use either $config->get('path') or $config->path. I think most of PHP developers(including myself) cannot resist the temptation of using the latter one to get data.

That is pretty enough for Zend_Config class.

Monday, March 9, 2009

Zend_Db component

Zend framework provides some database access components. A common feature of these components is, it can access/query database in OO way. So, it is quite possible that you won't see a whole, integrated sql query in code. Instead, you probably will see a lot of these queries: $where = 'where id = ?'; $db->select('*', $where);

When regarding database query, i am not a supporter of OO. I prefer writing full,whole, or integrated sql, even some times this means duplication. For example, i probably need to use two queries: 1. SELECT * FROM user WHERE id=?; 2.SELECT * FROM user WHERE username = ?. I would like to write them twice, instead of write them in this way: $select = "SELECT * FROM user"; $whereId = " WHERE id=?"; $whereName = " WHERE username=?". Finally, I would just run the whole query by $db->query($sql);

So my model will not extend from any Zend_Db components. And there is no data access object carried with model. I have DAO layer. Instead of calling $user->save(), i would call $daoUser->save($user). And in my dao object, it is always like this: $sql = "SELECT * FROM user"; $db->query($sql);

Zend_Form

I give Zend_Form a couple of tries. I recognize it is a really cool component. However, i think it kind of violate the layer separation law. What if a designer wants to decorate the form? He must open up the PHP file and learn how Zend_Form work?

In spite of this, I cannot resist the temptation of using this component. The benefit is it can save a lot of time. With Zend_validator and Zend_Filter, Zend_Form can finish most of form processing job for you. You, as a developer, probably just need to add some extra own validators and that is it!

Performance, easy development, code solid

Performance is an issue often mentioned by developers. Regarding zend framework, one consideration is using Zend autoload or not. Autoload can make our life much easier. As long as we specify the file name and class name in accordance with name space rule, we don't need to put include/require on top of files. Some articles on internet, however, point out that zend autoload slow down the web site's performance. They prefer puting lot of include/require statement on top of files.

For me, i prefer using autoload, because not only it really saves a lot of time, and makes the code look more clean, but also most problems of a web application, based on my opinions, are caused by coding errors, bugs instead of performance.

some thoughts of using zend framework

1. Where is business logic located? Controller + Model? Fat controller + Thin model or Thin controller + fat model, or controller + model + another business logic layer?

2. If the web application is organized in modules, and we don't want to use zend autoload due to performance consideration, how to access one module's data/model from another module?

3. Active Record or DAO? I personally prefer DAO. And the reason is obvious: data access get separated from business model, and your model won't become heavy. Just think of a situation: send email to millions of subscribers, if each subscriber has to carry db accesss object, that is gonna take a lot of resurces.
Here is an article about active record and DAO: http://blog.astrumfutura.com/archives/128-DAO-vs-ActiveRecord-DAO-Wins.html

Thursday, March 5, 2009

Integrate Smarty with Zend_Layout

If you can endure adding {include file="header.tpl"} and {include file="footer.tpl"} in all of your template files, then you don't have to worry about this issue. And you are lucky! (You are even more lucky if you don't use smarty at all!)

Unfortunately, I cannot accept this kind of coding, so I have to face the pain and find out how to integrate smarty with zend_layout.

I can only find one article from internet regarding this issue. The article is here: http://anders.tyckr.com/2008/03/12/implementing-zend-layout-and-smarty-using-zend-framework-mvc/

It gives us a detailed solution to implement smary with zend layout, and it DOES WORK! Thank God...

CLI/Console program with Zend Framework

You can google this tutorial "PHP frameworks, Part 5: Integrating external tasks" for dealing with working outside the frameworks.

Even for a web application, we may need to run scripts in command line in some cases. For example, a cron job. And we want to use our models and Zend framework components. The most important thing is to setup include path to include your Zend library and your models. For example, if Zend is placed in 'library' folder, we can
set_include_path('.' . PATH_SEPARATOR . ROOT_DIR . '/library/'
. PATH_SEPARATOR . ROOT_DIR . '/application/models/'
. PATH_SEPARATOR . get_include_path());

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

That is it. Now you can use your models and Zend components as you use them in your web application. For example,

$config = new Zend_Config_Ini(ROOT_DIR . '/config/config.ini', 'cli');
Zend_Registry::set('config', $config);
//set up default time zone
date_default_timezone_set($config->date_default_timezone);

// configure database and store to the registery
$db = Zend_Db::factory($config->database);
Zend_Db_Table_Abstract::setDefaultAdapter($db);
Zend_Registry::set('db', $db);

//do sth
$user = new User();
$user->save();

$logFile = Zend_Registry::get('config')->logFile;
$log = new Zend_Log(new Zend_Log_Writer_Stream($logFile));
$log->info('a new user is saved');

Zend_Log limitation

I researched Zend_Log for a while today. My case is I want to user one logger and log messages to different files based on the message type. The message type is not Zend_Log's message priority. It is more event/action based. For example, i want to log login information to login.log and pay information to pay.log.

Although we can add multiple writters to a logger, we can't specify which writter we want to use. Well, i should say, I didn't find out how to specify a writter for Zend_Log.

Sure i know where there is an issue, there is a solution. But I wish next Zend framework release could bring us a better Zend_Log

Thursday, January 8, 2009

Zend framework Zend_Acl

Acl stands for action control list.

The typical flow for using Zend_Acl in a web application is as follows:
1. Instantiate the Zend_Acl class (let’s call this object $acl).
2. Add one or more roles to $acl using the addRole() method.
3. Add resources to $acl using the add() method.
4. Add the full list of privileges for each role (that is, use allow() or deny() to indicate which resources roles have access to).
5. Use the isAllowed() method on $acl to determine whether a particular role has access
to a particular resource/privilege combination.
6. Repeat step 5 as often as necessary while the script executes.

Example:
$this->auth = $auth;
$this->acl = new Zend_Acl();
//add the different user role
$this->acl->addRole(new Zend_Acl_Role($this->_defaultRole));
$this->acl->addRole(new Zend_Acl_Role('admin'));
$this->acl->addRole(new Zend_Acl_Role('member'));
$this->acl->addRole(new Zend_Acl_Role('administrator'), 'member');
// add the resources we want to have control over
$this->acl->add(new Zend_Acl_Resource('account'));
$this->acl->add(new Zend_Acl_Resource('admin'));

// allow access to everything for all users by default
// except for the account management and administration areas
$this->acl->allow();
$this->acl->deny(null, 'account');
$this->acl->deny(null, 'admin');
// add an exception so guests can log in or register
// in order to gain privilege
$this->acl->allow('guest', 'account', array('login', 'fetchpassword' , 'register' , 'registercomplete'));
// allow members access to the account management area
$this->acl->allow('member', 'account');
$this->acl->allow('admin', 'admin');

public function preDispatch (Zend_Controller_Request_Abstract $request)
{
// check if a user is logged in and has a valid role,
// otherwise, assign them the default role (guest)
if ($this->auth->hasIdentity()) {
$role = $this->auth->getIdentity()->subscription_type;
} else {
$role = $this->_defaultRole;
}
if (! $this->acl->hasRole($role)) {
$role = $this->_defaultRole;
}
// the ACL resource is the requested controller name
$resource = $request->controller;
// the ACL privilege is the requested action name
$privilege = $request->action;
// if we haven't explicitly added the resource, check
// the default global permissions
if (! $this->acl->has($resource))
$resource = null;
// access denied - reroute the request to the default action handler
if (! $this->acl->isAllowed($role, $resource, $privilege)) {
$request->setControllerName($this->_authController['controller']);
$request->setActionName($this->_authController['action']);
}
}

in bootstrap file, we add
$frontController->registerPlugin(new ControllerAclManager($auth));

Wednesday, January 7, 2009

Zend framework Zend_Auth

We can use Zend_Auth to process user authentication.

$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) { //user already login
$this->_redirect(/*redirect url*/);
}

$request = $this->getRequest();
if ($request->isPost()) {
$username = trim($request->getPost('username'));
$password = trim($request->getPost('password'));
//below is the key part of authentication
//$this->db must be a db adapter
$adapter = new Zend_Auth_Adapter_DbTable($this->db, 'username', 'password', 'md5(?)');
$adapter->setIdentity($username);
$adapter->setCredential($password);
$result = $auth->authenticate($adapter);
if ($result->isValid()) { //login successful
$auth->getStorage()->write($identity); //save itentity
//redirect
} else {//login failed

}
}

logout is simple as well: Zend_Auth::getInstance()->clearIdentity(); That is it.