At Internet Vision Techonologies, we develop a service oriented web application.
Given it is already split nicely into services, we decided to expose certain functionality
externally as web services. In order to do this, we use a simplistic implementation of AOP
in PHP, using the PHP reflection API, to permission certain functions. Each function
which is to be exposed as a web service requires an attribute called @access.
If this is not present, the function is not exposed as a web service. If it is defined
then the currently logged in user must hold all of the access credentials specified.
require_once( "AttributeFinder.php" );
class RecordService
{
/**
* This function will pretend to create a record, but will restrict access
* to users with a position of 'admin' and 'user'.
* @access admin Requires admin users to create a new record
* @access user Also must be of type user
*/
public function createRecord( $name )
{
echo "Creating record...\n";
}
}
$attributeFinder = new AttributeFinder( "RecordService", "createRecord" );
$requiredAccess = $attributeFinder->getAccess();
echo "Only go ahead with the invocation of createRecord() if the current user is in all of these groups:\n";
echo implode( ", ", $requiredAccess ) . "\n";
The above code shows an example usage of this technique. The class AttributeFinder is just a very simple class which utilises PHP's reflection API to extract the doc comments and then perform a little bit of string processing to extract the attributes.
/**
* Given a class or a method, looks for particular attributes inside the doc-comments for that class/method.
* Also has a few helper functions for tasks which this was introduced for (i.e. method authentication).
* @author Peter Serwylo (peter@ivt.com.au)
*/
class AttributeFinder
{
private $className;
private $methodName;
private $docComments;
public function __construct( $class, $method = "")
{
if ( strlen( trim( $method ) ) )
{
$reflect = new ReflectionMethod( $class, $method );
}
else
{
$reflect = new ReflectionClass( $class );
}
$this->className = $class;
$this->methodName = $method;
$this->docComments = $reflect->getDocComment();
}
/**
* Looks for the attribute in the doc comments of the specified class/function.
* Returns an array, because there can be multiple's of any attribute
* (like in standard PHP: param, in your application: access).
* @param string $attribute
* @return array List of attribute values matching $attribute.
*/
private function getAttributes( $attribute )
{
if ( substr( $attribute, 0, 1 ) != "@" )
{
$attribute = "@" . $attribute;
}
$tag = trim( $tag );
$matches = array();
preg_match_all( "/" . $attribute . "(.*)/", $this->docComments, $matches );
if ( isset( $matches[1] ) )
{
return $matches[1];
}
return array();
}
/**
* Parses the attributes, collecting every "@access" tag.
* Each tag can have a comment as well, but only the first "word" is examined and expected to be an int.
* In the example tag of: "@access admin Super user can have access because ..."
* The 'admin' value will get extracted and added to the array of required access.
* @return array List of permission's which are allowed to access this method.
*/
public function getAccess()
{
$matches = $this->getAttributes( "@access" );
$access = array();
foreach( $matches as $match )
{
$match = trim( $match );
if ( strlen( $match ) == 0 )
{
$error =
"Invalid @access syntax for " . $this->className . "::" . $this->methodName . "(). " .
"Expecting an integer as the first value after the attribute tag, '" . $parts[0] . "' given.";
trigger_error( $error, E_USER_WARNING );
}
else
{
$parts = split( " ", $match, 2 );
$permissionRequired = trim( $parts[0] );
$access[] = $permissionRequired;
}
}
return $access;
}
}