Real World Example

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;
	}

}