Caching ACL permissions with CachedAclComponent
When you set up ACL with a bunch of groups with subgroups, you will end up with five or maybe more queries per request. When you have a lot of active users it might fry your database. This is a simple solution to get rid of all those queries.
How it works
This component checks if the permission is stored in the cache. If this is not the case, Cake's AclComponent is called to get the permission. The result is caught and placed in the cache for next time. So when the user lands on the page and the permission is cached, no queries are issued. The permission is retrieved from the cache.When someone changes permissions on a group using allow() or deny(), the cache for that ACO (controller/action combination) is cleared. Beware though, if you have multiple servers and no central cache, this won't work as expected, because the cache can't be cleared on all servers. Instead the cache is only cleared on a single server, the other servers depend on the cache duration.
Requirements
The namespace supporting cache engine, found here: http://bakery.cakephp.org/articles/view/efficient-caching-with-namespacefileengine and a proper and working ACL setup.Installation
First, copy the component file to APP/controllers/components/cached_acl.php. Next, place the following line(s) in your bootstrap.php at APP/config/bootstrap.php:Download code
<?php
App::import('Vendor', 'NamespaceFile');
Cache::config('acl', array('engine' => 'NamespaceFile', 'duration'=> '+1 month', 'prefix' => 'acl.'));
?>
Now it is time to see some magic happen. Change (in your AppController) this:
Controller Class:
Download code
<?php
var $components = array('Auth', 'Acl');
?>
To:
Controller Class:
Download code
<?php
var $components = array('Auth', 'CachedAcl');
?>
The permissions are now beeing cached.
Phally
http://www.frankdegraaf.net
Comments
Bug
1 Don't work with different aro syntax
I tried distinct syntax for the SAME aro object and results are different / wrong :
$this->Acl->check($this->Auth->user(), 'Tools/list');-> this works fine (result = true)$this->Acl->check(array('model'=>'User','foreign_key'=>$this->user["User"]["id"]), 'Tools/list');-> bad result (result = false)$this->Acl->check('User:4', 'Tools/list');-> dispatch a warning: Invalid argument supplied for foreach() [ROOT/cake_1.2.2.8120/cake/libs/set.php, line 1046] -> and bad result (result = false)With the core Acl components, the 3 syntaxes return true...
It seems that __cachePath() in the component don't return the same thing :
For syntax 1: return "Tools_list.User_id_4"
For syntax 2: return "Tools_list.model_User"
For syntax 3: return "Tools_list."
Comment
2 Confirmed
- Edit: I have fixed it for you. Let me know if you have any issues.
Comment
3 Update
Comment
4 another problem
But another problem occurs with the check() function.
You write in this function:
if ($check == false) {But if the cached value for acl is "0", Cache:read() returns "0" and then this condition is true, so the function calls "parent::check...'I modified the code like this :
function check($aro, $aco, $action = "*") {$path = $this->__cachePath($aro, $aco, $action);
$check = Cache::read($path, 'acl');
if ($check === false) {
$check = parent::check($aro, $aco, $action);
Cache::write($path, $check ? 1 : 0, 'acl');
} else {
$check = $check == true;
}
return $check;
}
And it's work fine for me (no sql request left when acl are cached)
The modifications are:
1. if ($check === false) (=== in place of ==)
2. Cache::write($path, $check ? 1 : 0, 'acl'); (1 in place of true)
3. $check = $check == true; (== in place of ===)
Comment
5 Solved, but
<?phpif ($check === false)
?>
instead of
<?phpif ($check == false)
?>
is enough. Sorry for missing that, I have updated the code.
Comment
6 acl check not working in component
$this->Acl->check(array('model' => 'User', 'foreign_key' => $aroId), array('model' => 'User', 'foreign_key' => $acoId), $action)The result is null when it should be true when I run the acl check from the custom component (stripped down to only do the basic one line check:$this->controller->Acl->check(array('model' => 'User', 'foreign_key' => $aroId), array('model' => 'User', 'foreign_key' => $acoId), $action)Any ideas?Question
7 Any news?
I just thought of one looking at your code again. That second param is an array, with a model, but was built to support action paths like "/controllers/users/add". So this might be the problem you are facing.
Comment
8 possibly solved
Initially that seems to have solved the problem. I have tested Acl->check() using the alias instead of model=>, user=> for the aco.
Comment
9 ta
Comment
10 Error with __deleteCache
/**
* Deletes an ACO from the cache.
*
* @param string $aro ARO
* @param string $aco ACO
* @access private
*/
function __deleteCache($aro, $aco, $action) {
if (is_array($action)) {
foreach ($action as $act) {
Cache::delete($this->__cachePath($aro, $aco, $act, true), 'acl');
}
}else {
Cache::delete($this->__cachePath($aro, $aco, $action, true), 'acl');
}
}
Comment
11 question about paths
Comment
12 A suggestion for a big performance boost
One way that this can be alleviated is to update the component to understand user types/roles/groups and to flag users that have permissions specially assigned to them (instead of the user group). This takes a little bit of work to modify the user model, your user data structure, and the CacheAcl component itself but the end result is a big performance boost.
Say, for example, I were to check 50 ACL permissions on a single page (because I'm crazy like that). The component checks the user session and sees that the user does not have special permissions, so it changes the Aro to the User Type instead. That alone will give you a boost by consolidating cache files down for most users.
Now, if we take it a step further and keep all of the checks for that user type in a single associative array, written out to a single permissions cache file for the User Type and suddenly we're reading a single cache file for all the permissions associated with that user - 1 time.
If the Aro isn't a user or user type, default back to the original caching behavior (to handle everything else). These updates will allow you to fully take advantage of ACL for users across your entire application with very little concern for performance.
Question
13 cahed acl component
I can't find the cached_acl.php component. Can anybody say from where I can get the component?
Comment
14 link
cotes football