Adding Zend ACL to CodeIgniter

Adding the Zend ACL library to your CodeIgniter installation can be really handy plus it’s really easy to do. In this article I share with you how I accomplished this.

As you might know I’m a big fan of CodeIgniter and although the system is pretty awesome right out-of-the-box there are some elements I always add before I start developing an application in CodeIgniter. One of those is the following ACL class. I used to work with a simple and straight-forward ACL class I wrote myself but about two years ago I decided to implement Zend’s powerful ACL library in my CodeIgniter installations. I decided to share this with you in this little how-to.

  1. Download the latest version of the Zend framework.
  2. Create the folder Zend in your CodeIgniter’s /system/application/libraries.
  3. Copy the folder Acl, Acl.php and Exception.php from the framework into it.
  4. Create the file Zacl.php in your libraries folder and copy the code below into it.
  5. Add ‘zacl’ to the array $autoload[‘libraries’] in /system/application/config/autoload.php.

This is the content of my Zacl.php.

<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Zacl
{
	// Set the instance variable
	var $CI;

	function __construct()
	{
		// Get the instance
		$this->CI =& get_instance();

		// Set the include path and require the needed files
		set_include_path(get_include_path() . PATH_SEPARATOR . BASEPATH . "application/libraries");
		require_once(APPPATH . '/libraries/Zend/Acl.php');
		require_once(APPPATH . '/libraries/Zend/Acl/Role.php');
		require_once(APPPATH . '/libraries/Zend/Acl/Resource.php');
		$this->acl = new Zend_Acl();

		// Set the default ACL
		$this->acl->addRole(new Zend_Acl_Role('default'));
		$query = $this->CI->db->get('tbl_aclresources');
		foreach($query->result() AS $row){
			$this->acl->add(new Zend_Acl_Resource($row->resource));
			if($row->default_value == 'true'){
				$this->acl->allow('default', $row->resource);
			}
		}
		// Get the ACL for the roles
		$this->CI->db->order_by("roleorder", "ASC");
		$query = $this->CI->db->get('tbl_aclroles');
		foreach($query->result() AS $row){
			$role = (string)$row->name;
			$this->acl->addRole(new Zend_Acl_Role($role), 'default');
			$this->CI->db->from('tbl_acl');
			$this->CI->db->join('tbl_aclresources', 'tbl_acl.resource_id = tbl_aclresources.id');
			$this->CI->db->where('type', 'role');
			$this->CI->db->where('type_id', $row->id);
			$subquery = $this->CI->db->get();
			foreach($subquery->result() AS $subrow){
				if($subrow->action == "allow"){
					$this->acl->allow($role, $subrow->resource);
				} else {
					$this->acl->deny($role, $subrow->resource);
				}
			}
			// Get the ACL for the users
			$this->CI->db->from('tbl_users');
			$this->CI->db->where('roleid', $row->id);
			$userquery = $this->CI->db->get();
			foreach($userquery->result() AS $userrow){
				$this->acl->addRole(new Zend_Acl_Role($userrow->user), $role);
				$this->CI->db->from('tbl_acl');
				$this->CI->db->join('tbl_aclresources', 'tbl_acl.resource_id = tbl_aclresources.id');
				$this->CI->db->where('type', 'user');
				$this->CI->db->where('type_id', $userrow->userid);
				$usersubquery = $this->CI->db->get();
				foreach($usersubquery->result() AS $usersubrow){
					if($usersubrow->action == "allow"){
						$this->acl->allow($userrow->user, $usersubrow->resource);
					} else {
						$this->acl->deny($userrow->user, $usersubrow->resource);
					}
				}
			}
		}
	}

	// Function to check if the current or a preset role has access to a resource
	function check_acl($resource, $role = '')
	{
		if (!$this->acl->has($resource))
		{
			return 1;
		}
		if (empty($role)) {
			if (isset($this->CI->session->userdata['user'])) {
				$role = $this->CI->session->userdata['user'];
			}
		}
		if (empty($role)) {
			return false;
		}
		return $this->acl->isAllowed($role, $resource);
	}
}

You’re now able to check the access for a certain resource really easy with the following snippit.

// Check the access for the resource 'dummy' for the current logged in user.
if ($this->zacl->check_acl('dummy')){
	...
}

// Check the access for the resource 'manage_users' for the role 'admin'.
if ($this->zacl->check_acl('manage_users', 'admin')) {
	...
}

I think the code is pretty self-explainatory but just in case I’ve included the SQL for the correct table structure below.

