Database Driven Zend ACL Tutorial Part Two
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.





April 26th, 2011 at 8:37 am
The Entities_RoleTable::getAll(); and all other following examples of the usage of this are unclear to me. Is this something thats going to be explained in part 3? It isn’t something native to Zend, is it?
Thanks for the great tutorial so far tho
April 26th, 2011 at 9:36 am
Hi, Sorry if I didn’t make this too clear. Basically these are my calls to my model (I tend you use Doctrine for this), so you will need to replace these with calls to your model. One thing I haven’t shown in these tutorials however is the format of the data that is returned via the model, so thank you for highlighting this for me. I will clear up some of these points in part 3, which I will publish in the next day or so.
April 27th, 2011 at 9:55 am
Will you also explain Doctrine in this case? I’m rather new to Zend and learning about new libraries in combination with Zend would be great. All I really need to know is how to install it properly. This will come in handy for a project I’m busy with for my employer
May 3rd, 2011 at 12:12 am
Thanks for the great tutorial
May 13th, 2011 at 2:10 am
This has been a great help. It really helped to have you show the directory structure. That’s one thing that’s been missing from other tutorials.
June 18th, 2011 at 1:56 pm
When allowing the roles to the resources, do you get the rolename and resource data from the roles table and resources table? Or do you just use the role-id and resource-id?
In other words when executing
$arrRoleResources = Entities_RoleResourceTable::getAll();
you don’t just fetch all the rows from the roleresources table?
July 3rd, 2011 at 12:04 pm
Hello brothers!!
i’ve found this tutorial very useful, i’ve integrated it to my application.I’ve been using ZF with doctrine so i’ve modified the data layer bit of your code.
It’s works fine but whenever there is an exception, it’s seems like i can be redirected to the error action of the ErrorController.
i’ve been having this error
“Fatal error: Uncaught exception ‘Zend_Acl_Exception’ with message ‘Resource ‘default::error::error’ not found’ in F:\work\php\zendworkspace\myproject\library\Zend\Acl.php on line 365″.
the resource defaut::error::error is been added and added to role for guest. can anyone shed some light?
thank you.
July 3rd, 2011 at 9:22 pm
When you build the ACL, are you building it for the current user only? If so are you making sure that the guest role has been inherited. I have yet to publish the final part of this tutorial, been bogged down with other work I’m afraid. In the final part I will be explaining role inheritance and modifying the code to implement this. Look at the ACL plugin where it actually checks the ACL using isAllowed, and grabs the current module,controller and action from the request object. If you var_dump, or print_r your ACL object here you should be able to see what resources and roles have been loaded.
July 3rd, 2011 at 9:35 pm
@Ward – I get the resource names not the id. The resource name is made up of the module::controller::action which is made available in the request object for you to check againsr.
September 7th, 2011 at 3:50 pm
Hi,
its really a very helpful tutorial.
i have implemented this in my project but i have an issue while getting roles ans resources from database.
Fatal error: Class ‘Entities_RoleTable’ not found in E:\wamp\www\zf-tutorial\library\My\ACL\Factory.php on line 30
There is some problem in _loadAclFromDB() on my system configuration.
I will be more happy if you will help me to solve this issue.
Regards,
Shiv kumar
September 15th, 2011 at 1:18 pm
I was getting an error from the `buildActionArrays` function due to it looking for Controllers in the default module, that included the Module name. I’ve made a slight tweak to `buildActionArrays` like this:
public function buildActionArrays() {
if( count($this->arrControllers) > 0 ) {
$frontController = Zend_Controller_Front::getInstance();
$defaultModule = $frontController->getDefaultModule();
foreach( $this->arrControllers as $strModule => $arrController ) {
foreach( $arrController as $strController ) {
$strClassName = ucfirst( $strController . ‘Controller’ );
if ($strModule != $defaultModule) {
$strClassName = ucfirst( $strModule ).’_’.$strClassName;
}
…..
Notice we get the defaultModule before the foreach loops, then only add the module name to the controller name if we’re not in the default module.
Thanks for the tutorials – they’re great! Looking forward to part 3
Chris
December 9th, 2011 at 1:51 pm
Hi
when I try implement this tutorials I got error something like
Fatal error: Class ‘Entities_RoleTable’ not found in /zf-tutorial/library/My/ACL/Factory.php on line 30 not found please help me ASAP
January 11th, 2012 at 12:19 pm
Hey there! These tutorials are really great. I’m starting with Zend and this kind of stuff really helps.
Thanks for the good work.
PS: part 3?
January 16th, 2012 at 6:02 pm
I agree! Part 3?
January 19th, 2012 at 5:28 pm
Where is part 3?
January 20th, 2012 at 12:35 am
Sorry, it’s been almost a year since I wrote this. Been extremely busy. In part 3 I was hoping to tie everything together and fill some of the holes I left in the previous part. I also want to provide a fully working copy to download.
Bare with me. I’ll try and get it live in the next week or so.
Thanks for reading.
January 24th, 2012 at 11:10 am
thanks for this tutorial!
i’m waiting part 3!
January 26th, 2012 at 6:31 pm
Part 3 please
February 14th, 2012 at 12:47 pm
Part 3 please…..