This is just some idea I had. Read it and post a comment if you think it can help or if you have questions.
1 - Introduction
This project's main goal is to provide a better access control on class methods in PHP, well beyond the current private/protected/public access levels available today.
In a typical OO design, objects must often be called only from a limited number of classes. Examples include layered design, plugin architectures, and many other patterns. In PHP 5 today, there is no simple way to enforce such a policy. If a method is to be accessed by a non ancestor/descendant class, it must be declared public. There is no 'friend' class concept as in other languages, nor any alternative fine access control mechanism.
So, the usual ways to 'control' access to restricted classes (beyond PHP built-in access types) are quite basic and essentially manual : include the class definition only in the files which will really have to use them, and manually control where the objects are created and where their references are stored (control object visibility). Being essentially human-based, these methods are often inefficient, hard to trust, and generally don't work well on large projects, especially when classes are autoloaded, or when trying to restrict access to static methods and functions.
The features described in this document provide a new list-based access restriction mechanism for methods and functions.
Note: At least called_from() and restrict_access() could be implemented in PHP (using 'Exception::getTrace()' ) but it would be much too slow. So, they will be provided in a C extension.
2 - Functions
Note: A flag argument applies to every following argument.
2.1 - called_from()
Checks if at least one of the arguments matches the current caller. Returns
true/false.
boolean called_from($arg, $arg, $flag, $flag, $arg, $flag, $arg, ...) |
Note: Each $arg or $flag can also be an array of arguments.
2.2 - restrict_access()
This is the core feature. This function enforces access restriction. It calls called_from() and throws an exception if
called_from() returns false :
void restrict_access(arg, arg, flag, flag, arg, flag, arg, ...) |
Note: Each arg or flag can also be an array of args.
For performance reasons, this feature can be disabled globally through an INI parameter : setting 'oo_access_restriction' to 'off' in php.ini disables access restriction, forcing the restrict_access() function to accept everything without any check. This way, access restrictions can be turned on in development and validation phases, and then turned off in the production environment, without any code modification.
2.3 - get_caller([$level])
Returns the calling environment/context :
| '' (empty string) | We were called from the main (upper level) |
| <string> | A function name |
| Array(string class, string method) | A static method (class::method) |
| Array(object obj, string method) | An object method (obj->method) |
| false | We are at the main/upper level |
This function is useful, for instance, when we want to call a method in the object we were called from (as in a plugin/factory pattern).
$level is a positive integer and counts from the current execution level : 1 means the immediate/first calling level, 2 means one level upper,... Default is 1. If the requested $level is greater than the current calling level, the function returns false.
Note that, for very complex cases, called_from() and restrict_access() replacements can be implemented in PHP, using get_caller() with explicit $level arguments to explore the call stack.
You may also note that this function, afaik, cannot be implemented in PHP (we could get everything except the object instance).
3 - Arguments
Each argument of called_from() and restrict_access() is either :
| self | The current class (corresponds to private access) |
| parent | The parent class |
| <An object instance> | A method from this specific object instance |
| $this | The current object |
| '<function>' | A function |
| '' (empty string) or null | Upper level (main) |
or a class/method combination as a string : '<class>::<method>', where <class> can be :
| 'parent' | The parent class |
| 'parent+' | An ancestor class (any level) |
| 'self' | The current class |
| 'self+' | The current class or an ancestor |
| 'self-' | The current class or a descendant |
| '-' | A descendant of the current class |
| 'protected' | A parent, descendant, or self (corresponds to protected access) |
| '<string>' | A named class |
| '<string>-' | A named class or one of its descendants (instanceof <class>) |
| '*' | Any class |
or an array(<class> , <method>), where <class> can be any of the elements listed in the tables above except those highlighted in yellow.
In the above tables, <method> is a string of the form :
- <Empty string> or '*' : Every methods
- <method>[,<method,...] : A list of methods, separated by ',' chars.
Flags are pre-defined integer constants. The following flags are supported :
- CALLER_LEVEL_ANY : Check if one of the callers (any level) matches the condition.
- CALLER_LEVEL_FIRST : the opposite, we check only the first level (the real caller). This is the default.
Note that the flags are not OR-ed together. They are provided as distinct arguments, and a flag affects every argument coming after it.
4 - Examples
First, a poor man's 'factory' example : let's say that 'Item' objects must be created only from the 'Item_factory' class :
class Item_factory |
A more complex example with a minimal plugin system :
class Plugin_Mgr |
There are dozens of variations on this theme. If we want to do that with PHP today, there is no simple way to restrict access to public methods (which MUST remain public), and the Plugin_Mgr object must send $this as an argument to info(), in order to be called back.
Making the plugin classes descendants of the plugin class, just to allow them to access protected methods is an ugly workaround : I still prefer making methods public.






