This is a follow-up to my initial overview of the roles and capabilities system in WordPress. I’d check out Part I first.
I’ve previously explained the complexities of the roles and capabilities system, but I haven’t adequately argued why they are too complex.
Numerous developers have weighed in at Trac ticket #10201, but I want to touch on this anyway. More or less, the current system is fine if you want to know what the current user can do. We load up their roles and merge the capabilities that make up those roles, add any other capabilities the user was granted, and remove any revoked capabilities. Then, we check if the capability we’re checking is in that list of capabilities.
The problem is apparent when you want to know the answer to this question: How many users have capability X? Querying for abstract capability X strikes some as edge case at first, so a better question might be: how many users should be considered at least an author?
To answer this question, we have to load up each user individually and build their derived capability list, by loading up their roles, add-on capabilities, and revoked capabilities. It should be fairly apparent that on a site with many users, the capabilities system can be a performance issue, to say the least.
Let’s remove user-level capabilities from the equation. Now, we can load up all of the roles, figure out which have capability X, and figure out which users have those roles. Clearly a huge performance benefit, as we’re running one query (to fetch the role definitions), unserializing its result, looping through the roles and checking for a cap, then querying the users for that role.
If only it were that easy. We store a user’s roles and capabilities in a serialized array in usermeta. That means we have to again leave SQL with every user’s usermeta value, unserialize it, and check if the user has the role. Core does employ a hack in a few spots for performance reasons, by using
LIKE "%editor%" against serialized data. This very unpoetic code is the epitomy of the overly complex capabilities system.
But, we’ll no longer be storing capabilities, or multiple roles. Thus, we can now have our usermeta value be a simple text value with the name of the role. Two example queries:
SELECT * FROM $wpdb->users u INNER JOIN $wpdb->usermeta m WHERE u.id = m.user_id AND m.meta_key = 'wp_capabilities' AND m.meta_value = 'Editor';
SELECT user_id FROM $wpdb->usermeta WHERE meta_key = 'wp_capabilities' AND m.meta_value = 'Editor';
Now that is poetic code, and it would scale much better in an environment with many users. (When I mean many, I mean many, many thousands.) And I’ll point out here that there have been suggestions for a new table, to get this out of usermeta entirely.
The proposed overhaul is controversial, which explains its postponement for multiple releases, but appears to be gaining traction for 3.1. As the wp-hackers thread progressed, I suggested that the proposed overhaul could always end up being watered down a bit before entering core as a compromise, notwithstanding rather solid support from the core developers for the proposal on the table.
That said, I cannot imagine that user-specific capabilities would remain in core. If there is a compromise, it would end up being multiple roles, because we could still avoid serialized data, by using the same meta key multiple times, with different meta values. The one thing I don’t believe we would want to do is take an overly complicated system and oversimplify it, especially given the growth and scale of WordPress as a CMS. If we can sanely implement multiple roles in the new schema, keeping them may be something to consider.
The counter-argument is that we have a chance to simplify the capabilities API to make it manageable, and we should take advantage of that. We could sanely build a core plugin for role management. Multiple roles not supported in the user interface, nor would they be supported in such a plugin, leaving them to be the only piece of the API that would not be exposed.
That brings me to a final question: Is anyone actually using multiple roles? Because I’m noticing that WP_User::remove_role() was broken and WP_User::add_role() had problems as well. Both were fixed in the 3.0 development cycle after having issues for a few versions. Makes you wonder.