Location: PHPKode > projects > Recess PHP Framework > recess/recess/lang/Object.class.php
<?php
Library::import('recess.lang.ClassDescriptor');
Library::import('recess.lang.AttachedMethod');
Library::import('recess.lang.WrappableAnnotation');
Library::import('recess.lang.BeforeAnnotation');
Library::import('recess.lang.AfterAnnotation');

/**
 * Object is the base class for extensible classes in the Recess.
 * Object introduces a standard mechanism for building a class
 * descriptor through reflection and the realization of Annotations.
 * Object also introduces the ability to attach methods to a class
 * at run-time.
 * 
 * Sub-classes of Object can introduce extensibility points 
 * with 'wrappable' methods. A wrappable method can be dynamically 'wrapped' 
 * by other methods which are called prior to or after the wrapped method.
 * 
 * Wrappable methods can be declared using a Wrappable annotation on the 
 * method being wrapped. The annotation takes a single parameter, which is
 * the desired name of the wrapped method. By convention the native PHP method
 * being wrapped is prefixed with 'wrapped', i.e.:
 *  class Foobar {
 *    /** !Wrappable foo * /
 *    function wrappedFoo() { ... }
 *  }
 *  $obj->foo();
 * 
 * Example usage of wrappable methods and a hypothetical "EchoWrapper" which
 * wraps a method by echo'ing strings before and after. 
 * 
 *   class Model extends Object {
 *     /** !Wrappable insert * /
 *     function wrappedInsert() { echo "Wrapped (insert)"; }
 *   }
 * 
 *   /** !EchoWrapper insert, Before: "Hello", After: "World" * /
 *   class Person extends Model {}
 * 
 *   $person = new Person();
 *   $person->insert();
 *   
 *   // Output:
 *   Hello
 *   Wrapped (insert)
 *   World
 * 
 * @author Kris Jordan <hide@address.com>
 * @copyright 2008, 2009 Kris Jordan
 * @package Recess PHP Framework
 * @license MIT
 * @link http://www.recessframework.org/
 */
abstract class Object {
	
	protected static $descriptors = array();
	
	/**
	 * Attach a method to a class. The result of this static method is the ability to
	 * call, on any instance of $attachOnClassName, a method named $attachedMethodAlias
	 * which delegates that method call to $providerInstance's $providerMethodName.
	 *
	 * @param string $attachOnClassName
	 * @param string $attachedMethodAlias
	 * @param object $providerInstance
	 * @param string $providerMethodName
	 */
	static function attachMethod($attachOnClassName, $attachedMethodAlias, $providerInstance, $providerMethodName) {
		self::getClassDescriptor($attachOnClassName)->attachMethod($attachOnClassName, $attachedMethodAlias, $providerInstance, $providerMethodName);
	}
	
	/**
	 * Wrap a method on a class. The result of this static method is the provided IWrapper
	 * implementation will be called before and after the wrapped method.
	 * 
	 * @param string $wrapOnClassName
	 * @param string $wrappableMethodName
	 * @param IWrapper $wrapper
	 */
	static function wrapMethod($wrapOnClassName, $wrappableMethodName, IWrapper $wrapper) {
		self::getClassDescriptor($wrapOnClassName)->addWrapper($wrappableMethodName, $wrapper);
	}
	
	/**
	 * Dynamic dispatch of function calls to attached methods.
	 *
	 * @param string $name
	 * @param array $arguments
	 * @return variant
	 */
	final function __call($name, $arguments) {
		$classDescriptor = self::getClassDescriptor($this);
		
		$attachedMethod = $classDescriptor->getAttachedMethod($name);
		if($attachedMethod !== false) {
			$object = $attachedMethod->object;
			$method = $attachedMethod->method;
			array_unshift($arguments, $this);
			$reflectedMethod = new ReflectionMethod($object, $method);
			return $reflectedMethod->invokeArgs($object, $arguments);
		} else {
			throw new RecessException('"' . get_class($this) . '" class does not contain a method or an attached method named "' . $name . '".', get_defined_vars());
		}
	}
	
	const RECESS_CLASS_KEY_PREFIX = 'Object::desc::';

	/**
	 * Return the ObjectInfo for provided Object instance.
	 *
	 * @param variant $classNameOrInstance - String Class Name or Instance of Recess Class
	 * @return ClassDescriptor
	 */
	final static protected function getClassDescriptor($classNameOrInstance) {
		if($classNameOrInstance instanceof Object) {
			$class = get_class($classNameOrInstance);
			$instance = $classNameOrInstance;
		} else {
			$class = $classNameOrInstance;
			if(class_exists($class, true)) {
				$reflectionClass = new ReflectionClass($class);
				if(!$reflectionClass->isAbstract()) {	
					$instance = new $class;
				} else {
					return new ClassDescriptor();
				}
			}
		}
		
		if(!isset(self::$descriptors[$class])) {		
			$cache_key = self::RECESS_CLASS_KEY_PREFIX . $class;
			$descriptor = Cache::get($cache_key);
			
			if($descriptor === false) {				
				if($instance instanceof Object) {
					$descriptor = call_user_func(array($class, 'buildClassDescriptor'), $class);
					
					Cache::set($cache_key, $descriptor);
					self::$descriptors[$class] = $descriptor;
				} else {
					throw new RecessException('ObjectRegistry only retains information on classes derived from Object. Class of type "' . $class . '" given.', get_defined_vars());
				}
			} else {
				self::$descriptors[$class] = $descriptor;
			}
		}
		
		return self::$descriptors[$class];
	}
	
