faces.inc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. <?php
  2. /**
  3. * @file
  4. * Extendable Object Faces API. Provided by the faces module.
  5. */
  6. if (!interface_exists('FacesExtenderInterface', FALSE)) {
  7. /**
  8. * Interface for extenders.
  9. */
  10. interface FacesExtenderInterface {
  11. /**
  12. * Constructs an instance of the extender.
  13. */
  14. public function __construct(FacesExtendable $object);
  15. /**
  16. * Returns the extended object.
  17. */
  18. public function getExtendable();
  19. }
  20. /**
  21. * The Exception thrown by the FacesExtendable.
  22. */
  23. class FacesExtendableException extends ErrorException {}
  24. }
  25. if (!class_exists('FacesExtender', FALSE)) {
  26. /**
  27. * A common base class for FacesExtenders.
  28. *
  29. * Extenders may access protected methods and properties of the extendable
  30. * using the property() and call() methods.
  31. */
  32. abstract class FacesExtender implements FacesExtenderInterface {
  33. /**
  34. * @var FacesExtendable
  35. */
  36. protected $object;
  37. public function __construct(FacesExtendable $object) {
  38. $this->object = $object;
  39. }
  40. /**
  41. * Returns the extended object.
  42. */
  43. public function getExtendable() {
  44. return $this->object;
  45. }
  46. /**
  47. * Makes protected properties of the extendable accessible.
  48. */
  49. protected function &property($name) {
  50. $var =& $this->object->property($name);
  51. return $var;
  52. }
  53. /**
  54. * Invokes any method on the extended object, including protected methods.
  55. *
  56. * @param string $name
  57. * The method name.
  58. * @param array $args
  59. * An array of arguments to pass to the method.
  60. */
  61. protected function call($name, array $args = array()) {
  62. return $this->object->call($name, $args);
  63. }
  64. }
  65. }
  66. if (!class_exists('FacesExtendable', FALSE)) {
  67. /**
  68. * An extendable base class.
  69. */
  70. abstract class FacesExtendable {
  71. protected $facesMethods = array();
  72. protected $faces = array();
  73. protected $facesIncludes = array();
  74. protected $facesClassInstances = array();
  75. static protected $facesIncluded = array();
  76. /**
  77. * Wraps calls to module_load_include() to prevent multiple inclusions.
  78. *
  79. * @see module_load_include()
  80. */
  81. protected static function load_include($args) {
  82. $args += array('type' => 'inc', 'module' => '', 'name' => NULL);
  83. $key = implode(':', $args);
  84. if (!isset(self::$facesIncluded[$key])) {
  85. self::$facesIncluded[$key] = TRUE;
  86. module_load_include($args['type'], $args['module'], $args['name']);
  87. }
  88. }
  89. /**
  90. * Magic method: Invoke the dynamically implemented methods.
  91. */
  92. public function __call($name, $arguments = array()) {
  93. if (isset($this->facesMethods[$name])) {
  94. $method = $this->facesMethods[$name];
  95. // Include code, if necessary.
  96. if (isset($this->facesIncludes[$name])) {
  97. self::load_include($this->facesIncludes[$name]);
  98. $this->facesIncludes[$name] = NULL;
  99. }
  100. if (isset($method[0])) {
  101. // We always pass the object reference and the name of the method.
  102. $arguments[] = $this;
  103. $arguments[] = $name;
  104. return call_user_func_array($method[0], $arguments);
  105. }
  106. // Call the method on the extender object, but don't use extender()
  107. // for performance reasons.
  108. if (!isset($this->facesClassInstances[$method[1]])) {
  109. $this->facesClassInstances[$method[1]] = new $method[1]($this);
  110. }
  111. return call_user_func_array(array($this->facesClassInstances[$method[1]], $name), $arguments);
  112. }
  113. $class = check_plain(get_class($this));
  114. throw new FacesExtendableException("There is no method $name for this instance of the class $class.");
  115. }
  116. /**
  117. * Returns the extender object for the given class.
  118. *
  119. * May be used to explicitly invoke a specific extender, e.g. a function
  120. * overriding a method may use that to explicitly invoke the original
  121. * extender.
  122. */
  123. public function extender($class) {
  124. if (!isset($this->facesClassInstances[$class])) {
  125. $this->facesClassInstances[$class] = new $class($this);
  126. }
  127. return $this->facesClassInstances[$class];
  128. }
  129. /**
  130. * Returns whether the object can face as the given interface.
  131. *
  132. * Returns whether the object can face as the given interface, thus it
  133. * returns TRUE if this object has been extended by an appropriate
  134. * implementation.
  135. *
  136. * @param $interface
  137. * Optional. An interface to test for. If it's omitted, all interfaces
  138. * that the object can be faced as are returned.
  139. *
  140. * @return bool
  141. * Whether the object can face as the interface or an array of interface
  142. * names.
  143. */
  144. public function facesAs($interface = NULL) {
  145. if (!isset($interface)) {
  146. return array_values($this->faces);
  147. }
  148. return in_array($interface, $this->faces) || $this instanceof $interface;
  149. }
  150. /**
  151. * Extend the object by a class to implement the given interfaces.
  152. *
  153. * @param $interface
  154. * The interface name or an array of interface names.
  155. * @param $className
  156. * The extender class, which has to implement the FacesExtenderInterface.
  157. * @param array $includes
  158. * An optional array describing the file to include before invoking the
  159. * class. The array entries known are 'type', 'module', and 'name'
  160. * matching the parameters of module_load_include(). Only 'module' is
  161. * required as 'type' defaults to 'inc' and 'name' to NULL.
  162. */
  163. public function extendByClass($interface, $className, array $includes = array()) {
  164. $parents = class_implements($className);
  165. if (!in_array('FacesExtenderInterface', $parents)) {
  166. throw new FacesExtendableException("The class " . check_plain($className) . " doesn't implement the FacesExtenderInterface.");
  167. }
  168. $interfaces = is_array($interface) ? $interface : array($interface);
  169. foreach ($interfaces as $interface) {
  170. if (!in_array($interface, $parents)) {
  171. throw new FacesExtendableException("The class " . check_plain($className) . " doesn't implement the interface " . check_plain($interface) . ".");
  172. }
  173. $this->faces[$interface] = $interface;
  174. $this->faces += class_implements($interface);
  175. $face_methods = get_class_methods($interface);
  176. $this->addIncludes($face_methods, $includes);
  177. foreach ($face_methods as $method) {
  178. $this->facesMethods[$method] = array(1 => $className);
  179. }
  180. }
  181. }
  182. /**
  183. * Extend the object by the given functions to implement the given
  184. * interface. There has to be an implementation function for each method of
  185. * the interface.
  186. *
  187. * @param $interface
  188. * The interface name or FALSE to extend the object without a given
  189. * interface.
  190. * @param array $callbacks
  191. * An array, where the keys are methods of the given interface and the
  192. * values the callback functions to use.
  193. * @param array $includes
  194. * An optional array to describe files to include before invoking the
  195. * callbacks. You may pass a single array describing one include for all
  196. * callbacks or an array of arrays, keyed by the method names. Look at the
  197. * extendByClass() $include parameter for more details about how to
  198. * describe a single file.
  199. */
  200. public function extend($interface, array $callbacks = array(), array $includes = array()) {
  201. $face_methods = $interface ? get_class_methods($interface) : array_keys($callbacks);
  202. if ($interface) {
  203. if (array_diff($face_methods, array_keys($callbacks))) {
  204. throw new FacesExtendableException("Missing methods for implementing the interface " . check_plain($interface) . ".");
  205. }
  206. $this->faces[$interface] = $interface;
  207. $this->faces += class_implements($interface);
  208. }
  209. $this->addIncludes($face_methods, $includes);
  210. foreach ($face_methods as $method) {
  211. $this->facesMethods[$method] = array(0 => $callbacks[$method]);
  212. }
  213. }
  214. /**
  215. * Override the implementation of an extended method.
  216. *
  217. * @param array $callbacks
  218. * An array of methods of the interface, that should be overridden, where
  219. * the keys are methods to override and the values the callback functions
  220. * to use.
  221. * @param array $includes
  222. * An optional array to describe files to include before invoking the
  223. * callbacks. You may pass a single array describing one include for all
  224. * callbacks or an array of arrays, keyed by the method names. Look at the
  225. * extendByClass() $include parameter for more details about how to
  226. * describe a single file.
  227. */
  228. public function override(array $callbacks = array(), array $includes = array()) {
  229. if (array_diff_key($callbacks, $this->facesMethods)) {
  230. throw new FacesExtendableException("A not implemented method is to be overridden.");
  231. }
  232. $this->addIncludes(array_keys($callbacks), $includes);
  233. foreach ($callbacks as $method => $callback) {
  234. $this->facesMethods[$method] = array(0 => $callback);
  235. }
  236. }
  237. /**
  238. * Adds in include files for the given methods while removing any old files.
  239. *
  240. * If a single include file is described, it's added for all methods.
  241. */
  242. protected function addIncludes($methods, $includes) {
  243. $includes = isset($includes['module']) && is_string($includes['module']) ? array_fill_keys($methods, $includes) : $includes;
  244. $this->facesIncludes = $includes + array_diff_key($this->facesIncludes, array_flip($methods));
  245. }
  246. /**
  247. * Only serialize what is really necessary.
  248. */
  249. public function __sleep() {
  250. return array('facesMethods', 'faces', 'facesIncludes');
  251. }
  252. /**
  253. * Destroys all references to created instances.
  254. *
  255. * Destroys all references to created instances so that PHP's garbage
  256. * collection can do its work. This is needed as PHP's gc has troubles with
  257. * circular references until PHP < 5.3.
  258. */
  259. public function destroy() {
  260. // Avoid circular references.
  261. $this->facesClassInstances = array();
  262. }
  263. /**
  264. * Makes protected properties accessible.
  265. */
  266. public function &property($name) {
  267. if (property_exists($this, $name)) {
  268. return $this->$name;
  269. }
  270. }
  271. /**
  272. * Invokes any method.
  273. *
  274. * This also allows to pass arguments by reference, so it may be used to
  275. * pass arguments by reference to dynamically extended methods.
  276. *
  277. * @param string $name
  278. * The method name.
  279. * @param array $args
  280. * An array of arguments to pass to the method.
  281. */
  282. public function call($name, array $args = array()) {
  283. if (method_exists($this, $name)) {
  284. return call_user_func_array(array($this, $name), $args);
  285. }
  286. return $this->__call($name, $args);
  287. }
  288. }
  289. }