• Share
  • Sharebar
  • Share

I have had a few requests to get this second part of my Zend ACL tutorial published. I apologise for the long period between part one and part two, my life has been a bit hectic of late, and I just haven’t had time. I will try not to make you wait as long for the third and final part. So happy reading and I hope it’s not a disappointment after such a long wait.

In this the second part of my database driven Zend ACL tutorial I walk you through the code to actually generate an ACL object that can be used within your application. Hopefully you have now set up your database tables shown in part one of this tutorial and have managed to generate your resources. The code in this part is reliant on the database structure discussed in part one, so if you haven’t got these tables prepared I suggest your read Database Driven Zend ACL Tutorial Part One first.

Ok so we have our tables, we have roles and we have resources, so lets build our ACL. My ACL class file can be seen below, with an explanation to follow.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class DJC_ACL_Factory {        
    private static $_sessionNameSpace = 'DJC_ACL_Namespace';
    private static $_objAuth;
    private static $_objAclSession;
    private static $_objAcl;
 
    public static function get(Zend_Auth $objAuth,$clearACL=false) {
 
        self::$_objAuth = $objAuth;
	self::$_objAclSession = new Zend_Session_Namespace(self::$_sessionNameSpace);
 
	if($clearACL) {self::_clear();}
 
	    if(isset(self::$_objAclSession->acl)) {
		return self::$_objAclSession->acl;		
	    } else {
	        return self::_loadAclFromDB();
	    }
	}
 
    private static function _clear() {
        unset(self::$_objAclSession->acl);
    }
 
    private static function _saveAclToSession() {
        self::$_objAclSession->acl = self::$_objAcl;
    }
 
    private static function _loadAclFromDB() {
        $arrRoles = Entities_RoleTable::getAll();
	$arrResources = Entities_ResourceTable::getAll();
	$arrRoleResources = Entities_RoleResourceTable::getAll();
 
	self::$_objAcl = new Zend_Acl();
 
	self::$_objAcl->addRole(new Zend_Acl_Role(Entities_RoleTable::getGuestRoleName()));
	foreach($arrRoles as $role) {
            self::$_objAcl->addRole(new Zend_Acl_Role($role['role']));	
        }
 
        // add all resources to the acl
        foreach($arrResources as $resource) {
            self::$_objAcl->add(new Zend_Acl_Resource($resource['module'] .'::' .$resource['controller'] .'::' .$resource['action']));	
        }
 
        // allow roles to resources
        foreach($arrRoleResources as $roleResource) {
            self::$_objAcl->allow($roleResource['role']['roleName'],$roleResource['resource']['module'] .'::' .$roleResource['resource']['controller'] .'::' .$roleResource['resource']['action']);	
        }
 
	self::_saveAclToSession();	
	return self::$_objAcl;
    }
}

As you may have noticed I have called this class DJC_ACL_Factory, but it does not really follow the Factory pattern. I have used this terminology simply because the class does build my ACL, and I like to think of it as a mini ACL factory. The bulk of the work is done via the private methods which are called upon by the one static method DJC_ACL_Factory::get(). First of all a session object is created and the session is checked to see if the ACL has already been built. If it has the ACL is directly returned from the session, if it isn’t _loadAclFromDB() is called.

_loadAclFromDB() is the method that builds the ACL based on the resources and roles you have saved in your database tables. Firstly using what ever database strategy you prefer, all the roles, resources and links between roles and resources are gathered from the database and stored in arrays. Initially a guest role is added to the ACL, this is for the parts of the site that don’t require a user to be logged in. For example, login/signup pages. Then all the roles that were retrieved from the database are added to the ACL. The resources and the links between roles and resources are then also added to the ACL object. Finally the ACL is stored in a session for easy re-use, and the ACL object is returned. This is the basic version of my ACL implementation, without any role inheritance or resource exceptions, which will be discussed in part 3 of this tutorial.

Ok, so now we have the class that will build our ACL, how do we put it into practice? There are various options for pulling this off, but my preferred choice is via a controller plugin. The code for my controller plugin can be seen below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class DJC_Controller_Plugin_ACL extends Zend_Controller_Plugin_Abstract {
 
    public function preDispatch(Zend_Controller_Request_Abstract $request) {
        $objAuth = Zend_Auth::getInstance();
	$clearACL = false;
 
	// initially treat the user as a guest so we can determine if the current
	// resource is accessible by guest users
	$role = 'guest';
 
	// if its not accessible then we need to check for a user login
	// if the user is logged in then we check if the role of the logged
	// in user has the credentials to access the current resource
 
        try {
	    if($objAuth->hasIdentity()) {
	        $arrUser = $objAuth->getIdentity();
 
	         $sess = new Zend_Session_Namespace('DJC_ACL');
	         if($sess->clearACL) {
	             $clearACL = true;
	              unset($sess->clearACL);
	         }
 
                 $objAcl = DJC_ACL_Factory::get($objAuth,$clearACL);
 
	         if(!$objAcl->isAllowed($arrUser['role'], $request->getModuleName() .'::' .$request->getControllerName() .'::' .$request->getActionName())) {
	             $request->setModuleName('default');
        	     $request->setControllerName('error');
        	     $request->setActionName('noauth');
	         }
 
            } else {
	        $objAcl = DJC_ACL_Factory::get($objAuth,$clearACL);
	        if(!$objAcl->isAllowed($role, $request->getModuleName() .'::' .$request->getControllerName() .'::' .$request->getActionName())) {
	            return Zend_Controller_Action_HelperBroker::getStaticHelper('redirector')->setGotoRoute(array(),"login");	
	        } 
	    }
        } catch(Zend_Exception $e) {
            $request->setModuleName('default');
            $request->setControllerName('error');
            $request->setActionName('noresource');
        }
    }
}

Both the ACL class and the ACL Controller Plugin need to be saved in specific directories within your applications directory structure so that they can be autoloaded by Zend Framework. The directory structure is shown below.

/myproject
  /application
    /configs
    /layouts
    /models
    /modules
    /bin
    /docs
    /library
      /Zend
      /DJC
        /ACL
          Factory.php
        /Controller
          /Plugin
            ACL.php
    /public
    /tests

Finally you will need to register this plugin via Bootstrap.php, which will make sure the plugin is executed on every request.

1
2
3
4
protected function _initPlugins() {
    $front = Zend_Controller_Front::getInstance();
    $front->registerPlugin(new DJC_Controller_Plugin_ACL(), 1);
}

Obviously at this point you will also need to allocate roles to your users, and allocate resources to roles. You can do this manually via the database, or build a nice admin area for managing users, roles and resources.

That’s it for now. You should now have a database driven ACL up and running, which is quite easy to manage. However this wasn’t enough for me. I needed an ACL with role inheritance and I also required resource allow/deny exceptions per user. I will walk through this in the final part of this tutorial, which I plan to publish in the next few days.