A common problem with developing web applications is controlling user access to certain sections or single pages. Zend Framework's Controller Plugin system provides a clean and efficient way to manage these checks, without muddling up your Controllers.
Controller plug-ins are an extension of Zend_Controller_Plugin_Abstract. They allow you to modify the request (or perform any other action) during any point in the Zend Framework request life-cycle. For more information about controller plug-ins themselves, visit the Zend Framework manual.
Lets start out with the controller plugin itself. The first thing we need to know is what point(s) of the request process lifecycle we should be operating on. In our case, we need to know what controller and action the end user is requesting, so all routing must have occurred. I chose to use preDispatch() because it ensures that an optimal amount of plugins have had their opportunity to make changes to the request, and gives us the highest chance of checking access to the final destination. We can also catch any forwards from our action controller by performing the check here.
<?php
/**
* A front controller plugin to check the current user against an access control list.
*
* @author A.J. Brown
* @category Examples
* @package Application_Controller
* @subpackage Plugin
* @version $Id$
*
*/
class Application_Controller_Plugin_CheckHasAccess
extends Zend_Controller_Plugin_Abstract
{
public function preDispatch( Zend_Controller_Request_Abstract $request )
{
$isLoggedIn = Zend_Auth::getInstance()->hasIdentity();
// Save some cycles if we're already logged in
if( $isLoggedIn ) {
return;
}
$config = $this->_getConfig();
$action = $request->getParam( 'action' );
$controller = $request->getParam( 'controller' );
// Make sure we don't end up in a loop
if( $controller == $config->loginController
&& $action == $config->loginAction )
{
return;
}
$secure = $this->_checkIsSecure(
$request->getParam( 'action' )
, $request->getParam( 'controller' )
);
if( $secure ) {
$request->setParam( 'ref', $request->getPathInfo() );
$request->setControllerName( $config->loginController );
$request->setActionName( $config->loginController );
$request->setDispatched( false );
}
}
/**
* Load the configuration.
*
* @return Zend_Config_Ini
*/
protected function _getConfig()
{
static $config = null;
if( null === $config ) {
$config = new Zend_Config_Ini(
APPLICATION_PATH . '/configs/access.ini' , 'global' );
}
return $config;
}
protected function _checkIsSecure( $action, $controller, $module = 'default' )
{
$config = $this->_getConfig();
// If no match is found, what should be the default?
$public = ( isset( $config->defaultAccess ) && $config->defaultAccess == 'public' );
// Check the action level, then controller
if( isset( $config->controllers->$controller->actions->$action->access ) ) {
$public = ( $config->controllers->$controller->actions->$action->access == 'public' );
} elseif( isset( $config->controllers->$controller->access ) ) {
$public = ( $config->controllers->$controller->access == 'public' );
}
return !$public;
}
}
As you can see, the plug-in makes use of an ini configuration file to determine if a given controller and action is public or not. If a setting for the action doesn't exist, we fall back to the controller's setting. If the controller doesn't have a setting, we use whatever the default is.
Lets take a look at the configuration file.
[global] defaultAccess = "private" loginController = "index" loginAction = "login" controllers.index.acccess = "public" controllers.index.actions.profile.access = "private" controllers.account.access = "private" controllers.account.actions.confirm = "public"
In this configuration, all routes will be private unless they're specifically made private. All of our actions within the "index" controller will be public, except for the "profile" action. For the "account" controller, only the "confirm" action will be public.
The last step is making sure our plugin is registered with the front controller. If we forget this part, our plug-in will never have a chance to intercept the request. Registering can be done anywhere you want and at any point during the request process. In fact, your plug-ins can even register other plug-ins. The easiest and most common way is by adding an entry in our application.ini file for the FrontController resource.
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" resources.frontController.params.displayExceptions = 0 resources.frontController.plugins.CheckHasAcess = "Application_Controller_Plugin_CheckHasAccess"
Conclusion
The nice part about designing an access control system using Zend Framework's controller plug-in system is that our code is separated from our controller code. Using this system, a developer doesn't necessarily have to concern himself with modifying the system. Any new controllers that are added will automatically use the system without any additional code, and permissions can be changed quickly simply by modifying a configuration file.
Happy Coding!