CREATE TABLE IF NOT EXISTS `tbl_acl` (
	`id` int(11) NOT NULL auto_increment,
	`type` enum('role','user') NOT NULL,
	`type_id` int(11) NOT NULL,
	`resource_id` int(11) NOT NULL,
	`action` enum('allow','deny') NOT NULL,
	PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT;

CREATE TABLE IF NOT EXISTS `tbl_aclresources` (
	`id` int(11) NOT NULL auto_increment,
	`resource` varchar(255) NOT NULL,
	`description` longtext NOT NULL,
	`aclgroup` varchar(255) NOT NULL,
	`aclgrouporder` int(11) NOT NULL,
	`default_value` enum('true','false') NOT NULL,
	PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT;

CREATE TABLE IF NOT EXISTS `tbl_aclroles` (
	`id` int(11) NOT NULL auto_increment,
	`name` varchar(255) NOT NULL,
	`roleorder` int(11) NOT NULL,
	PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT;

INSERT INTO `tbl_aclroles` (`id`, `name`, `roleorder`) VALUES
(1, 'Admin', 1),
(2, 'User', 2),
(3, 'Guest', 3);

CREATE TABLE IF NOT EXISTS `tbl_users` (
	`userid` int(11) NOT NULL auto_increment,
	`user` longtext NOT NULL,
	`pass` longtext NOT NULL,
	`firstname` longtext NOT NULL,
	`prefix` longtext NOT NULL,
	`lastname` longtext NOT NULL,
	`gender` enum('m','f') NOT NULL,
	`roleid` int(11) NOT NULL,
	`mail` longtext NOT NULL,
	PRIMARY KEY  (`userid`)
) ENGINE=MyISAM DEFAULT;

With this setup, some controllers for editing the ACL and a modified version of SimpleLogin you’re pretty much sure to hit the ground running when starting on a new CodeIgniter application.

If you have any ideas or improvements for this library please feel free to leave them in the comments.

Author: Luc De Brouwer

Mild-mannered software engineer, Nine Inch Nails lover, gin drinker, cat person, and somewhat of a geek. Ron Swanson is my spirit animal.

32 thoughts on “Adding Zend ACL to CodeIgniter”

  1. Hello Luc,

    Great article that’s help me a lot !
    I have few questions regarding the database tables’fields.

    In the tbl_aclroles table :
    Why do you add the “roleorder” field ? How do you use it ?

    In the tbl_aclresources table :
    Why do you add a “aclgrouporder” field ? How do you use it ?

    Thank you !
    Denis

    1. Glad to hear you liked it!

      I use those fields for ordering the roles and for ordering the resources within a certain resource group ( the field aclgroup in the table tbl_aclresources ) in my backend. I’ve build a full-fletched administration panel for my ACL roles and resources in CodeIgniter.

  2. Hi Luc! As you mentioned in your post, would you mind mentioning what other elements you add to a CodeIgniter application? The reason I ask is because I’m also starting to use it. So far, it’s great. But I’m always on the lookout for hints or tips from more experienced developers.

  3. Thanks for this!

    I don’t suppose you have some sample data for the tables? (even some screenshots). It would help to make sense of the entries required.

    i looked at ion auth.. but setting up site elements as resources makes alot of sense.

    1. Sure, below you’ll find a basic table lay-out and some sample data.

      CREATE TABLE `tbl_acl` (
        `id` int(11) NOT NULL auto_increment,
        `type` enum('role','user') NOT NULL,
        `type_id` int(11) NOT NULL,
        `resource_id` int(11) NOT NULL,
        `action` enum('allow','deny') NOT NULL,
        PRIMARY KEY  (`id`)
      ) ENGINE=MyISAM;
      
      INSERT INTO `tbl_acl` (`id`, `type`, `type_id`, `resource_id`, `action`) VALUES(1, 'role', 1, 1, 'allow');
      INSERT INTO `tbl_acl` (`id`, `type`, `type_id`, `resource_id`, `action`) VALUES(2, 'role', 1, 2, 'allow');
      INSERT INTO `tbl_acl` (`id`, `type`, `type_id`, `resource_id`, `action`) VALUES(3, 'role', 1, 3, 'allow');
      
      CREATE TABLE `tbl_aclresources` (
        `id` int(11) NOT NULL auto_increment,
        `resource` varchar(255) NOT NULL,
        `description` longtext NOT NULL,
        `aclgroup` varchar(255) NOT NULL,
        `aclgrouporder` int(11) NOT NULL,
        `default_value` enum('true','false') NOT NULL,
        PRIMARY KEY  (`id`)
      ) ENGINE=MyISAM;
      
      INSERT INTO `tbl_aclresources` (`id`, `resource`, `description`, `aclgroup`, `aclgrouporder`, `default_value`) VALUES(1, 'user_management', '', 'user', 0, 'false');
      INSERT INTO `tbl_aclresources` (`id`, `resource`, `description`, `aclgroup`, `aclgrouporder`, `default_value`) VALUES(2, 'relation_manager', '', 'relations', 0, 'false');
      
      CREATE TABLE `tbl_aclroles` (
        `id` int(11) NOT NULL auto_increment,
        `name` varchar(255) NOT NULL,
        `roleorder` int(11) NOT NULL,
        PRIMARY KEY  (`id`)
      ) ENGINE=MyISAM;
      
      INSERT INTO `tbl_aclroles` (`id`, `name`, `roleorder`) VALUES(1, 'Admin', 1);
      INSERT INTO `tbl_aclroles` (`id`, `name`, `roleorder`) VALUES(2, 'User', 2);
      
      CREATE TABLE `tbl_users` (
        `userid` int(11) NOT NULL auto_increment,
        `user` longtext NOT NULL,
        `pass` longtext NOT NULL,
        `firstname` longtext NOT NULL,
        `prefix` longtext NOT NULL,
        `lastname` longtext NOT NULL,
        `gender` enum('m','f') NOT NULL,
        `roleid` int(11) NOT NULL,
        `mail` longtext NOT NULL,
        PRIMARY KEY  (`userid`)
      ) ENGINE=MyISAM;
      
  4. Hi! This solution seems great, but it seems like I have a
    problem with the Zend framework files. I keep getting these “failed
    to open stream” errors : Message:
    require_once(Zend/Acl/Resource/Interface.php)
    [function.require-once]: failed to open stream: No such file or
    directory Filename: Zend/Acl.php I did copy the Acl folder and
    Acl.php, Exception.php files in the library directory. Any thoughts
    on how to solve that problem? Thanks for your help!

    1. I solved part of the problem: it’s because I had moved my
      application folder out of the system folder. If I want to do that,
      how should I edit the Zacl.php file? Thanks a million!

        1. Thx sturggled with that a little.
          for other people:

          set_include_path(get_include_path() . PATH_SEPARATOR . “application/libraries”);

  5. Just wanted to say thanks for sharing – was able to tinker a little bit and get this integrated with Ben Edmunds IonAuth package, and operational with CI 2.0.0. Would love to know more about your ACL/Resource management, particularly in how you handle user permissions vs role permissions and the orders in which they are inserted, as it seems last-inserted takes priority.

    1. Baiscally you have ACL resources ( tbl_aclresources ). These resources have default access rights but exceptions can be stored in the tbl_acl table. Exceptions are linked to either roles ( tbl_aclroles ) or users ( tbl_users ). Users always have a certain role assigned to them so they can inherit the right of the role.

  6. i luc, i got this error
    Fatal error: Cannot redeclare class Zend_Acl in C:\xampp\php\PEAR\Zend\Acl.php on line 48

    how to solve this?

    thanks

  7. Hi Luc,

    Thank you for your tutorial. I’m using CI 2.0.2. The ‘application’ directory is outside ‘system’. So, I change the file Zacl.php, removing the “BASEPATH” :

    set_include_path(get_include_path() . PATH_SEPARATOR . “application
    /libraries”);

    Is it right to do so ?? But now I got other error msg :
    “Undefined property: Welcome::$db”
    Filename: libraries/Zacl.php
    Line Number: 22

    How to solve this ?

    Thanks
    – pedma –

    1. Hi Luc,

      Is it possible to use this for controlling user access (roles) to specified module (using HMVC, the module is under …/application/modules directory). Example: UserA has access to Sales module, UserB has access to Purchasing module, UserC has access to all modules.
      Is it possible ? Any idea how to do it ?

      Many thanks
      – pedma –

      1. Hi Luc,

        I found it. I forgot to add “database” to $autoload[‘libraries’] , at autoload.php file …. 🙁

        Thanks
        – pedma –

  8. Thanks alot for this Tutorial. I was amazed at how incredibly simple it is to implement this library into CI. Its a real life saver

  9. What is “default_value” in tbl_aclresources?

    If true access ok, but if false access ok (it is correct?)

    Tks.

  10. Hi Luc seriously i confused how to use this plugin. Is this plugin require datamapper or can be used with datamapper? I think some demo and download example application make it clearly. Thanks

    1. Hi Elziman,

      If you follow the tutorial step by step than you should have a perfect working example. I’ll try to come up with a more elaborate write up soon.

      Cheers,

      Luc

  11. Hi Luc,

    Sorry if this is kinda newbie/stupid question.. I was just started using CI and your code and I’m wondering, how am I gonna register the roles and resources? And how can I implement the Zacl class to my Controller, Please help

    1. Hi Astrode,

      If you follow all the steps you should have nailed the implementation. Registering the roles and resources is something you’re going to have to do yourself. This post just helps you to set the actual ACL up.

      Cheers,

      Luc

      1. Hi Luc, i already understand it thanks for the help i able to add restriction to the particular class even not using database or whatsoever. I just use the zend acl so cool .. thanks man 🙂

  12. I am getting the error “Uncaught exception ‘Zend_Acl_Role_Registry_Exception’ with message ‘Role id ‘Administrator’ already exists in the registry'” when I modify a Role. Creating new roles doesn’t produce this error. Any ideas how to fix this?

    1. Oops sorry, it was just a logic error on my code. It used the add() code when it should have used edit(), my bad!

  13. Hi Great article, do you have a demo of your implementation of the management screens / code you built to consume this library at all you could share?

Comments are closed.