PageRenderTime 87ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/htdocs/wp-content/plugins/nextgen-gallery/pope/lib/class.extensibleobject.php

https://github.com/Fishgate/privatecollectionswp
PHP | 1419 lines | 766 code | 205 blank | 448 comment | 106 complexity | d418e066caba0aa6c52413035f79eedc MD5 | raw file
  1. <?php
  2. define('__EXTOBJ_STATIC__', '__STATICALLY_CALLED__');
  3. define('__EXTOBJ_NO_INIT__', '__NO_INIT__');
  4. if (!defined('EXTENSIBLE_OBJECT_ENFORCE_INTERFACES')) {
  5. define('EXTENSIBLE_OBJECT_ENFORCE_INTERFACES', TRUE);
  6. }
  7. /**
  8. * Provides helper methods for Pope objects
  9. */
  10. class PopeHelpers
  11. {
  12. /**
  13. * Merges two associative arrays
  14. * @param array $a1
  15. * @param array $a2
  16. * @return array
  17. */
  18. function array_merge_assoc($a1, $a2, $skip_empty=FALSE)
  19. {
  20. if ($a2) {
  21. foreach ($a2 as $key => $value) {
  22. if ($skip_empty && $value === '' OR is_null($value)) continue;
  23. if (isset($a1[$key])) {
  24. if (is_array($value)) {
  25. $a1[$key] = $this->array_merge_assoc($a1[$key], $value);
  26. }
  27. else {
  28. $a1[$key] = $value;
  29. }
  30. }
  31. else $a1[$key] = $value;
  32. }
  33. }
  34. return $a1;
  35. }
  36. /**
  37. * Returns TRUE if a property is empty
  38. * @param string $var
  39. * @return boolean
  40. */
  41. function is_empty($var, $element=FALSE)
  42. {
  43. if (is_array($var) && $element) {
  44. if (isset($var[$element])) $var = $var[$element];
  45. else $var = FALSE;
  46. }
  47. return (is_null($var) OR (is_string($var) AND strlen($var) == 0) OR $var === FALSE);
  48. }
  49. }
  50. /**
  51. * An ExtensibleObject can be extended at runtime with methods from another
  52. * class.
  53. *
  54. * - Mixins may be added or removed at any time during runtime
  55. * - The path to the mixin is cached so that subsequent method calls are
  56. * faster
  57. * - Pre and post hooks can be added or removed at any time during runtime.
  58. * - Each method call has a list of associated properties that can be modified
  59. * by pre/post hooks, such as: return_value, run_pre_hooks, run_post_hooks, etc
  60. * - Methods can be replaced by other methods at runtime
  61. * - Objects can implement interfaces, and are constrained to implement all
  62. * methods as defined by the interface
  63. * - All methods are public. There's no added security by having private/protected
  64. * members, as monkeypatching can always expose any method. Instead, protect
  65. * your methods using obscurity. Conventionally, use an underscore to define
  66. * a method that's private to an API
  67. */
  68. class ExtensibleObject extends PopeHelpers
  69. {
  70. const METHOD_PROPERTY_RUN='run';
  71. const METHOD_PROPERTY_RUN_POST_HOOKS='run_post_hooks';
  72. const METHOD_PROPERTY_RUN_PRE_HOOKS='run_pre_hooks';
  73. const METHOD_PROPERTY_RETURN_VALUE='return_value';
  74. var $_mixins = array();
  75. var $_mixin_priorities = array();
  76. var $_pre_hooks = array();
  77. var $_global_pre_hooks = array();
  78. var $_global_post_hooks= array();
  79. var $_post_hooks = array();
  80. var $_method_map_cache = array();
  81. var $_interfaces = array();
  82. var $_overrides = array();
  83. var $_aliases = array();
  84. var $_method_properties = array();
  85. var $_throw_error = TRUE;
  86. var $_wrapped_instance = FALSE;
  87. var $object = NULL;
  88. var $_disabled_pre_hooks = array();
  89. var $_disabled_post_hooks = array();
  90. var $_disabled_mixins = array();
  91. /**
  92. * Defines a new ExtensibleObject. Any subclass should call this constructor.
  93. * Subclasses are expected to provide the following:
  94. * define_instance() - adds extensions which provide instance methods
  95. * define_class() - adds extensions which provide static methods
  96. * initialize() - used to initialize the state of the object
  97. */
  98. function __construct()
  99. {
  100. // Mixins access their parent class by accessing $this->object.
  101. // Sometimes users mistakenly use $this->object within the parent object
  102. // itself. As it's becoming a common mistake, we define a $this->object
  103. // property which points to the current instance (itself)
  104. $this->object = $this;
  105. $args = func_get_args();
  106. $define_instance = TRUE;
  107. $init_instance = TRUE;
  108. // The first argument could be a flag to ExtensibleObject
  109. // which indicates that only static-like methods will be called
  110. if (count($args) >= 1) {
  111. $first_arg = $args[0];
  112. if (is_string($first_arg)) {
  113. switch ($first_arg) {
  114. case __EXTOBJ_STATIC__:
  115. {
  116. $define_instance = FALSE;
  117. $init_instance = FALSE;
  118. if (method_exists($this, 'define_class')) {
  119. $this->call_callback($this, 'define_class', $args);
  120. }
  121. elseif (method_exists($this, 'define_static')) {
  122. $this->call_callback($this, 'define_static', $args);
  123. }
  124. break;
  125. }
  126. case __EXTOBJ_NO_INIT__:
  127. {
  128. $init_instance = FALSE;
  129. break;
  130. }
  131. }
  132. }
  133. }
  134. // Are we to define instance methods?
  135. if ($define_instance)
  136. {
  137. if (method_exists($this, 'define_instance'))
  138. {
  139. $reflection = new ReflectionMethod($this, 'define_instance');
  140. $reflection->invokeArgs($this, $args);
  141. // call_user_func_array(array($this, 'define_instance'), $args);
  142. }
  143. elseif (method_exists($this, 'define')) {
  144. $reflection = new ReflectionMethod($this, 'define');
  145. $reflection->invokeArgs($this, $args);
  146. // call_user_func_array(array($this, 'define'), $args);
  147. }
  148. if (EXTENSIBLE_OBJECT_ENFORCE_INTERFACES) $this->_enforce_interface_contracts();
  149. if ($init_instance)
  150. {
  151. // Initialize the state of the object
  152. if (method_exists($this, 'initialize')) {
  153. $reflection = new ReflectionMethod($this, 'initialize');
  154. $reflection->invokeArgs($this, $args);
  155. // call_user_func_array(array($this, 'initialize'), $args);
  156. }
  157. }
  158. }
  159. }
  160. /**
  161. * Disabled prehooks for a particular method
  162. * @param string $method
  163. */
  164. function disable_pre_hooks($method)
  165. {
  166. $this->_disabled_pre_hooks[] = $method;
  167. return $this;
  168. }
  169. /**
  170. * Enable prehooks for a particular method
  171. * @param string $method
  172. */
  173. function enable_pre_hooks($method)
  174. {
  175. $index = array_search($method, $this->_disabled_pre_hooks);
  176. if ($index !== FALSE) {
  177. unset($this->_disabled_pre_hooks[$index]);
  178. }
  179. return $this;
  180. }
  181. /**
  182. * Disabled posthooks for a particular method
  183. * @param string $method
  184. */
  185. function disable_post_hooks($method)
  186. {
  187. $this->_disabled_post_hooks[] = $method;
  188. return $this;
  189. }
  190. /**
  191. * Enable post-hooks for a particular method
  192. * @param string $method
  193. */
  194. function enable_post_hooks($method)
  195. {
  196. $index = array_search($method, $this->_disabled_post_hooks);
  197. if ($index !== FALSE) {
  198. unset($this->_disabled_post_hooks[$index]);
  199. }
  200. return $this;
  201. }
  202. /**
  203. * Determines if post hooks are enabled for a particular method
  204. * @param string $method
  205. * @return bool
  206. */
  207. function are_post_hooks_enabled($method)
  208. {
  209. return !empty($this->_post_hooks) && (!in_array($method, $this->_disabled_post_hooks));
  210. }
  211. /**
  212. * Determines if pre hooks are enabled for a particular method
  213. * @param string $method
  214. * @return bool
  215. */
  216. function are_pre_hooks_enabled($method)
  217. {
  218. return !empty($this->_pre_hooks) && (!in_array($method, $this->_disabled_pre_hooks));
  219. }
  220. /**
  221. * Adds an extension class to the object. The extension provides
  222. * methods for this class to expose as it's own
  223. * @param string $class
  224. */
  225. function add_mixin($class, $instantiate=FALSE)
  226. {
  227. $retval = TRUE;
  228. if (!$this->has_mixin($class)) {
  229. // We used to instantiate the class, but I figure
  230. // we might as well wait till the method is called to
  231. // save memory. Instead, the _call() method calls the
  232. // _instantiate_mixin() method below.
  233. $this->_mixins[$class] = FALSE; // new $class();
  234. array_unshift($this->_mixin_priorities, $class);
  235. $this->_flush_cache();
  236. // Should we instantiate the object now?
  237. if ($instantiate) $this->_instantiate_mixin($class);
  238. }
  239. else $retval = FALSE;
  240. return $retval;
  241. }
  242. /**
  243. * Determines if a mixin has been added to this class
  244. * @param string $klass
  245. * @return bool
  246. */
  247. function has_mixin($klass)
  248. {
  249. return (isset($this->_mixins[$klass]));
  250. }
  251. /**
  252. * Stores the instantiated class
  253. * @param string $class
  254. * @return mixed
  255. */
  256. function _instantiate_mixin($class)
  257. {
  258. $retval = FALSE;
  259. if ($this->_mixins[$class])
  260. $retval = $this->_mixins[$class];
  261. else {
  262. $obj= new $class();
  263. $obj->object = &$this;
  264. $retval = $this->_mixins[$class] = &$obj;
  265. if (method_exists($obj, 'initialize')) $obj->initialize();
  266. }
  267. return $retval;
  268. }
  269. /**
  270. * Deletes an extension from the object. The methods provided by that
  271. * extension are no longer available for the object
  272. * @param string $class
  273. */
  274. function del_mixin($class)
  275. {
  276. unset($this->_mixins[$class]);
  277. $index = array_search($class, $this->_mixin_priorities);
  278. if ($index !== FALSE) {
  279. unset($this->_mixin_priorities[$index]);
  280. foreach ($this->_disabled_mixins as $method => $disabled_mixins) {
  281. $index = array_search($class, $disabled_mixins);
  282. if (is_int($index)) unset($this->_disabled_mixins[$method][$index]);
  283. }
  284. $this->_flush_cache();
  285. }
  286. }
  287. function remove_mixin($class)
  288. {
  289. $this->del_mixin($class);
  290. }
  291. /**
  292. * Replaces an extension methods with that of another class.
  293. * @param string $method
  294. * @param string $class
  295. * @param string $new_method
  296. */
  297. function replace_method($method, $class, $new_method=FALSE)
  298. {
  299. if (!$new_method) $new_method = $method;
  300. $this->_overrides[$method] = $class;
  301. $this->add_pre_hook($method, "replacement_{$method}_{$class}_{$new_method}", $class, $new_method);
  302. $this->_flush_cache();
  303. }
  304. /**
  305. * Restores a method that was replaced by a former call to replace_method()
  306. * @param string $method
  307. */
  308. function restore_method($method)
  309. {
  310. $class = $this->_overrides[$method];
  311. unset($this->_overrides[$method]);
  312. $this->del_pre_hook($method, $class);
  313. $this->_flush_cache();
  314. }
  315. /**
  316. * Returns the Mixin which provides the specified method
  317. * @param string $method
  318. */
  319. function get_mixin_providing($method, $return_obj=FALSE)
  320. {
  321. $retval = FALSE;
  322. // If it's cached, then we've got it easy
  323. if ($this->is_cached($method)) {
  324. $object = $this->_method_map_cache[$method];
  325. $retval = get_class($object);
  326. }
  327. // Otherwise, we have to look it up
  328. else {
  329. foreach ($this->get_mixin_priorities($method) as $klass) {
  330. $object = $this->_instantiate_mixin($klass);
  331. if (method_exists($object, $method)) {
  332. $retval = $return_obj ? $object : get_class($object);
  333. $this->_cache_method($object, $method);
  334. break;
  335. }
  336. }
  337. }
  338. return $retval;
  339. }
  340. /**
  341. * When an ExtensibleObject is instantiated, it checks whether all
  342. * the registered extensions combined provide the implementation as required
  343. * by the interfaces registered for this object
  344. */
  345. function _enforce_interface_contracts()
  346. {
  347. $errors = array();
  348. foreach ($this->_interfaces as $i) {
  349. $r = new ReflectionClass($i);
  350. foreach ($r->getMethods() as $m) {
  351. if (!$this->has_method($m->name)) {
  352. $klass = $this->get_class_name($this);
  353. $errors[] = "`{$klass}` does not implement `{$m->name}` as required by `{$i}`";
  354. }
  355. }
  356. }
  357. if ($errors) throw new Exception(implode(". ", $errors));
  358. }
  359. /**
  360. * Implement a defined interface. Does the same as the 'implements' keyword
  361. * for PHP, except this method takes into account extensions
  362. * @param string $interface
  363. */
  364. function implement($interface)
  365. {
  366. $this->_interfaces[] = $interface;
  367. }
  368. /**
  369. * Adds a hook that gets executed before every method call
  370. * @param string $name
  371. * @param string $class
  372. * @param string $hook_method
  373. */
  374. function add_global_pre_hook($name, $class, $hook_method)
  375. {
  376. $this->add_pre_hook('*', $name, $class, $hook_method);
  377. }
  378. /**
  379. * Adds a hook that gets executed after every method call
  380. *
  381. * @param string $name
  382. * @param string $class
  383. * @param string $hook_method
  384. */
  385. function add_global_post_hook($name, $class, $hook_method)
  386. {
  387. $this->add_pre_hook('*', $name, $class, $hook_method);
  388. }
  389. /**
  390. * Adds a hook that will get executed before a particular method call
  391. * @param string $method
  392. * @param string $name
  393. * @param string $class
  394. * @param string $hook_method
  395. */
  396. function add_pre_hook($method, $name, $class, $hook_method=FALSE)
  397. {
  398. if (!$hook_method) $hook_method = $method;
  399. // Is this a global pre hook?
  400. if ($method == '*') {
  401. $this->_global_pre_hooks[$name] = array(
  402. new $class,
  403. $hook_method
  404. );
  405. }
  406. // This is a method-specific pre hook
  407. else {
  408. if (!isset($this->_pre_hooks[$method])) {
  409. $this->_pre_hooks[$method] = array();
  410. }
  411. $this->_pre_hooks[$method][$name] = array(
  412. new $class,
  413. $hook_method
  414. );
  415. }
  416. }
  417. /**
  418. * Adds a hook to be called after a particular method call
  419. * @param string $method
  420. * @param string $hook_name
  421. * @param string $class
  422. * @param string $hook_method
  423. */
  424. function add_post_hook($method, $hook_name, $class, $hook_method=FALSE)
  425. {
  426. // Is this a global post hook?
  427. if ($method == '*') {
  428. $this->_post_hooks[$hook_name] = array(
  429. new $class,
  430. $hook_method
  431. );
  432. }
  433. // This is a method-specific post hook
  434. else {
  435. if (!$hook_method) $hook_method = $method;
  436. if (!isset($this->_post_hooks[$method])) {
  437. $this->_post_hooks[$method] = array();
  438. }
  439. $this->_post_hooks[$method][$hook_name] = array(
  440. new $class,
  441. $hook_method
  442. );
  443. }
  444. }
  445. /**
  446. * Deletes a hook that's executed before the specified method
  447. * @param string $method
  448. * @param string $name
  449. */
  450. function del_pre_hook($method, $name)
  451. {
  452. unset($this->_pre_hooks[$method][$name]);
  453. }
  454. /**
  455. * Deletes all pre hooks registered
  456. **/
  457. function del_pre_hooks($method=FALSE)
  458. {
  459. if (!$method)
  460. $this->_pre_hooks = array();
  461. else
  462. unset($this->_pre_hooks[$method]);
  463. }
  464. /**
  465. * Deletes a hook that's executed after the specified method
  466. * @param string $method
  467. * @param string $name
  468. */
  469. function del_post_hook($method, $name)
  470. {
  471. unset($this->_post_hooks[$method][$name]);
  472. }
  473. /**
  474. * Deletes all post hooks
  475. */
  476. function del_post_hooks($method=FALSE)
  477. {
  478. if (!$method)
  479. $this->_post_hooks = array();
  480. else
  481. unset($this->_post_hooks[$method]);
  482. }
  483. /**
  484. * Wraps a class within an ExtensibleObject class.
  485. * @param string $klass
  486. * @param array callback, used to tell ExtensibleObject how to instantiate
  487. * the wrapped class
  488. */
  489. function wrap($klass, $callback=FALSE, $args=array())
  490. {
  491. if ($callback) {
  492. $this->_wrapped_instance = call_user_func($callback, $args);
  493. }
  494. else {
  495. $this->_wrapped_instance = new $klass();
  496. }
  497. }
  498. /**
  499. * Determines if the ExtensibleObject is a wrapper for an existing class
  500. */
  501. function is_wrapper()
  502. {
  503. return $this->_wrapped_instance ? TRUE : FALSE;
  504. }
  505. /**
  506. * Returns the name of the class which this ExtensibleObject wraps
  507. * @return object
  508. */
  509. function &get_wrapped_instance()
  510. {
  511. return $this->_wrapped_instance;
  512. }
  513. /**
  514. * Returns TRUE if the wrapped class provides the specified method
  515. */
  516. function wrapped_class_provides($method)
  517. {
  518. $retval = FALSE;
  519. // Determine if the wrapped class is another ExtensibleObject
  520. if (method_exists($this->_wrapped_instance, 'has_method')) {
  521. $retval = $this->_wrapped_instance->has_method($method);
  522. }
  523. elseif (method_exists($this->_wrapped_instance, $method)){
  524. $retval = TRUE;
  525. }
  526. return $retval;
  527. }
  528. /**
  529. * Provides a means of calling static methods, provided by extensions
  530. * @param string $method
  531. * @return mixed
  532. */
  533. static function get_class()
  534. {
  535. // Note: this function is static so $this is not defined
  536. $klass = self::get_class_name();
  537. $obj = new $klass(__EXTOBJ_STATIC__);
  538. return $obj;
  539. }
  540. /**
  541. * Gets the name of the ExtensibleObject
  542. * @return string
  543. */
  544. static function get_class_name($obj = null)
  545. {
  546. if ($obj)
  547. return get_class($obj);
  548. elseif (function_exists('get_called_class'))
  549. return get_called_class();
  550. else
  551. return get_class();
  552. }
  553. /**
  554. * Gets a property from a wrapped object
  555. * @param string $property
  556. * @return mixed
  557. */
  558. function &__get($property)
  559. {
  560. $retval = NULL;
  561. if ($this->is_wrapper()) {
  562. try {
  563. $reflected_prop = new ReflectionProperty($this->_wrapped_instance, $property);
  564. // setAccessible method is only available for PHP 5.3 and above
  565. if (method_exists($reflected_prop, 'setAccessible')) {
  566. $reflected_prop->setAccessible(TRUE);
  567. }
  568. $retval = $reflected_prop->getValue($this->_wrapped_instance);
  569. }
  570. catch (ReflectionException $ex)
  571. {
  572. $retval = $this->_wrapped_instance->$property;
  573. }
  574. }
  575. return $retval;
  576. }
  577. /**
  578. * Determines if a property (dynamic or not) exists for the object
  579. * @param string $property
  580. * @return boolean
  581. */
  582. function __isset($property)
  583. {
  584. $retval = FALSE;
  585. if (property_exists($this, $property)) {
  586. $retval = isset($this->$property);
  587. }
  588. elseif ($this->is_wrapper() && property_exists($this->_wrapped_instance, $property)) {
  589. $retval = isset($this->$property);
  590. }
  591. return $retval;
  592. }
  593. /**
  594. * Sets a property on a wrapped object
  595. * @param string $property
  596. * @param mixed $value
  597. * @return mixed
  598. */
  599. function &__set($property, $value)
  600. {
  601. $retval = NULL;
  602. if ($this->is_wrapper()) {
  603. try {
  604. $reflected_prop = new ReflectionProperty($this->_wrapped_instance, $property);
  605. // The property must be accessible, but this is only available
  606. // on PHP 5.3 and above
  607. if (method_exists($reflected_prop, 'setAccessible')) {
  608. $reflected_prop->setAccessible(TRUE);
  609. }
  610. $retval = &$reflected_prop->setValue($this->_wrapped_instance, $value);
  611. }
  612. // Sometimes reflection can fail. In that case, we need
  613. // some ingenuity as a failback
  614. catch (ReflectionException $ex) {
  615. $this->_wrapped_instance->$property = $value;
  616. $retval = &$this->_wrapped_instance->$property;
  617. }
  618. }
  619. else {
  620. $this->$property = $value;
  621. $retval = &$this->$property;
  622. }
  623. return $retval;
  624. }
  625. /**
  626. * Finds a method defined by an extension and calls it. However, execution
  627. * is a little more in-depth:
  628. * 1) Execute all global pre-hooks and any pre-hooks specific to the requested
  629. * method. Each method call has instance properties that can be set by
  630. * other hooks to modify the execution. For example, a pre hook can
  631. * change the 'run_pre_hooks' property to be false, which will ensure that
  632. * all other pre hooks will NOT be executed.
  633. * 2) Runs the method. Checks whether the path to the method has been cached
  634. * 3) Execute all global post-hooks and any post-hooks specific to the
  635. * requested method. Post hooks can access method properties as well. A
  636. * common usecase is to return the value of a post hook instead of the
  637. * actual method call. To do this, set the 'return_value' property.
  638. * @param string $method
  639. * @param array $args
  640. * @return mixed
  641. */
  642. function __call($method, $args)
  643. {
  644. $this->reset_method_properties($method, $args);
  645. // Run pre hooks?
  646. if ($this->are_pre_hooks_enabled($method) && $this->get_method_property($method, self::METHOD_PROPERTY_RUN_PRE_HOOKS)) {
  647. // Combine global and method-specific pre hooks
  648. $prehooks = $this->_global_pre_hooks;
  649. if (isset($this->_pre_hooks[$method])) {
  650. $prehooks = array_merge($prehooks, $this->_pre_hooks[$method]);
  651. }
  652. // Apply each hook
  653. foreach ($prehooks as $hook_name => $hook) {
  654. $method_args = $this->get_method_property($method, 'arguments', $args);
  655. $this->_run_prehook(
  656. $hook_name,
  657. $method,
  658. $hook[0],
  659. $hook[1],
  660. $method_args
  661. );
  662. }
  663. }
  664. // Are we to run the actual method? A pre hook might have told us
  665. // not to
  666. if ($this->get_method_property($method, self::METHOD_PROPERTY_RUN) && !isset($this->_overrides[$method]))
  667. {
  668. if (($this->get_mixin_providing($method))) {
  669. $this->set_method_property(
  670. $method,
  671. self::METHOD_PROPERTY_RETURN_VALUE,
  672. $this->_exec_cached_method($method, $this->get_method_property($method, 'arguments'))
  673. );
  674. }
  675. // This is NOT a wrapped class, and no extensions provide the method
  676. else {
  677. // Perhaps this is a wrapper and the wrapped object
  678. // provides this method
  679. if ($this->is_wrapper() && $this->wrapped_class_provides($method))
  680. {
  681. $object = $this->add_wrapped_instance_method($method);
  682. $this->set_method_property(
  683. $method,
  684. self::METHOD_PROPERTY_RETURN_VALUE,
  685. call_user_func_array(
  686. array(&$object, $method),
  687. $this->get_method_property($method, 'arguments')
  688. )
  689. );
  690. }
  691. elseif ($this->_throw_error) {
  692. throw new Exception("`{$method}` not defined for " . get_class());
  693. }
  694. else {
  695. return FALSE;
  696. }
  697. }
  698. }
  699. // Are we to run post hooks? A pre hook might have told us not to
  700. if ($this->are_post_hooks_enabled($method) && $this->get_method_property($method, self::METHOD_PROPERTY_RUN_POST_HOOKS)) {
  701. // Combine global and method-specific post hooks
  702. $posthooks = $this->_global_post_hooks;
  703. if (isset($this->_post_hooks[$method])) {
  704. $posthooks = array_merge($posthooks, $this->_post_hooks[$method]);
  705. }
  706. // Apply each hook
  707. foreach ($posthooks as $hook_name => $hook) {
  708. $method_args = $this->get_method_property($method, 'arguments', $args);
  709. $this->_run_post_hook(
  710. $hook_name,
  711. $method,
  712. $hook[0],
  713. $hook[1],
  714. $method_args
  715. );
  716. }
  717. }
  718. // Get return value, clear all method properties, and then return
  719. $retval = $this->get_method_property($method, self::METHOD_PROPERTY_RETURN_VALUE);
  720. $this->remove_method_properties($method);
  721. return $retval;
  722. }
  723. /**
  724. * Adds the implementation of a wrapped instance method to the ExtensibleObject
  725. * @param string $method
  726. * @return Mixin
  727. */
  728. function add_wrapped_instance_method($method)
  729. {
  730. $retval = $this->get_wrapped_instance();
  731. // If the wrapped instance is an ExtensibleObject, then we don't need
  732. // to use reflection
  733. if (!is_subclass_of($this->get_wrapped_instance(), 'ExtensibleObject')) {
  734. $func = new ReflectionMethod($this->get_wrapped_instance(), $method);
  735. // Get the entire method definition
  736. $filename = $func->getFileName();
  737. $start_line = $func->getStartLine() - 1; // it's actually - 1, otherwise you wont get the function() block
  738. $end_line = $func->getEndLine();
  739. $length = $end_line - $start_line;
  740. $source = file($filename);
  741. $body = implode("", array_slice($source, $start_line, $length));
  742. $body = preg_replace("/^\s{0,}private|protected\s{0,}/", '', $body);
  743. // Change the context
  744. $body = str_replace('$this', '$this->object', $body);
  745. $body = str_replace('$this->object->object', '$this->object', $body);
  746. $body = str_replace('$this->object->$', '$this->object->', $body);
  747. // Define method for mixin
  748. $wrapped_klass = get_class($this->get_wrapped_instance());
  749. $mixin_klass = "Mixin_AutoGen_{$wrapped_klass}_{$method}";
  750. if (!class_exists($mixin_klass)) {
  751. eval("class {$mixin_klass} extends Mixin{
  752. {$body}
  753. }");
  754. }
  755. $this->add_mixin($mixin_klass);
  756. $retval = $this->_instantiate_mixin($mixin_klass);
  757. $this->_cache_method($retval, $method);
  758. }
  759. return $retval;
  760. }
  761. /**
  762. * Provides an alternative way to call methods
  763. */
  764. function call_method($method, $args=array())
  765. {
  766. if (method_exists($this, $method))
  767. {
  768. $reflection = new ReflectionMethod($this, $method);
  769. return $reflection->invokeArgs($this, array($args));
  770. }
  771. else {
  772. return $this->__call($method, $args);
  773. }
  774. }
  775. /**
  776. * Returns TRUE if the method in particular has been cached
  777. * @param string $method
  778. * @return type
  779. */
  780. function is_cached($method)
  781. {
  782. return isset($this->_method_map_cache[$method]);
  783. }
  784. /**
  785. * Caches the path to the extension which provides a particular method
  786. * @param string $object
  787. * @param string $method
  788. */
  789. function _cache_method($object, $method)
  790. {
  791. $this->_method_map_cache[$method] = $object;
  792. }
  793. /**
  794. * Gets a list of mixins by their priority, excluding disabled mixins
  795. * @param string $method
  796. * @return array
  797. */
  798. function get_mixin_priorities($method)
  799. {
  800. $retval = array();
  801. foreach ($this->_mixin_priorities as $mixin) {
  802. if ($this->is_mixin_disabled($method, $mixin))
  803. continue;
  804. $retval[] = $mixin;
  805. }
  806. return $retval;
  807. }
  808. /**
  809. * Determines if a mixin is disabled for a particular method
  810. * @param string $method
  811. * @param string $mixin
  812. * @return boolean
  813. */
  814. function is_mixin_disabled($method, $mixin)
  815. {
  816. $retval = FALSE;
  817. if (isset($this->_disabled_mixins[$method]))
  818. if (in_array($mixin, $this->_disabled_mixins[$method]) !== FALSE)
  819. $retval = TRUE;
  820. return $retval;
  821. }
  822. /**
  823. * Flushes the method cache
  824. */
  825. function _flush_cache()
  826. {
  827. $this->_method_map_cache = array();
  828. }
  829. /**
  830. * Returns TRUE if the object provides the particular method
  831. * @param string $method
  832. * @return boolean
  833. */
  834. function has_method($method)
  835. {
  836. $retval = FALSE;
  837. // Have we looked up this method before successfully?
  838. if ($this->is_cached($method)) {
  839. $retval = TRUE;
  840. }
  841. // Is this a local PHP method?
  842. elseif (method_exists($this, $method)) {
  843. $retval = TRUE;
  844. }
  845. // Is a mixin providing this method
  846. elseif ($this->get_mixin_providing($method)) {
  847. $retval = TRUE;
  848. }
  849. elseif ($this->is_wrapper() && $this->wrapped_class_provides($method)) {
  850. $retval = TRUE;
  851. }
  852. return $retval;
  853. }
  854. /**
  855. * Runs a particular pre hook for the specified method. The return value
  856. * is assigned to the "[hook_name]_prehook_retval" method property
  857. * @param string $hook_name
  858. * @param string $method_called
  859. * @param Ext $object
  860. * @param string $hook_method
  861. *
  862. */
  863. function _run_prehook($hook_name, $method_called, $object, $hook_method, &$args)
  864. {
  865. $object->object = &$this;
  866. $object->method_called = $method_called;
  867. // Are we STILL to execute pre hooks? A pre-executed hook might have changed this
  868. if ($this->get_method_property($method_called, 'run_pre_hooks'))
  869. {
  870. $reflection = new ReflectionMethod($object, $hook_method);
  871. $this->set_method_property(
  872. $method_called,
  873. $hook_name . '_prehook_retval',
  874. $reflection->invokeArgs($object, $args)
  875. );
  876. }
  877. }
  878. /**
  879. * Runs the specified post hook for the specified method
  880. * @param string $hook_name
  881. * @param string $method_called
  882. * @param Ext $object
  883. * @param string $hook_method
  884. */
  885. function _run_post_hook($hook_name, $method_called, $object, $hook_method, &$args)
  886. {
  887. $object->object = &$this;
  888. $object->method_called = $method_called;
  889. // Are we STILL to execute post hooks? A post-executed hook might have changed this
  890. if ($this->get_method_property($method_called, 'run_post_hooks'))
  891. {
  892. $reflection = new ReflectionMethod($object, $hook_method);
  893. $this->set_method_property(
  894. $method_called,
  895. $hook_name . '_post_hook_retval',
  896. $reflection->invokeArgs($object, $args)
  897. );
  898. }
  899. }
  900. /**
  901. * Returns TRUE if a pre-hook has been registered for the specified method
  902. * @param string $method
  903. * @return boolean
  904. */
  905. function have_prehook_for($method, $name = null)
  906. {
  907. if (is_null($name)) {
  908. return isset($this->_pre_hooks[$method]);
  909. } else {
  910. return isset($this->_pre_hooks[$method][$name]);
  911. }
  912. }
  913. /**
  914. * Returns TRUE if a posthook has been registered for the specified method
  915. * @param string $method
  916. * @return boolean
  917. */
  918. function have_posthook_for($method, $name = null)
  919. {
  920. $retval = FALSE;
  921. if (isset($this->_post_hooks[$method])) {
  922. if (!$name) $retval = TRUE;
  923. else $retval = isset($this->_post_hooks[$method][$name]);
  924. }
  925. return $retval;
  926. }
  927. /**
  928. * Disables a mixin for a particular method. This ensures that even though
  929. * mixin provides a particular method, it won't be used to provide the
  930. * implementation
  931. * @param string $method
  932. * @param string $klass
  933. */
  934. function disable_mixin($method, $klass)
  935. {
  936. unset($this->_method_map_cache[$method]);
  937. if (!isset($this->_disabled_mixins[$method])) {
  938. $this->_disabled_mixins[$method] = array();
  939. }
  940. $this->_disabled_mixins[$method][] = $klass;
  941. }
  942. /**
  943. * Enable a mixin for a particular method, that was previously disabled
  944. * @param string $method
  945. * @param string $klass
  946. */
  947. function enable_mixin($method, $klass)
  948. {
  949. unset($this->_method_map_cache[$method]);
  950. if (isset($this->_disabled_mixins[$method])) {
  951. $index = array_search($klass, $this->_disabled_mixins[$method]);
  952. if ($index !== FALSE) unset($this->_disabled_mixins[$method][$index]);
  953. }
  954. }
  955. /**
  956. * Gets a list of mixins that are currently disabled for a particular method
  957. * @see disable_mixin()
  958. * @param string $method
  959. * @return array
  960. */
  961. function get_disabled_mixins_for($method)
  962. {
  963. $retval = array();
  964. if (isset($this->_disabled_mixins[$method])) {
  965. $retval = $this->_disabled_mixins[$method];
  966. }
  967. return $retval;
  968. }
  969. /**
  970. * Executes a cached method
  971. * @param string $method
  972. * @param array $args
  973. * @return mixed
  974. */
  975. function _exec_cached_method($method, $args=array())
  976. {
  977. $object = $this->_method_map_cache[$method];
  978. $object->object = &$this;
  979. $reflection = new ReflectionMethod($object, $method);
  980. return $reflection->invokeArgs($object, $args);
  981. }
  982. /**
  983. * Sets the value of a method property
  984. * @param string $method
  985. * @param string $property
  986. * @param mixed $value
  987. */
  988. function set_method_property($method, $property, $value)
  989. {
  990. if (!isset($this->_method_properties[$method])) {
  991. $this->_method_properties[$method] = array();
  992. }
  993. return $this->_method_properties[$method][$property] = $value;
  994. }
  995. /**
  996. * Gets the value of a method property
  997. * @param string $method
  998. * @param string $property
  999. */
  1000. function get_method_property($method, $property, $default=NULL)
  1001. {
  1002. $retval = NULL;
  1003. if (isset($this->_method_properties[$method][$property])) {
  1004. $retval = $this->_method_properties[$method][$property];
  1005. }
  1006. if (is_null($retval)) $retval=$default;
  1007. return $retval;
  1008. }
  1009. /**
  1010. * Clears all method properties to have their default values. This is called
  1011. * before every method call (before pre-hooks)
  1012. * @param string $method
  1013. */
  1014. function reset_method_properties($method, $args=array())
  1015. {
  1016. $this->_method_properties[$method] = array(
  1017. 'run' => TRUE,
  1018. 'run_pre_hooks' => TRUE,
  1019. 'run_post_hooks' => TRUE,
  1020. 'arguments' => $args
  1021. );
  1022. }
  1023. /**
  1024. * Removes the cache of the method properties
  1025. * @param $method
  1026. */
  1027. function remove_method_properties($method)
  1028. {
  1029. unset($this->_method_properties[$method]);
  1030. }
  1031. /**
  1032. * Gets all method properties
  1033. * @return array
  1034. */
  1035. function get_method_properties($method)
  1036. {
  1037. return $this->_method_properties[$method];
  1038. }
  1039. /**
  1040. * Sets all method properties
  1041. * @param $method
  1042. * @param $props
  1043. */
  1044. function set_method_properties($method, $props)
  1045. {
  1046. foreach ($props as $key => $value) {
  1047. $this->set_method_property($method, $key, $value);
  1048. }
  1049. }
  1050. /**
  1051. * Returns TRUE if the ExtensibleObject has decided to implement a
  1052. * particular interface
  1053. * @param string $interface
  1054. * @return boolean
  1055. */
  1056. function implements_interface($interface)
  1057. {
  1058. return in_array($interface, $this->_interfaces);
  1059. }
  1060. function get_class_definition_dir($parent=FALSE)
  1061. {
  1062. return dirname($this->get_class_definition_file($parent));
  1063. }
  1064. function get_class_definition_file($parent=FALSE)
  1065. {
  1066. $klass = $this->get_class_name($this);
  1067. $r = new ReflectionClass($klass);
  1068. if ($parent) {
  1069. $parent = $r->getParentClass();
  1070. return $parent->getFileName();
  1071. }
  1072. return $r->getFileName();
  1073. }
  1074. /**
  1075. * Returns get_class_methods() optionally limited by Mixin
  1076. *
  1077. * @param string (optional) Only show functions provided by a mixin
  1078. * @return array Results from get_class_methods()
  1079. */
  1080. public function get_instance_methods($name = null)
  1081. {
  1082. if (is_string($name))
  1083. {
  1084. $methods = array();
  1085. foreach ($this->_method_map_cache as $method => $mixin) {
  1086. if ($name == get_class($mixin))
  1087. {
  1088. $methods[] = $method;
  1089. }
  1090. }
  1091. return $methods;
  1092. } else {
  1093. $methods = get_class_methods($this);
  1094. foreach ($this->_mixins as $mixin) {
  1095. $methods = array_unique(array_merge($methods, get_class_methods($mixin)));
  1096. sort($methods);
  1097. }
  1098. return $methods;
  1099. }
  1100. }
  1101. }
  1102. /**
  1103. * An mixin provides methods for an ExtensibleObject to use
  1104. */
  1105. class Mixin extends PopeHelpers
  1106. {
  1107. /**
  1108. * The ExtensibleObject which called the extension's method
  1109. * @var ExtensibleObject
  1110. */
  1111. var $object;
  1112. /**
  1113. * The name of the method called on the ExtensibleObject
  1114. * @var type
  1115. */
  1116. var $method_called;
  1117. /**
  1118. * There really isn't any concept of 'parent' method. An ExtensibleObject
  1119. * instance contains an ordered array of extension classes, which provides
  1120. * the method implementations for the instance to use. Suppose that an
  1121. * ExtensibleObject has two extension, and both have the same methods.The
  1122. * last extension appears to 'override' the first extension. So, instead of calling
  1123. * a 'parent' method, we're actually just calling an extension that was added sooner than
  1124. * the one that is providing the current method implementation.
  1125. */
  1126. function call_parent($method)
  1127. {
  1128. $retval = NULL;
  1129. // To simulate a 'parent' call, we remove the current extension from the
  1130. // ExtensibleObject that is providing the method's implementation, re-emit
  1131. // the call on the instance to trigger the implementation from the previously
  1132. // added extension, and then restore things by re-adding the current extension.
  1133. // It's complicated, but it works.
  1134. // We need to determine the name of the extension. Because PHP 5.2 is
  1135. // missing get_called_class(), we have to look it up in the backtrace
  1136. $backtrace = debug_backtrace();
  1137. $klass = get_class($backtrace[0]['object']);
  1138. // Get the method properties. We'll store this afterwards.
  1139. $props = $this->object->get_method_properties($method);
  1140. // Perform the routine described above...
  1141. $this->object->disable_pre_hooks($method);
  1142. $this->object->disable_post_hooks($method);
  1143. $this->object->disable_mixin($method, $klass);
  1144. // Call anchor
  1145. $args = func_get_args();
  1146. // Remove $method parameter
  1147. array_shift($args);
  1148. $retval = $this->object->call_method($method, $args);
  1149. // Re-enable hooks
  1150. $this->object->enable_pre_hooks($method);
  1151. $this->object->enable_post_hooks($method);
  1152. $this->object->enable_mixin($method, $klass);
  1153. // Re-set all method properties
  1154. $this->object->set_method_properties($method, $props);
  1155. return $retval;
  1156. }
  1157. /**
  1158. * Although is is preferrable to call $this->object->method(), sometimes
  1159. * it's nice to use $this->method() instead.
  1160. * @param string $method
  1161. * @param array $args
  1162. * @return mixed
  1163. */
  1164. function __call($method, $args)
  1165. {
  1166. if ($this->object->has_method($method)) {
  1167. return call_user_func_array(array(&$this->object, $method), $args);
  1168. }
  1169. }
  1170. /**
  1171. * Although extensions can have state, it's probably more desirable to maintain
  1172. * the state in the parent object to keep a sane environment
  1173. * @param string $property
  1174. * @return mixed
  1175. */
  1176. function __get($property)
  1177. {
  1178. return $this->object->$property;
  1179. }
  1180. }
  1181. /**
  1182. * An extension which has the purpose of being used as a hook
  1183. */
  1184. class Hook extends Mixin
  1185. {
  1186. // Similiar to a mixin's call_parent method.
  1187. // If a hook needs to call the method that it applied the
  1188. // Hook n' Anchor pattern to, then this method should be called
  1189. function call_anchor()
  1190. {
  1191. // Disable hooks, so that we call the anchor point
  1192. $this->object->disable_pre_hooks($this->method_called);
  1193. $this->object->disable_post_hooks($this->method_called);
  1194. // Call anchor
  1195. $args = func_get_args();
  1196. $retval = $this->object->call_method($this->method_called, $args);
  1197. // Re-enable hooks
  1198. $this->object->enable_pre_hooks($this->method_called);
  1199. $this->object->enable_post_hooks($this->method_called);
  1200. return $retval;
  1201. }
  1202. /**
  1203. * Provides an alias for call_anchor, as there's no parent
  1204. * to call in the context of a hook.
  1205. */
  1206. function call_parent($method)
  1207. {
  1208. $args = func_get_args();
  1209. return call_user_func_array(
  1210. array(&$this, 'call_anchor'),
  1211. $args
  1212. );
  1213. }
  1214. };