The part where I complain you can ignore
While most of you have been sitting under the Christmas tree and happily unpacking presents surrounded by your loved ones, I was reading Matthew's blog post about Zend_ACL. What? I finally got time to to these kinds of things now 
In the comments, Lukas already mentioned the good ol' PEAR package LiveUser, of which I was the original author and that has been accused of being over-engineered and way too complex to grasp. Looking back I'd have to say people were right. It was too complex and it was over-engineered. And now I'm seeing similar problems again with Zend_ACL.
I haven't used LiveUser myself in many, many years because all of the projects I've been involved with ever since either did not require any really sophisticated rights management, or when they did there already was an existing system in place. Also, coworkers needed a really long time to grasp its concepts and it was a real pain to initially set up and quite hard to debug at times.
While Zend_ACL at first sight is less complex, as it supports only one style of rights management and does not even define any specific persistance mechanism for the storage of resources, roles and their relationships, it deals with a whole different level of complexity: A gazillion of small classes with next to no functionality, a single one for every tiny little aspect involved in the management of rights throughout your application. This sounds like an awful lot of overhead and an extreme case of Javaism (Java-style architecture ported 1:1 to PHP without questioning whether or not it's the right way to do it in the context of PHP).
The idea
Rewind a few years to a previous programming gig, my former Boss Thies came up with a very simplistic way of managing user rights that is a lot more suited to the PHP way of doing things. Basically, you define a list of rights, following a naming scheme that implies a hierarchical structure - like this:
ApplicationName.ModuleName.FunctionName.ActionName
An example of a list of rights for a real-life application could look like this:
- MyGuestbook.FrontPage.ItemList.Show
- MyGuestbook.FrontPage.EntryForm.Show
- MyGuestbook.FrontPage.EntryForm.Post
- MyGuestbook.Admin.ItemList.Show
- MyGuestbook.Admin.ItemList.Delete
- MyGuestbook.Admin.ItemList.Edit
- MyGuestbook.Admin.General.Delete
- MyGuestbook.Admin.General.Activate
- MyGuestbook.Admin.General.Deactivate
...I guess you can see where this is going. A list like this is very easily maintainable and it's so straightforward, you don't even need to think about your rights structure in advance, you can just make it up as you go while you're happily coding away and suddenly feel the need to ask for a user right at some point.
I've written my own implementation of this style of rights management that defines only two methods:
- setRights(Array $rightList)
Sets a list of rights for a given user. This list could come from a database, a file, anywhere you'd like. Example will follow later on. Invoked only once after a user has logged in.
- checkRight(String $rightName)
Checks whether the given rights list contains the right with the given name. Invoked at any time a right has to be checked.
Until now, the approach may have sounded a bit too simplistic. What if I have hundreds of rights throughout my application? I'd need to assign every right to every user manually! Not so, because as a shortcut, you can use wildcards to give a user all rights in a given area. In the example above, to give a user access to the front page, but only the item list in the admin section, you could have a rights list like this:
- MyGuestbook.FrontPage.*
- MyGuestbook.Admin.ItemList.*
It's also possible to negate a right by prefixing it with a minus sign. If, for example, I'd like the user to have access to the aforementioned modules, but I don't want him to be able to delete entries, the list could look like this:
- MyGuestbook.FrontPage.*
- MyGuestbook.Admin.ItemList.*
- -MyGuestbook.Admin.ItemList.Delete
Notice the minus sign: It means although the user would have all the rights for the ItemList action because of the wildcard assignment, the Delete function is specifically revoked. You could also use wildcards for revoking a whole bunch of rights, but if you needed to do that, you'd probably have made a mistake with your rights assignments anyway
Groups and roles
I can already hear the outcry: "But I need groups! And roles!". Yeah - about that... there has been a lot of discussion on this topic on the LiveUser mailinglist many moons ago. The general consensus was: Groups and roles are practically the same thing, they're just named differently depending on context. Let me illustrate: A group represents a number of entities (say, "users") sharing the same set of rights. A role represents a set of actions which entities that are assigned to this role are supposed to be able to perform. Such actions can in turn be represented as rights, and there you go - we're back where we were with the group thingy.
Groups (and hence, roles) are easily implemented using the above approach: The simplest method would be to keep a separate rights object for each group the user belongs to, and whenever a right is checked, iterate through the group rights array and do a check on each of the objects it contains. After all groups have been checked, you can then check the individual user rights (non-group rights) that could potentially override group rights. After 10-15 minutes of light coding and the use of only very few brain cycles you'll have your enhanced group rights capable version of the system illustrated above. A more advanced method would be to first merge all rights into one array and do lookups only on the merged structure - the only challenge here would be to think of a system that decides the order in which groups that define the same rights would be able to override each other... but the system is simple enough that nearly anyone could come up with at least something that works.
"Okay", you say, "but if the rights are not defined centrally, but made up as-I-go when I code stuff, how do I find out which rights are actually used?". There are multiple valid approaches to tackle that issue. One possibility would be to do a lookup in some database table and insert the right name if it is not already present. It's an approach I've used myself in the (far) past - it works, but it's quite expensive, especially as right checks are common. You could cache the results, but the caching mechanism results in more code, so we trade in slow database lookups with more logic overhead. A better method is what gettext does - just scan the source for calls to the checkRight() method and write the found right names into some datastore that can be used for writing graphical user interfaces for the administration of user rights. To scan your code you can either use grep or write a really small PHP script.
Examples
I have some example code up that I wrote back in 2006. Be warned that this code has never been tested in production, it is merely the result of a pissing contest with someone who claimed that this would be very complicated to implement - the code was only used to prove him wrong. It should work, but may contain bugs that bite you in places where it really hurts:
Limitations
What this approach solves (and, I think, solves very well) is the problem of static user rights that are defined for specific actions on the application level. Where it fails is when you have user-generated content for which you want to maintain object-specific ACLs - e.g., user A uploads a document that is supposed to be viewable by everyone, but editable only for users B and C, which are members of user-defined group FooBar. Rights specific to this document would have to be created at runtime. While certainly doable with the above approach, the handling would be a real nightmare, as you'd have to deal with potentially millions of dynamically generated rights. This scenario may be one where Zend_ACL is better suited, although a simpler approach might be to just define four basic rights (create, insert, update, delete) that are dynamically mixed into the user rights array depending on object context - that way, the above approach could even be used in these situations (although it would smell a bit like rape).
Conclusion? Input?
Okay, alright - I haven't really given you the ultimate ready-to-use solution for easy rights management, but hey, Christmas is over

What I meant to illustrate is that rights management can be done in an easy way, a way that IMO suits "the PHP way" much better than all those overly complicated packages like Zend_ACL, phpGACL or, yes, LiveUser. PHP is not about purity, it's about getting things done. People sometimes seem to forget that (including me), so I think a little reminder every now and then can't hurt
What I'd like to know now is what other people (yes, that means you) think about this approach and how would you apply it (or something similar) to user-generated content without abandoning the simplicity illustrated above?
Happy new year to all of the PHP community!
In den letzten Wochen sind in diversen englischsprachigen Blogs sehr interessante und teilweise recht ausführliche Beiträge erschienen, die verschiedene Themen zum Zend Framework abdecken. Ich möchte an dieser Stelle einmal auf diese Beiträge hinwe...
Tracked: Jan 20, 18:32