Home arrow Projects arrow Access control

Access control Print E-mail
User Rating: / 1
PoorBest 
Last Updated: Saturday, 26 May 2007

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
{
private $items=array();

public function create_item($name)
{
$items[$name]=new Item();    // Works
}
... //--- End of Item_factory class

class Item
{
public function __construct()
{
restrict_access('Manager');
}
... //--- End of Item class

function Wrong()
{
//-- Error exception : 'Access forbidden: Cannot call Item::__construct from Wrong'
$i=new Item();    
...

A more complex example with a minimal plugin system :

class Plugin_Mgr
{
private $plugins=array();

public function create_plugin($class,$name)
{
$this->plugins[$name]=new $class();
}

public function display($msg)
{
//-- Accept access from the registered plugin objects only (callback)
restrict_access($this->plugins);
echo "$msg\n";
}

public function plugin_info()
{
foreach($this->plugins as $plugin) $plugin->info();
}
...
} // End of class Plugin_Mgr

class A_Plugin
{

public function __construct()
{
restrict_access('Plugin_Mgr::create_plugin');
}

public function info()
{
//-- Could be more restrictive if always called from Plugin_Mgr::plugin_info()
restrict_access('Plugin_Mgr');
$caller=get_caller();
//-- No need to check more here, as restrict_access() already did it.
$mgr=$caller[0];
$mgr->display('My name is Bond, James Bond');
}
...

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.

Comments

Only registered users can write comments.
Please login or register.

Powered by AkoComment!


All site content is (C) F. Laupretre (wishlist) - Unauthorized reproduction forbidden without express written permission.
Joomla! is Free Software released under the GNU/GPL License. - Template design: JLM@joomlabox