Skip to content
This repository was archived by the owner on Jan 31, 2020. It is now read-only.

Commit 7dc18ca

Browse files
committed
Merge branch 'master' of git://git.zendframework.com/zf

File tree

3 files changed

+226
-23
lines changed

3 files changed

+226
-23
lines changed

src/CallbackHandler.php

Lines changed: 144 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
*/
2424
namespace Zend\Stdlib;
2525

26+
use Closure,
27+
WeakRef;
28+
2629
/**
2730
* CallbackHandler
2831
*
@@ -63,15 +66,65 @@ class CallbackHandler
6366
* Constructor
6467
*
6568
* @param string $event Event to which slot is subscribed
66-
* @param string|array|object $callback PHP callback (first element may be )
69+
* @param string|array|object $callback PHP callback
6770
* @param array $options Options used by the callback handler (e.g., priority)
6871
* @return void
6972
*/
7073
public function __construct($event, $callback, array $options = array())
7174
{
7275
$this->event = $event;
73-
$this->callback = $callback;
7476
$this->options = $options;
77+
$this->registerCallback($callback);
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. See {@link isValid()} for more information
89+
* on how this affects execution.
90+
*
91+
* @param callback $callback
92+
* @return void
93+
*/
94+
protected function registerCallback($callback)
95+
{
96+
// If pecl/weakref is not installed, simply register it
97+
if (!class_exists('WeakRef', false)) {
98+
$this->callback = $callback;
99+
return;
100+
}
101+
102+
// If we have a non-closure object, pass it to WeakRef, and then
103+
// register it.
104+
if (is_object($callback) && !$callback instanceof Closure) {
105+
$this->callback = new WeakRef($callback);
106+
return;
107+
}
108+
109+
// If we have a string or closure, register as-is
110+
if (!is_array($callback)) {
111+
$this->callback = $callback;
112+
return;
113+
}
114+
115+
list($target, $method) = $callback;
116+
117+
// If we have an array callback, and the first argument is not an
118+
// object, register as-is
119+
if (!is_object($target)) {
120+
$this->callback = $callback;
121+
return;
122+
}
123+
124+
// We have an array callback with an object as the first argument;
125+
// pass it to WeakRef, and then register the new callback
126+
$target = new WeakRef($target);
127+
$this->callback = array($target, $method);
75128
}
76129

77130
/**
@@ -88,26 +141,33 @@ public function getEvent()
88141
* Retrieve registered callback
89142
*
90143
* @return Callback
91-
* @throws Exception\InvalidCallbackException
144+
* @throws Exception\InvalidCallbackException If callback is invalid
92145
*/
93146
public function getCallback()
94147
{
95-
if ($this->isValidCallback) {
96-
return $this->callback;
148+
if (!$this->isValid()) {
149+
throw new Exception\InvalidCallbackException('Invalid callback provided; not callable');
97150
}
98151

99152
$callback = $this->callback;
100153
if (is_string($callback)) {
101-
return $this->validateStringCallback($callback);
154+
return $callback;
102155
}
103-
if (is_array($callback)) {
104-
return $this->validateArrayCallback($callback);
156+
157+
if ($callback instanceof WeakRef) {
158+
return $callback->get();
105159
}
106-
if (is_callable($callback)) {
107-
$this->isValidCallback = true;
160+
161+
if (is_object($callback)) {
108162
return $callback;
109163
}
110-
throw new Exception\InvalidCallbackException('Invalid callback provided; not callable');
164+
165+
list($target, $method) = $callback;
166+
if ($target instanceof WeakRef) {
167+
return array($target->get(), $method);
168+
}
169+
170+
return $callback;
111171
}
112172

113173
/**
@@ -146,44 +206,106 @@ public function getOption($name)
146206
return null;
147207
}
148208

209+
/**
210+
* Is the composed callback valid?
211+
*
212+
* Typically, this method simply checks to see if we have a valid callback.
213+
* In a few situations, it does more.
214+
*
215+
* * If we have a string callback, we pass execution to
216+
* {@link validateStringCallback()}.
217+
* * If we have an object callback, we test to see if that object is a
218+
* WeakRef {@see http://pecl.php.net/weakref}. If so, we return the value
219+
* of its valid() method. Otherwise, we return the result of is_callable().
220+
* * If we have a callback array with a string in the first position, we
221+
* pass execution to {@link validateArrayCallback()}.
222+
* * If we have a callback array with an object in the first position, we
223+
* test to see if that object is a WeakRef (@see http://pecl.php.net/weakref).
224+
* If so, we return the value of its valid() method. Otherwise, we return
225+
* the result of is_callable() on the callback.
226+
*
227+
* WeakRef is used to allow listeners to go out of scope. This functionality
228+
* is turn-key if you have pecl/weakref installed; otherwise, you will have
229+
* to manually remove listeners before destroying an object referenced in a
230+
* listener.
231+
*
232+
* @return bool
233+
*/
234+
public function isValid()
235+
{
236+
// If we've already tested this, we can move on. Note: if a callback
237+
// composes a WeakRef, this will never get set, and thus result in
238+
// validation on each call.
239+
if ($this->isValidCallback) {
240+
return $this->callback;
241+
}
242+
243+
$callback = $this->callback;
244+
245+
if (is_string($callback)) {
246+
return $this->validateStringCallback($callback);
247+
}
248+
249+
if ($callback instanceof WeakRef) {
250+
return $callback->valid();
251+
}
252+
253+
if (is_object($callback) && is_callable($callback)) {
254+
$this->isValidCallback = true;
255+
return true;
256+
}
257+
258+
if (!is_array($callback)) {
259+
return false;
260+
}
261+
262+
list($target, $method) = $callback;
263+
if ($target instanceof WeakRef) {
264+
if (!$target->valid()) {
265+
return false;
266+
}
267+
$target = $target->get();
268+
return is_callable(array($target, $method));
269+
}
270+
return $this->validateArrayCallback($callback);
271+
}
272+
149273
/**
150274
* Validate a string callback
151275
*
152276
* Check first if the string provided is callable. If not see if it is a
153277
* valid class name; if so, determine if the object is invokable.
154278
*
155279
* @param string $callback
156-
* @return Callback
157-
* @throws Exception\InvalidCallbackException
280+
* @return bool
158281
*/
159282
protected function validateStringCallback($callback)
160283
{
161284
if (is_callable($callback)) {
162285
$this->isValidCallback = true;
163-
return $callback;
286+
return true;
164287
}
165288

166289
if (!class_exists($callback)) {
167-
throw new Exception\InvalidCallbackException('Provided callback is not a function or a class');
290+
return false;
168291
}
169292

170293
// check __invoke before instantiating
171294
if (!method_exists($callback, '__invoke')) {
172-
throw new Exception\InvalidCallbackException('Class provided as a callback does not implement __invoke');
295+
return false;
173296
}
174297
$object = new $callback();
175298

176299
$this->callback = $object;
177300
$this->isValidCallback = true;
178-
return $object;
301+
return true;
179302
}
180303

181304
/**
182305
* Validate an array callback
183306
*
184307
* @param array $callback
185-
* @return callback
186-
* @throws Exception\InvalidCallbackException
308+
* @return bool
187309
*/
188310
protected function validateArrayCallback(array $callback)
189311
{
@@ -194,15 +316,15 @@ protected function validateArrayCallback(array $callback)
194316
// Dealing with a class/method callback, and class provided is a string classname
195317

196318
if (!class_exists($context)) {
197-
throw new Exception\InvalidCallbackException('Class provided in callback does not exist');
319+
return false;
198320
}
199321

200322
// We need to determine if we need to instantiate the class first
201323
$r = new \ReflectionClass($context);
202324
if (!$r->hasMethod($method)) {
203325
// Explicit method does not exist
204326
if (!$r->hasMethod('__callStatic') && !$r->hasMethod('__call')) {
205-
throw new Exception\InvalidCallbackException('Class provided in callback does not define the method requested');
327+
return false;
206328
}
207329

208330
if ($r->hasMethod('__callStatic')) {
@@ -238,7 +360,6 @@ protected function validateArrayCallback(array $callback)
238360
return $callback;
239361
}
240362

241-
242-
throw new Exception\InvalidCallbackException('Method provided in callback does not exist in object');
363+
return false;
243364
}
244365
}

src/IsAssocArray.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Zend\Stdlib;
4+
5+
/**
6+
* Simple class for testing whether a value is an associative array
7+
*
8+
* Declared abstract, as we have no need for instantiation.
9+
*/
10+
abstract class IsAssocArray
11+
{
12+
/**
13+
* Test whether a value is an associative array
14+
*
15+
* We have an associative array if at least one key is a string.
16+
*
17+
* @param mixed $value
18+
* @return bool
19+
*/
20+
public static function test($value)
21+
{
22+
return (is_array($value)
23+
&& count(array_filter(array_keys($value), 'is_string')) > 0
24+
);
25+
}
26+
}

test/IsAssocArrayTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace ZendTest\Stdlib;
4+
5+
use PHPUnit_Framework_TestCase as TestCase,
6+
stdClass,
7+
Zend\Stdlib\IsAssocArray;
8+
9+
class IsAssocArrayTest extends TestCase
10+
{
11+
public static function validAssocArrays()
12+
{
13+
return array(
14+
array(array(
15+
'foo' => 'bar',
16+
)),
17+
array(array(
18+
'bar',
19+
'foo' => 'bar',
20+
'baz',
21+
)),
22+
);
23+
}
24+
25+
public static function invalidAssocArrays()
26+
{
27+
return array(
28+
array(null),
29+
array(true),
30+
array(false),
31+
array(0),
32+
array(1),
33+
array(0.0),
34+
array(1.0),
35+
array('string'),
36+
array(array(0, 1, 2)),
37+
array(new stdClass),
38+
);
39+
}
40+
41+
/**
42+
* @dataProvider validAssocArrays
43+
*/
44+
public function testValidAssocArraysReturnTrue($test)
45+
{
46+
$this->assertTrue(IsAssocArray::test($test));
47+
}
48+
49+
/**
50+
* @dataProvider invalidAssocArrays
51+
*/
52+
public function testInvalidAssocArraysReturnFalse($test)
53+
{
54+
$this->assertFalse(IsAssocArray::test($test));
55+
}
56+
}

0 commit comments

Comments
 (0)