PageRenderTime 79ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 0ms

/recess/recess/lang/Object.class.php

https://github.com/eugenix/recess
PHP | 266 lines | 100 code | 27 blank | 139 comment | 12 complexity | a94f9a74079e290072a51adee37404de MD5 | raw file
  1. <?php
  2. Library::import('recess.lang.ClassDescriptor');
  3. Library::import('recess.lang.AttachedMethod');
  4. Library::import('recess.lang.WrappableAnnotation');
  5. Library::import('recess.lang.BeforeAnnotation');
  6. Library::import('recess.lang.AfterAnnotation');
  7. /**
  8. * Object is the base class for extensible classes in the Recess.
  9. * Object introduces a standard mechanism for building a class
  10. * descriptor through reflection and the realization of Annotations.
  11. * Object also introduces the ability to attach methods to a class
  12. * at run-time.
  13. *
  14. * Sub-classes of Object can introduce extensibility points
  15. * with 'wrappable' methods. A wrappable method can be dynamically 'wrapped'
  16. * by other methods which are called prior to or after the wrapped method.
  17. *
  18. * Wrappable methods can be declared using a Wrappable annotation on the
  19. * method being wrapped. The annotation takes a single parameter, which is
  20. * the desired name of the wrapped method. By convention the native PHP method
  21. * being wrapped is prefixed with 'wrapped', i.e.:
  22. * class Foobar {
  23. * /** !Wrappable foo * /
  24. * function wrappedFoo() { ... }
  25. * }
  26. * $obj->foo();
  27. *
  28. * Example usage of wrappable methods and a hypothetical "EchoWrapper" which
  29. * wraps a method by echo'ing strings before and after.
  30. *
  31. * class Model extends Object {
  32. * /** !Wrappable insert * /
  33. * function wrappedInsert() { echo "Wrapped (insert)"; }
  34. * }
  35. *
  36. * /** !EchoWrapper insert, Before: "Hello", After: "World" * /
  37. * class Person extends Model {}
  38. *
  39. * $person = new Person();
  40. * $person->insert();
  41. *
  42. * // Output:
  43. * Hello
  44. * Wrapped (insert)
  45. * World
  46. *
  47. * @author Kris Jordan <krisjordan@gmail.com>
  48. * @copyright 2008, 2009 Kris Jordan
  49. * @package Recess PHP Framework
  50. * @license MIT
  51. * @link http://www.recessframework.org/
  52. */
  53. abstract class Object {
  54. protected static $descriptors = array();
  55. /**
  56. * Attach a method to a class. The result of this static method is the ability to
  57. * call, on any instance of $attachOnClassName, a method named $attachedMethodAlias
  58. * which delegates that method call to $providerInstance's $providerMethodName.
  59. *
  60. * @param string $attachOnClassName
  61. * @param string $attachedMethodAlias
  62. * @param object $providerInstance
  63. * @param string $providerMethodName
  64. */
  65. static function attachMethod($attachOnClassName, $attachedMethodAlias, $providerInstance, $providerMethodName) {
  66. self::getClassDescriptor($attachOnClassName)->attachMethod($attachOnClassName, $attachedMethodAlias, $providerInstance, $providerMethodName);
  67. }
  68. /**
  69. * Wrap a method on a class. The result of this static method is the provided IWrapper
  70. * implementation will be called before and after the wrapped method.
  71. *
  72. * @param string $wrapOnClassName
  73. * @param string $wrappableMethodName
  74. * @param IWrapper $wrapper
  75. */
  76. static function wrapMethod($wrapOnClassName, $wrappableMethodName, IWrapper $wrapper) {
  77. self::getClassDescriptor($wrapOnClassName)->addWrapper($wrappableMethodName, $wrapper);
  78. }
  79. /**
  80. * Dynamic dispatch of function calls to attached methods.
  81. *
  82. * @param string $name
  83. * @param array $arguments
  84. * @return variant
  85. */
  86. final function __call($name, $arguments) {
  87. $classDescriptor = self::getClassDescriptor($this);
  88. $attachedMethod = $classDescriptor->getAttachedMethod($name);
  89. if($attachedMethod !== false) {
  90. $object = $attachedMethod->object;
  91. $method = $attachedMethod->method;
  92. array_unshift($arguments, $this);
  93. $reflectedMethod = new ReflectionMethod($object, $method);
  94. return $reflectedMethod->invokeArgs($object, $arguments);
  95. } else {
  96. throw new RecessException('"' . get_class($this) . '" class does not contain a method or an attached method named "' . $name . '".', get_defined_vars());
  97. }
  98. }
  99. const RECESS_CLASS_KEY_PREFIX = 'Object::desc::';
  100. /**
  101. * Return the ObjectInfo for provided Object instance.
  102. *
  103. * @param variant $classNameOrInstance - String Class Name or Instance of Recess Class
  104. * @return ClassDescriptor
  105. */
  106. final static protected function getClassDescriptor($classNameOrInstance) {
  107. if($classNameOrInstance instanceof Object) {
  108. $class = get_class($classNameOrInstance);
  109. $instance = $classNameOrInstance;
  110. } else {
  111. $class = $classNameOrInstance;
  112. if(class_exists($class, true)) {
  113. $reflectionClass = new ReflectionClass($class);
  114. if(!$reflectionClass->isAbstract()) {
  115. $instance = new $class;
  116. } else {
  117. return new ClassDescriptor();
  118. }
  119. }
  120. }
  121. if(!isset(self::$descriptors[$class])) {
  122. $cache_key = self::RECESS_CLASS_KEY_PREFIX . $class;
  123. $descriptor = Cache::get($cache_key);
  124. if($descriptor === false) {
  125. if($instance instanceof Object) {
  126. $descriptor = call_user_func(array($class, 'buildClassDescriptor'), $class);
  127. Cache::set($cache_key, $descriptor);
  128. self::$descriptors[$class] = $descriptor;
  129. } else {
  130. throw new RecessException('ObjectRegistry only retains information on classes derived from Object. Class of type "' . $class . '" given.', get_defined_vars());
  131. }
  132. } else {
  133. self::$descriptors[$class] = $descriptor;
  134. }
  135. }
  136. return self::$descriptors[$class];
  137. }
  138. /**
  139. * Retrieve an array of the attached methods for a particular class.
  140. *
  141. * @param variant $classNameOrInstance - String class name or instance of a Recess Class
  142. * @return array
  143. */
  144. final static function getAttachedMethods($classNameOrInstance) {
  145. $descriptor = self::getClassDescriptor($classNameOrInstance);
  146. return $descriptor->getAttachedMethods();
  147. }
  148. /**
  149. * Clear the descriptors cache.
  150. */
  151. final static function clearDescriptors() {
  152. self::$descriptors = array();
  153. }
  154. /**
  155. * Initialize a class' descriptor. Override to return a subclass specific descriptor.
  156. * A subclass's descriptor may need to initialize certain properties. For example
  157. * Model's descriptor has properties initialized for table, primary key, etc. The controller
  158. * descriptor has a routes array initialized as empty.
  159. *
  160. * @param $class string Name of class whose descriptor is being initialized.
  161. * @return ClassDescriptor
  162. */
  163. protected static function initClassDescriptor($class) { return new ClassDescriptor(); }
  164. /**
  165. * Prior to expanding the annotations for a class method this hook is called to give
  166. * a subclass an opportunity to manipulate its descriptor. For example Controller
  167. * uses this in able to create default routes for methods which do not have explicit
  168. * Route annotations.
  169. *
  170. * @param $class string Name of class whose descriptor is being initialized.
  171. * @param $method ReflectionMethod
  172. * @param $descriptor ClassDescriptor
  173. * @param $annotations Array of annotations found on method.
  174. * @return ClassDescriptor
  175. */
  176. protected static function shapeDescriptorWithMethod($class, $method, $descriptor, $annotations) { return $descriptor; }
  177. /**
  178. * Prior to expanding the annotations for a class property this hook is called to give
  179. * a subclass an opportunity to manipulate its class descriptor. For example Model
  180. * uses this to initialize the datastructure for a Property before a Column annotation
  181. * applies metadata.
  182. *
  183. * @param $class string Name of class whose descriptor is being initialized.
  184. * @param $property ReflectionProperty
  185. * @param $descriptor ClassDescriptor
  186. * @param $annotations Array of annotations found on method.
  187. * @return ClassDescriptor
  188. */
  189. protected static function shapeDescriptorWithProperty($class, $property, $descriptor, $annotations) { return $descriptor; }
  190. /**
  191. * After all methods and properties of a class have been visited and annotations expanded
  192. * this hook provides a sub-class a final opportunity to do post-processing and sanitization.
  193. * For example, Model uses this hook to ensure consistency between model's descriptor
  194. * and the actual database's columns.
  195. *
  196. * @param $class
  197. * @param $descriptor
  198. * @return ClassDescriptor
  199. */
  200. protected static function finalClassDescriptor($class, $descriptor) { return $descriptor; }
  201. /**
  202. * Builds a class' metadata structure (Class Descriptor through reflection
  203. * and expansion of annotations. Hooks are provided in a Strategy Pattern-like
  204. * fashion to allow subclasses to influence various points in the pipeline of
  205. * building a class descriptor (initialization, discovery of method, discovery of
  206. * property, finalization).
  207. *
  208. * @param $class Name of class whose descriptor is being built.
  209. * @return ClassDescriptor
  210. */
  211. protected static function buildClassDescriptor($class) {
  212. $descriptor = call_user_func(array($class, 'initClassDescriptor'), $class);
  213. try {
  214. $reflection = new RecessReflectionClass($class);
  215. } catch(ReflectionException $e) {
  216. throw new RecessException('Class "' . $class . '" has not been declared.', get_defined_vars());
  217. }
  218. foreach ($reflection->getAnnotations() as $annotation) {
  219. $annotation->expandAnnotation($class, $reflection, $descriptor);
  220. }
  221. foreach($reflection->getMethods(false) as $method) {
  222. $annotations = $method->getAnnotations();
  223. $descriptor = call_user_func(array($class, 'shapeDescriptorWithMethod'), $class, $method, $descriptor, $annotations);
  224. foreach($annotations as $annotation) {
  225. $annotation->expandAnnotation($class, $method, $descriptor);
  226. }
  227. }
  228. foreach($reflection->getProperties(false) as $property) {
  229. $annotations = $property->getAnnotations();
  230. $descriptor = call_user_func(array($class, 'shapeDescriptorWithProperty'), $class, $property, $descriptor, $annotations);
  231. foreach($annotations as $annotation) {
  232. $annotation->expandAnnotation($class, $property, $descriptor);
  233. }
  234. }
  235. $descriptor = call_user_func(array($class, 'finalClassDescriptor'), $class, $descriptor);
  236. return $descriptor;
  237. }
  238. }
  239. ?>