I’ve been working on a system for a while now, inspired by the work that was done on the Sentry project, to provide a role-based access control system that was not only more well-maintained but also built on the foundation they provided to add in some new features. My “little project” Gatekeeper has really grown over time and (I think) really evolved into something that’s quite useful.
With this progression in mind, I’ve recently added another new feature that sits on top of the permissions and groups system that allows you to create reusable policies. Policies are a common concept when it comes to access control. They can make performing complex operations a lot simpler and, in the case of how it’s implemented here, make it much more reusable across the entire system (or multiple systems). Checking user permissions and groups is a relatively simple operation if you’re just doing one or two checks. You’d end up with something like this:
<?php use \Psecio\Gatekeeper\Gatekeeper as g; $user = g::findUserById(1); if ($user->inGroup("group1") && $user->hasPermission("perm1")) { echo "Good to go!"; } ?>
In this case, the check is relatively simple but there’s one think that any DRY code advocate could tell you – this exact check would need to be reproduced throughout the entire application exactly as stated to ensure the evaluation is the same. Even worse, if the requirements changed you’d have to work across the entire application and replace all instances with the new logic.
This is where policies can come in very handy. With the functionality that Gatekeeper includes, they’re dead simple to use too. The key is in their use of the Symfony Expression Language component. This language allows you to define text strings that represent logic and allow for more complex and self-contained evaluation. Enough talk, let’s see how we can use these policies to perform the same check as above.
<?php // First we'll make the policy - this only needs to be done once Gatekeeper::createPolicy(array( 'name' => 'admin-test1', 'expression' => '"group1" in user.groups.getName() and "perm1" in user.permissions.getName()', 'description' => 'See if a user has "permission1" and is in "group1"' )); // Now, we need to evaluate the user against the policy if (Gatekeeper::evaluatePolicy('admin-test1', $user) === true) { echo 'They have the permission! Rock on!'; } ?>
It’s a little more verbose than the previous example, but you can see how it would fetch the permissions and groups for the user and check it against the set of names. In this case the getName
function is a magic method that filters the collection and returns a set of the name
property values as an array. This way it can be used with the in
check. Once the policy is in place, then any time you need to perform that evaluation, all you need to do is call the evaluatePolicy
method with the information and it will always execute the same logic making it super portable and DRY.
I also mentioned how it helps with changing requirements. All you’d need to do here is change the policy contents (the expression
string) and all of the code already in place will now evaluate with that new logic with no code changes required. Easy, huh?
I hope this functionality will be useful if you’re a Gatekeeper user or, if you’re not, may give you a reason to check it out. I’m also interested to hear if you think this might make for a good stand-alone component, abstracted out from the Gatekeeper system. It’s integrated right now because of the known model/relationship structure but it’s not hard to pull it out and make it abstract enough to use for other systems.
One comment