	/**
	 * Retrieve an array of the attached methods for a particular class.
	 *
	 * @param variant $classNameOrInstance - String class name or instance of a Recess Class
	 * @return array
	 */
	final static function getAttachedMethods($classNameOrInstance) {
		$descriptor = self::getClassDescriptor($classNameOrInstance);
		return $descriptor->getAttachedMethods();
	}	
	
	/**
	 * Clear the descriptors cache.
	 */
	final static function clearDescriptors() {
		self::$descriptors = array();
	}
	
	/**
	 * Initialize a class' descriptor. Override to return a subclass specific descriptor.
	 * A subclass's descriptor may need to initialize certain properties. For example
	 * Model's descriptor has properties initialized for table, primary key, etc. The controller
	 * descriptor has a routes array initialized as empty.
	 * 
	 * @param $class string Name of class whose descriptor is being initialized.
	 * @return ClassDescriptor
	 */
	protected static function initClassDescriptor($class) {	return new ClassDescriptor(); }
		
	/**
	 * Prior to expanding the annotations for a class method this hook is called to give
	 * a subclass an opportunity to manipulate its descriptor. For example Controller
	 * uses this in able to create default routes for methods which do not have explicit
	 * Route annotations.
	 * 
	 * @param $class string Name of class whose descriptor is being initialized.
	 * @param $method ReflectionMethod
	 * @param $descriptor ClassDescriptor
	 * @param $annotations Array of annotations found on method.
	 * @return ClassDescriptor
	 */
	protected static function shapeDescriptorWithMethod($class, $method, $descriptor, $annotations) { return $descriptor; }
	
	/**
	 * Prior to expanding the annotations for a class property this hook is called to give
	 * a subclass an opportunity to manipulate its class descriptor. For example Model
	 * uses this to initialize the datastructure for a Property before a Column annotation
	 * applies metadata. 
	 * 
	 * @param $class string Name of class whose descriptor is being initialized.
	 * @param $property ReflectionProperty
	 * @param $descriptor ClassDescriptor
	 * @param $annotations Array of annotations found on method.
	 * @return ClassDescriptor
	 */
	protected static function shapeDescriptorWithProperty($class, $property, $descriptor, $annotations) { return $descriptor; }

	/**
	 * After all methods and properties of a class have been visited and annotations expanded
	 * this hook provides a sub-class a final opportunity to do post-processing and sanitization.
	 * For example, Model uses this hook to ensure consistency between model's descriptor
	 * and the actual database's columns.
	 * 
	 * @param $class
	 * @param $descriptor
	 * @return ClassDescriptor
	 */
	protected static function finalClassDescriptor($class, $descriptor) { return $descriptor; }
	
	/**
	 * Builds a class' metadata structure (Class Descriptor through reflection 
	 * and expansion of annotations. Hooks are provided in a Strategy Pattern-like 
	 * fashion to allow subclasses to influence various points in the pipeline of 
	 * building a class descriptor (initialization, discovery of method, discovery of
	 * property, finalization). 
	 * 
	 * @param $class Name of class whose descriptor is being built.
	 * @return ClassDescriptor
	 */
	protected static function buildClassDescriptor($class) {
		$descriptor = call_user_func(array($class, 'initClassDescriptor'), $class);

		try {
			$reflection = new RecessReflectionClass($class);
		} catch(ReflectionException $e) {
			throw new RecessException('Class "' . $class . '" has not been declared.', get_defined_vars());
		}
		
		foreach ($reflection->getAnnotations() as $annotation) {
			$annotation->expandAnnotation($class, $reflection, $descriptor);
		}
		
		foreach($reflection->getMethods(false) as $method) {
			$annotations = $method->getAnnotations();
			$descriptor = call_user_func(array($class, 'shapeDescriptorWithMethod'), $class, $method, $descriptor, $annotations);
			foreach($annotations as $annotation) {
				$annotation->expandAnnotation($class, $method, $descriptor);
			}
		}
		
		foreach($reflection->getProperties(false) as $property) {
			$annotations = $property->getAnnotations();
			$descriptor = call_user_func(array($class, 'shapeDescriptorWithProperty'), $class, $property, $descriptor, $annotations);
			foreach($annotations as $annotation) {
				$annotation->expandAnnotation($class, $property, $descriptor);
			}
		}
		
		$descriptor = call_user_func(array($class, 'finalClassDescriptor'), $class, $descriptor);
		
		return $descriptor;
	}	
	
}

?>
Return current item: Recess PHP Framework