|
1 <?php |
|
2 /** |
|
3 * Zend Framework |
|
4 * |
|
5 * LICENSE |
|
6 * |
|
7 * This source file is subject to the new BSD license that is bundled |
|
8 * with this package in the file LICENSE.txt. |
|
9 * It is also available through the world-wide-web at this URL: |
|
10 * http://framework.zend.com/license/new-bsd |
|
11 * If you did not receive a copy of the license and are unable to |
|
12 * obtain it through the world-wide-web, please send an email |
|
13 * to license@zend.com so we can send you a copy immediately. |
|
14 * |
|
15 * @category Zend |
|
16 * @package Zend_Stdlib |
|
17 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) |
|
18 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
19 */ |
|
20 |
|
21 /** |
|
22 * CallbackHandler |
|
23 * |
|
24 * A handler for a event, event, filterchain, etc. Abstracts PHP callbacks, |
|
25 * primarily to allow for lazy-loading and ensuring availability of default |
|
26 * arguments (currying). |
|
27 * |
|
28 * @category Zend |
|
29 * @package Zend_Stdlib |
|
30 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) |
|
31 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
32 */ |
|
33 class Zend_Stdlib_CallbackHandler |
|
34 { |
|
35 /** |
|
36 * @var string|array PHP callback to invoke |
|
37 */ |
|
38 protected $callback; |
|
39 |
|
40 /** |
|
41 * Did an error occur when testing the validity of the callback? |
|
42 * @var bool |
|
43 */ |
|
44 protected $error = false; |
|
45 |
|
46 /** |
|
47 * Callback metadata, if any |
|
48 * @var array |
|
49 */ |
|
50 protected $metadata; |
|
51 |
|
52 /** |
|
53 * Constructor |
|
54 * |
|
55 * @param string $event Event to which slot is subscribed |
|
56 * @param string|array|object $callback PHP callback |
|
57 * @param array $options Options used by the callback handler (e.g., priority) |
|
58 * @return void |
|
59 */ |
|
60 public function __construct($callback, array $metadata = array()) |
|
61 { |
|
62 $this->metadata = $metadata; |
|
63 $this->registerCallback($callback); |
|
64 } |
|
65 |
|
66 /** |
|
67 * Error handler |
|
68 * |
|
69 * Used by registerCallback() when calling is_callable() to capture engine warnings. |
|
70 * |
|
71 * @param int $errno |
|
72 * @param string $errstr |
|
73 * @return void |
|
74 */ |
|
75 public function errorHandler($errno, $errstr) |
|
76 { |
|
77 $this->error = true; |
|
78 } |
|
79 |
|
80 /** |
|
81 * Registers the callback provided in the constructor |
|
82 * |
|
83 * If you have pecl/weakref {@see http://pecl.php.net/weakref} installed, |
|
84 * this method provides additional behavior. |
|
85 * |
|
86 * If a callback is a functor, or an array callback composing an object |
|
87 * instance, this method will pass the object to a WeakRef instance prior |
|
88 * to registering the callback. |
|
89 * |
|
90 * @param Callable $callback |
|
91 * @return void |
|
92 */ |
|
93 protected function registerCallback($callback) |
|
94 { |
|
95 set_error_handler(array($this, 'errorHandler'), E_STRICT); |
|
96 $callable = is_callable($callback); |
|
97 restore_error_handler(); |
|
98 if (!$callable || $this->error) { |
|
99 require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php'; |
|
100 throw new Zend_Stdlib_Exception_InvalidCallbackException('Invalid callback provided; not callable'); |
|
101 } |
|
102 |
|
103 // If pecl/weakref is not installed, simply store the callback and return |
|
104 if (!class_exists('WeakRef')) { |
|
105 $this->callback = $callback; |
|
106 return; |
|
107 } |
|
108 |
|
109 // If WeakRef exists, we want to use it. |
|
110 |
|
111 // If we have a non-closure object, pass it to WeakRef, and then |
|
112 // register it. |
|
113 if (is_object($callback) && !$callback instanceof Closure) { |
|
114 $this->callback = new WeakRef($callback); |
|
115 return; |
|
116 } |
|
117 |
|
118 // If we have a string or closure, register as-is |
|
119 if (!is_array($callback)) { |
|
120 $this->callback = $callback; |
|
121 return; |
|
122 } |
|
123 |
|
124 list($target, $method) = $callback; |
|
125 |
|
126 // If we have an array callback, and the first argument is not an |
|
127 // object, register as-is |
|
128 if (!is_object($target)) { |
|
129 $this->callback = $callback; |
|
130 return; |
|
131 } |
|
132 |
|
133 // We have an array callback with an object as the first argument; |
|
134 // pass it to WeakRef, and then register the new callback |
|
135 $target = new WeakRef($target); |
|
136 $this->callback = array($target, $method); |
|
137 } |
|
138 |
|
139 /** |
|
140 * Retrieve registered callback |
|
141 * |
|
142 * @return Callable |
|
143 */ |
|
144 public function getCallback() |
|
145 { |
|
146 $callback = $this->callback; |
|
147 |
|
148 // String callbacks -- simply return |
|
149 if (is_string($callback)) { |
|
150 return $callback; |
|
151 } |
|
152 |
|
153 // WeakRef callbacks -- pull it out of the object and return it |
|
154 if ($callback instanceof WeakRef) { |
|
155 return $callback->get(); |
|
156 } |
|
157 |
|
158 // Non-WeakRef object callback -- return it |
|
159 if (is_object($callback)) { |
|
160 return $callback; |
|
161 } |
|
162 |
|
163 // Array callback with WeakRef object -- retrieve the object first, and |
|
164 // then return |
|
165 list($target, $method) = $callback; |
|
166 if ($target instanceof WeakRef) { |
|
167 return array($target->get(), $method); |
|
168 } |
|
169 |
|
170 // Otherwise, return it |
|
171 return $callback; |
|
172 } |
|
173 |
|
174 /** |
|
175 * Invoke handler |
|
176 * |
|
177 * @param array $args Arguments to pass to callback |
|
178 * @return mixed |
|
179 */ |
|
180 public function call(array $args = array()) |
|
181 { |
|
182 $callback = $this->getCallback(); |
|
183 |
|
184 $isPhp54 = version_compare(PHP_VERSION, '5.4.0rc1', '>='); |
|
185 |
|
186 if ($isPhp54 && is_string($callback)) { |
|
187 $this->validateStringCallbackFor54($callback); |
|
188 } |
|
189 |
|
190 // Minor performance tweak; use call_user_func() until > 3 arguments |
|
191 // reached |
|
192 switch (count($args)) { |
|
193 case 0: |
|
194 if ($isPhp54) { |
|
195 return $callback(); |
|
196 } |
|
197 return call_user_func($callback); |
|
198 case 1: |
|
199 if ($isPhp54) { |
|
200 return $callback(array_shift($args)); |
|
201 } |
|
202 return call_user_func($callback, array_shift($args)); |
|
203 case 2: |
|
204 $arg1 = array_shift($args); |
|
205 $arg2 = array_shift($args); |
|
206 if ($isPhp54) { |
|
207 return $callback($arg1, $arg2); |
|
208 } |
|
209 return call_user_func($callback, $arg1, $arg2); |
|
210 case 3: |
|
211 $arg1 = array_shift($args); |
|
212 $arg2 = array_shift($args); |
|
213 $arg3 = array_shift($args); |
|
214 if ($isPhp54) { |
|
215 return $callback($arg1, $arg2, $arg3); |
|
216 } |
|
217 return call_user_func($callback, $arg1, $arg2, $arg3); |
|
218 default: |
|
219 return call_user_func_array($callback, $args); |
|
220 } |
|
221 } |
|
222 |
|
223 /** |
|
224 * Invoke as functor |
|
225 * |
|
226 * @return mixed |
|
227 */ |
|
228 public function __invoke() |
|
229 { |
|
230 return $this->call(func_get_args()); |
|
231 } |
|
232 |
|
233 /** |
|
234 * Get all callback metadata |
|
235 * |
|
236 * @return array |
|
237 */ |
|
238 public function getMetadata() |
|
239 { |
|
240 return $this->metadata; |
|
241 } |
|
242 |
|
243 /** |
|
244 * Retrieve a single metadatum |
|
245 * |
|
246 * @param string $name |
|
247 * @return mixed |
|
248 */ |
|
249 public function getMetadatum($name) |
|
250 { |
|
251 if (array_key_exists($name, $this->metadata)) { |
|
252 return $this->metadata[$name]; |
|
253 } |
|
254 return null; |
|
255 } |
|
256 |
|
257 /** |
|
258 * Validate a static method call |
|
259 * |
|
260 * Validates that a static method call in PHP 5.4 will actually work |
|
261 * |
|
262 * @param string $callback |
|
263 * @return true |
|
264 * @throws Zend_Stdlib_Exception_InvalidCallbackException if invalid |
|
265 */ |
|
266 protected function validateStringCallbackFor54($callback) |
|
267 { |
|
268 if (!strstr($callback, '::')) { |
|
269 return true; |
|
270 } |
|
271 |
|
272 list($class, $method) = explode('::', $callback, 2); |
|
273 |
|
274 if (!class_exists($class)) { |
|
275 require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php'; |
|
276 throw new Zend_Stdlib_Exception_InvalidCallbackException(sprintf( |
|
277 'Static method call "%s" refers to a class that does not exist', |
|
278 $callback |
|
279 )); |
|
280 } |
|
281 |
|
282 $r = new ReflectionClass($class); |
|
283 if (!$r->hasMethod($method)) { |
|
284 require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php'; |
|
285 throw new Zend_Stdlib_Exception_InvalidCallbackException(sprintf( |
|
286 'Static method call "%s" refers to a method that does not exist', |
|
287 $callback |
|
288 )); |
|
289 } |
|
290 $m = $r->getMethod($method); |
|
291 if (!$m->isStatic()) { |
|
292 require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php'; |
|
293 throw new Zend_Stdlib_Exception_InvalidCallbackException(sprintf( |
|
294 'Static method call "%s" refers to a method that is not static', |
|
295 $callback |
|
296 )); |
|
297 } |
|
298 |
|
299 return true; |
|
300 } |
|
301 } |