CallBack.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. <?php
  2. /**
  3. * @package ActiveRecord
  4. */
  5. namespace ActiveRecord;
  6. use Closure;
  7. /**
  8. * Callbacks allow the programmer to hook into the life cycle of a {@link Model}.
  9. *
  10. * You can control the state of your object by declaring certain methods to be
  11. * called before or after methods are invoked on your object inside of ActiveRecord.
  12. *
  13. * Valid callbacks are:
  14. * <ul>
  15. * <li><b>after_construct:</b> called after a model has been constructed</li>
  16. * <li><b>before_save:</b> called before a model is saved</li>
  17. * <li><b>after_save:</b> called after a model is saved</li>
  18. * <li><b>before_create:</b> called before a NEW model is to be inserted into the database</li>
  19. * <li><b>after_create:</b> called after a NEW model has been inserted into the database</li>
  20. * <li><b>before_update:</b> called before an existing model has been saved</li>
  21. * <li><b>after_update:</b> called after an existing model has been saved</li>
  22. * <li><b>before_validation:</b> called before running validators</li>
  23. * <li><b>after_validation:</b> called after running validators</li>
  24. * <li><b>before_validation_on_create:</b> called before validation on a NEW model being inserted</li>
  25. * <li><b>after_validation_on_create:</b> called after validation on a NEW model being inserted</li>
  26. * <li><b>before_validation_on_update:</b> see above except for an existing model being saved</li>
  27. * <li><b>after_validation_on_update:</b> ...</li>
  28. * <li><b>before_destroy:</b> called after a model has been deleted</li>
  29. * <li><b>after_destroy:</b> called after a model has been deleted</li>
  30. * </ul>
  31. *
  32. * This class isn't meant to be used directly. Callbacks are defined on your model like the example below:
  33. *
  34. * <code>
  35. * class Person extends ActiveRecord\Model {
  36. * static $before_save = array('make_name_uppercase');
  37. * static $after_save = array('do_happy_dance');
  38. *
  39. * public function make_name_uppercase() {
  40. * $this->name = strtoupper($this->name);
  41. * }
  42. *
  43. * public function do_happy_dance() {
  44. * happy_dance();
  45. * }
  46. * }
  47. * </code>
  48. *
  49. * Available options for callbacks:
  50. *
  51. * <ul>
  52. * <li><b>prepend:</b> puts the callback at the top of the callback chain instead of the bottom</li>
  53. * </ul>
  54. *
  55. * @package ActiveRecord
  56. * @link http://www.phpactiverecord.org/guides/callbacks
  57. */
  58. class CallBack
  59. {
  60. /**
  61. * List of available callbacks.
  62. *
  63. * @var array
  64. */
  65. static protected $VALID_CALLBACKS = array(
  66. 'after_construct',
  67. 'before_save',
  68. 'after_save',
  69. 'before_create',
  70. 'after_create',
  71. 'before_update',
  72. 'after_update',
  73. 'before_validation',
  74. 'after_validation',
  75. 'before_validation_on_create',
  76. 'after_validation_on_create',
  77. 'before_validation_on_update',
  78. 'after_validation_on_update',
  79. 'before_destroy',
  80. 'after_destroy'
  81. );
  82. /**
  83. * Container for reflection class of given model
  84. *
  85. * @var object
  86. */
  87. private $klass;
  88. /**
  89. * List of public methods of the given model
  90. * @var array
  91. */
  92. private $publicMethods;
  93. /**
  94. * Holds data for registered callbacks.
  95. *
  96. * @var array
  97. */
  98. private $registry = array();
  99. /**
  100. * Creates a CallBack.
  101. *
  102. * @param string $model_class_name The name of a {@link Model} class
  103. * @return CallBack
  104. */
  105. public function __construct($model_class_name)
  106. {
  107. $this->klass = Reflections::instance()->get($model_class_name);
  108. foreach (static::$VALID_CALLBACKS as $name)
  109. {
  110. // look for explicitly defined static callback
  111. if (($definition = $this->klass->getStaticPropertyValue($name,null)))
  112. {
  113. if (!is_array($definition))
  114. $definition = array($definition);
  115. foreach ($definition as $method_name)
  116. $this->register($name,$method_name);
  117. }
  118. // implicit callbacks that don't need to have a static definition
  119. // simply define a method named the same as something in $VALID_CALLBACKS
  120. // and the callback is auto-registered
  121. elseif ($this->klass->hasMethod($name))
  122. $this->register($name,$name);
  123. }
  124. }
  125. /**
  126. * Returns all the callbacks registered for a callback type.
  127. *
  128. * @param $name string Name of a callback (see {@link VALID_CALLBACKS $VALID_CALLBACKS})
  129. * @return array array of callbacks or null if invalid callback name.
  130. */
  131. public function get_callbacks($name)
  132. {
  133. return isset($this->registry[$name]) ? $this->registry[$name] : null;
  134. }
  135. /**
  136. * Invokes a callback.
  137. *
  138. * @internal This is the only piece of the CallBack class that carries its own logic for the
  139. * model object. For (after|before)_(create|update) callbacks, it will merge with
  140. * a generic 'save' callback which is called first for the lease amount of precision.
  141. *
  142. * @param string $model Model to invoke the callback on.
  143. * @param string $name Name of the callback to invoke
  144. * @param boolean $must_exist Set to true to raise an exception if the callback does not exist.
  145. * @return mixed null if $name was not a valid callback type or false if a method was invoked
  146. * that was for a before_* callback and that method returned false. If this happens, execution
  147. * of any other callbacks after the offending callback will not occur.
  148. */
  149. public function invoke($model, $name, $must_exist=true)
  150. {
  151. if ($must_exist && !array_key_exists($name, $this->registry))
  152. throw new ActiveRecordException("No callbacks were defined for: $name on " . get_class($model));
  153. // if it doesn't exist it might be a /(after|before)_(create|update)/ so we still need to run the save
  154. // callback
  155. if (!array_key_exists($name, $this->registry))
  156. $registry = array();
  157. else
  158. $registry = $this->registry[$name];
  159. $first = substr($name,0,6);
  160. // starts with /(after|before)_(create|update)/
  161. if (($first == 'after_' || $first == 'before') && (($second = substr($name,7,5)) == 'creat' || $second == 'updat' || $second == 'reate' || $second == 'pdate'))
  162. {
  163. $temporal_save = str_replace(array('create', 'update'), 'save', $name);
  164. if (!isset($this->registry[$temporal_save]))
  165. $this->registry[$temporal_save] = array();
  166. $registry = array_merge($this->registry[$temporal_save], $registry ? $registry : array());
  167. }
  168. if ($registry)
  169. {
  170. foreach ($registry as $method)
  171. {
  172. $ret = ($method instanceof Closure ? $method($model) : $model->$method());
  173. if (false === $ret && $first === 'before')
  174. return false;
  175. }
  176. }
  177. return true;
  178. }
  179. /**
  180. * Register a new callback.
  181. *
  182. * The option array can contain the following parameters:
  183. * <ul>
  184. * <li><b>prepend:</b> Add this callback at the beginning of the existing callbacks (true) or at the end (false, default)</li>
  185. * </ul>
  186. *
  187. * @param string $name Name of callback type (see {@link VALID_CALLBACKS $VALID_CALLBACKS})
  188. * @param mixed $closure_or_method_name Either a closure or the name of a method on the {@link Model}
  189. * @param array $options Options array
  190. * @return void
  191. * @throws ActiveRecordException if invalid callback type or callback method was not found
  192. */
  193. public function register($name, $closure_or_method_name=null, $options=array())
  194. {
  195. $options = array_merge(array('prepend' => false), $options);
  196. if (!$closure_or_method_name)
  197. $closure_or_method_name = $name;
  198. if (!in_array($name,self::$VALID_CALLBACKS))
  199. throw new ActiveRecordException("Invalid callback: $name");
  200. if (!($closure_or_method_name instanceof Closure))
  201. {
  202. if (!isset($this->publicMethods))
  203. $this->publicMethods = get_class_methods($this->klass->getName());
  204. if (!in_array($closure_or_method_name, $this->publicMethods))
  205. {
  206. if ($this->klass->hasMethod($closure_or_method_name))
  207. {
  208. // Method is private or protected
  209. throw new ActiveRecordException("Callback methods need to be public (or anonymous closures). " .
  210. "Please change the visibility of " . $this->klass->getName() . "->" . $closure_or_method_name . "()");
  211. }
  212. else
  213. {
  214. // i'm a dirty ruby programmer
  215. throw new ActiveRecordException("Unknown method for callback: $name" .
  216. (is_string($closure_or_method_name) ? ": #$closure_or_method_name" : ""));
  217. }
  218. }
  219. }
  220. if (!isset($this->registry[$name]))
  221. $this->registry[$name] = array();
  222. if ($options['prepend'])
  223. array_unshift($this->registry[$name], $closure_or_method_name);
  224. else
  225. $this->registry[$name][] = $closure_or_method_name;
  226. }
  227. }
  228. ?>