TEventHandler   F
last analyzed

Complexity

Total Complexity 77

Size/Duplication

Total Lines 308
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 100
dl 0
loc 308
rs 2.24
c 1
b 0
f 0
wmc 77

15 Methods

Rating   Name   Duplication   Size   Complexity  
B offsetUnset() 0 13 7
B getHandlerObject() 0 15 7
B getHandler() 0 15 9
A hasHandler() 0 6 2
A getCount() 0 3 2
A count() 0 3 1
B __construct() 0 16 8
A getData() 0 6 5
B offsetSet() 0 13 7
C offsetGet() 0 31 13
A isSameHandler() 0 10 3
A offsetExists() 0 3 5
A __invoke() 0 12 5
A setData() 0 3 1
A hasWeakObject() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like TEventHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TEventHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * TEventHandler class
5
 *
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 */
9
10
namespace Prado;
11
12
use Prado\Collections\IPriorityProperty;
13
use Prado\Collections\IWeakRetainable;
14
use Prado\Collections\TPriorityPropertyTrait;
15
use Prado\Exceptions\TApplicationException;
16
use Prado\Exceptions\TInvalidDataTypeException;
17
use Prado\Exceptions\TInvalidDataValueException;
18
use Prado\Exceptions\TInvalidOperationException;
19
use Closure;
20
use WeakReference;
21
22
/**
23
 * TEventHandler class
24
 *
25
 * This class is a helper class for passing specific data along with an event handler.
26
 * Normal event handlers only have a sender and parameter, but under a TEventHandler
27
 * there is a third parameter in the event handler method for data.  This class is
28
 * invokable and will pass invoked method arguments forward to the managed handler
29
 * with the specified data.
30
 *
31
 * A TEventHandler handler method would be implemented as such:
32
 * ```php
33
 *    $handler = new TEventHandler([$object, 'myHandler'], ['key' => 'data']);
34
 *    $handler($sender, $param); // <- invokable
35
 *    $component->attachEventHandler('onMyEvent', $handler, $priority);
36
 *
37
 *  // In the $object class:
38
 *    public function myHandler(object $sender, mixed $param, mixed $data = null): mixed
39
 *    {
40
 *	 	// $data === ['key' => 'data']
41
 *       ....
42
 *    }
43
 * ```
44
 * In this instance, $data is default to null so it can be called on a raised
45
 * attached event without TEventHandler.  If you will only have a your handler use
46
 * TEventHandler, then $data can be required (without the null default).
47
 *
48
 * There are several ways to access the event handler (callable).  {@see getHandler}
49
 * will return the event handler and can be requested to return the callable as
50
 * WeakReference.
51
 *
52
 * The event handler can be accessed by ArrayAccess as well.  For example:
53
 * ```php
54
 *		$handler = new TEventHandler('TMyClass::myStaticHandler', ['data' => 2, ...]);
55
 *		$handler[null] === 'TMyClass::myStaticHandler' === $handler->getHandler();
56
 *		$handler[0] === 'TMyClass::myStaticHandler';
57
 *		$handler[1] === null
58
 *		$handler[2] === ['data' => 2, ...] === $handler->getData();
59
 *
60
 *		$handler = new TEventHandler([$behavior, 'myHandler'], ['data' => 3, ...]);
61
 *		$handler[null] === [$behavior, 'myHandler'] === $handler->getHandler();
62
 *		$handler[0] === $behavior;
63
 *		$handler[1] === 'myHandler';
64
 *		$handler[2] === ['data' => 3, ...] === $handler->getData();
65
 *
66
 *		// Add the handler to the event at priority 12 (the default is 10)
67
 *		$component->attachEventHandler('onMyEvent', $handler, 12);
68
 * ```
69
 *
70
 * PRADO event handler objects are stored as WeakReference to improve PHP garbage
71
 * collection.  To enable this functionality, TEventHandler holds its callable object
72
 * as WeakReference and re-references the callable handler object when used.
73
 *
74
 * The only exceptions to conversion into WeakReference are Closure and {@see \Prado\Collections\IWeakRetainable}.
75
 * Closure and IWeakRetainable are retained without WeakReference conversion because
76
 * they might be the only instance in the application.  Holding these instances
77
 * directly will properly increment their PHP use counter to be retained.
78
 *
79
 * {@see hasWeakObject} returns if there is an object being held as WeakReference.
80
 *
81
 * When the TEventHandler {@see getData data} is an array, and the 3rd parameter
82
 * $data of {@see __invoke invoke} is also an array, an `array_replace` (with the
83
 * function parameter array taking precedence) will combine the data.
84
 *
85
 * In nesting TEventHandlers, a base TEventHandler, pointing to a callable, can be
86
 * instanced with core data.  Children TEventHandler[s] can point to the parent
87
 * TEventHandler and override specific data items in the {@see getData Data} array
88
 * with its own (array) data items.  This only works if the Data is an array otherwise
89
 * the children will override the parent TEventHandler data.
90
 *
91
 * @author Brad Anderson <[email protected]>
92
 * @since 4.3.0
93
 */
94
class TEventHandler implements IPriorityProperty, IWeakRetainable, \ArrayAccess, \Countable
95
{
96
	use TPriorityPropertyTrait;
97
98
	/** @var mixed The callable event handler being managed. */
99
	private mixed $_handler;
100
101
	/** @var mixed The data to feed the handler when invoked. */
102
	private mixed $_data;
103
104
	/** @var bool Is the callable object converted into WeakReference? */
105
	private bool $_weakObject = false;
106
107
	/**
108
	 * Constructs a new TEventHandler and initializes the Event Handler and Data.
109
	 * @param mixed $handler The event handler being managed.
110
	 * @param mixed $data The data to feed the event handler.
111
	 */
112
	public function __construct(mixed $handler, mixed $data = null)
113
	{
114
		if (!is_callable($handler)) {
115
			throw new TInvalidDataTypeException('eventhandler_not_callable');
116
		}
117
118
		if (is_array($handler) && is_object($handler[0]) && !($handler[0] instanceof IWeakRetainable)) {
119
			$handler[0] = WeakReference::create($handler[0]);
120
			$this->_weakObject = true;
121
		} elseif (is_object($handler) && !($handler instanceof Closure) && !($handler instanceof IWeakRetainable)) {
122
			$handler = WeakReference::create($handler);
123
			$this->_weakObject = true;
124
		}
125
126
		$this->_handler = $handler;
127
		$this->_data = $data;
128
	}
129
130
	/**
131
	 * This calls the handler with the specified data.  If no $data is specified then
132
	 * TEventHandler injects its own data for the event handler.  When $data is an array
133
	 * and the TEventHandler data is an array, the data is combined by `array_replace`,
134
	 * with the function input array data  taking precedence over TEventHandler data.
135
	 * This allows TEventHandlers to be nested, with children taking precedence.
136
	 * @param mixed $sender The sender that raised the event.
137
	 * @param mixed $param The parameter that goes with the raised event.
138
	 * @param null|mixed $data The data for the managed event handler. default null
139
	 *   for data from the TEventHandler.
140
	 * @param array $argv Any additional function arguments.
141
	 * @throws TApplicationException
142
	 * @return mixed The result of the event handler.
143
	 */
144
	public function __invoke(mixed $sender = null, mixed $param = null, mixed $data = null, ...$argv): mixed
145
	{
146
		$handler = $this->getHandler();
147
		if (!$handler) {
148
			throw new TApplicationException('eventhandler_lost_weak_ref');
149
		}
150
		if (is_array($data) && is_array($this->_data)) {
151
			$data = array_replace($this->_data, $data);
152
		} elseif ($data === null) {
153
			$data = $this->_data;
154
		}
155
		return $handler($sender, $param, $data, ...$argv);
156
	}
157
158
	/**
159
	 * Gets the handler being managed. The WeakReference version of the handler can
160
	 * be retrieved by passing true to the $weak parameter.  When there are nested
161
	 * TEventHandlers, this will return the next TEventHandler as an invokable and will
162
	 * not link to the last and actual callable handler.
163
	 * @param bool $weak Return the handler with WeakReference instead of objects. Default false.
164
	 * @return null|array|object|string The callable event handler.
165
	 */
166
	public function getHandler(bool $weak = false): null|string|object|array
167
	{
168
		$handler = $this->_handler;
169
		if (!$weak && $this->_weakObject) {
170
			if (is_array($handler) && is_object($handler[0]) && ($handler[0] instanceof WeakReference)) {
171
				if ($obj = $handler[0]->get()) {
172
					$handler[0] = $obj;
173
				} else {
174
					$handler = null;
175
				}
176
			} elseif (is_object($handler) && ($handler instanceof WeakReference)) {
177
				$handler = $handler->get();
178
			}
179
		}
180
		return $handler;
181
	}
182
183
	/**
184
	 * This methods checks if the item is the same as the handler.  When TEventHandler
185
	 * are nested, we check the nested TEventHandler for isSameHandler.
186
	 * @param mixed $item
187
	 * @param bool $weak
188
	 * @return bool Is the $item the same as the managed callable.
189
	 */
190
	public function isSameHandler(mixed $item, bool $weak = false): bool
191
	{
192
		$handler = $this->getHandler($weak);
193
		if ($item === $handler) {
194
			return true;
195
		}
196
		if ($handler instanceof TEventHandler) {
197
			return $handler->isSameHandler($item, $weak);
198
		}
199
		return false;
200
	}
201
202
	/**
203
	 * If there is an object referenced in the callable, this method returns the object.
204
	 * By default, the WeakReference is re-referenced.  The WeakReference can be returned
205
	 * by passing true for $weak.  This will return Closure and IWeakRetainable objects
206
	 * without any WeakReference because they are exempt from conversion into WeakReference.
207
	 * When TEventHandlers are nested, this returns the last and actual callable object.
208
	 * @param bool $weak Return the callable with WeakReference instead of objects.
209
	 * @return ?object The object of the event handler if there is one.
210
	 */
211
	public function getHandlerObject(bool $weak = false): ?object
212
	{
213
		$handler = null;
214
		if (is_array($this->_handler) && is_object($this->_handler[0])) {
215
			$handler = $this->_handler[0];
216
		} elseif ($this->_handler instanceof TEventHandler) {
217
			return $this->_handler->getHandlerObject($weak);
218
		} elseif (is_object($this->_handler)) {
219
			$handler = $this->_handler;
220
221
		}
222
		if (!$weak && $this->_weakObject) {
223
			$handler = $handler->get();
224
		}
225
		return $handler;
226
	}
227
228
	/**
229
	 * This checks if the managed handler is still valid.  When TEventHandler are nested,
230
	 * this returns if the last and actual handler is still valid (from WeakReference).
231
	 * @return bool Does the managed event Handler exist and is callable.
232
	 */
233
	public function hasHandler(): bool
234
	{
235
		if ($this->_handler instanceof TEventHandler) {
236
			return $this->_handler->hasHandler();
237
		}
238
		return $this->getHandler() !== null;
239
	}
240
241
	/**
242
	 * Returns the data associated with the managed event handler. By passing true to
243
	 * $withHandlerData, this will combine the data from nested TEventHandlers when the
244
	 * data is in array format.  The children data take precedence in the combining
245
	 * of data.
246
	 * @param bool $withHandlerData
247
	 * @return mixed The data associated with the event handler.
248
	 */
249
	public function getData(bool $withHandlerData = false): mixed
250
	{
251
		if ($withHandlerData && ($this->_handler instanceof TEventHandler) && is_array($this->_data) && is_array($data = $this->_handler->getData(true))) {
252
			return array_replace($data, $this->_data);
253
		}
254
		return $this->_data;
255
	}
256
257
	/**
258
	 * @param mixed $data The data associated with the event handler.
259
	 */
260
	public function setData(mixed $data): void
261
	{
262
		$this->_data = $data;
263
	}
264
265
	/**
266
	 * Does the object contain any WeakReference objects?  When there are nested TEventHandler
267
	 * this will return the last and actual data from the actual callable.
268
	 * @return bool If there are objects as WeakReferences.
269
	 */
270
	public function hasWeakObject(): bool
271
	{
272
		if ($this->_handler instanceof TEventHandler) {
273
			return $this->_handler->hasWeakObject();
274
		}
275
		return $this->_weakObject;
276
	}
277
278
	/**
279
	 * Returns the number of items in the managed event handler (with data).
280
	 * This method is required by \Countable interface.
281
	 * @return int The number of items in the managed event handler.
282
	 */
283
	public function count(): int
284
	{
285
		return $this->getCount();
286
	}
287
288
	/**
289
	 * There are 3 items when the handler is an array, and 2 items when the handler
290
	 * is an invokable object or string.
291
	 * @return int The number of items in the managed event handler (with data).
292
	 */
293
	public function getCount(): int
294
	{
295
		return is_array($this->_handler) ? 3 : 2;
296
	}
297
298
	/**
299
	 * These values exist: null (the handler), 0, 2, and conditionally 1 on the handler
300
	 * being an array.
301
	 * @param mixed $offset The offset to check existence.
302
	 * @return bool Does the property exist for the managed event handler (with data).
303
	 */
304
	public function offsetExists(mixed $offset): bool
305
	{
306
		return $offset === null || $offset === 0 || $offset === 2 || (is_array($this->_handler) ? $offset === 1 : 0);
307
	}
308
309
	/**
310
	 * This is a convenience method for getting the data of the TEventHandler.
311
	 * - Index null will return the {@see getHandler Handler},
312
	 * - Index '0' will return the handler if its a string or object, or the first element
313
	 *   of the callable array;
314
	 * - Index '1' will return the second element of the callable array or null if not
315
	 *   an array; and
316
	 * - Index '2' will return the data associated with the managed event handler.
317
	 * If the WeakReference object of the managed event handler is invalid, this will return null
318
	 * for all handler offsets (null, 0 and 1).  Data will still return properly even
319
	 * when the handler is invalid.
320
	 * @param mixed $offset Which property of the managed event handler to retrieve.
321
	 * @throws TInvalidDataValueException When $offset is not a property of the managed event handler.
322
	 * @return mixed The value of the handler, handler elements, or data.
323
	 */
324
	public function offsetGet(mixed $offset): mixed
325
	{
326
		if ($offset === null) {
327
			return $this->getHandler();
328
		}
329
		if (is_numeric($offset)) {
330
			$offset = (int) $offset;
331
			if ($offset === 2) {
332
				return $this->_data;
333
			} elseif (is_array($this->_handler)) {
334
				if ($offset === 0) {
335
					if ($this->_weakObject) {
336
						return $this->_handler[$offset]->get();
337
					}
338
					return $this->_handler[$offset];
339
				} elseif ($offset === 1) {
340
					if ($this->_weakObject && !$this->_handler[0]->get()) {
341
						return null;
342
					}
343
					return $this->_handler[$offset];
344
				}
345
			} elseif ($offset === 0) {
346
				if ($this->_weakObject) {
347
					return $this->_handler->get();
348
				}
349
				return $this->_handler;
350
			} elseif ($offset === 1) {
351
				return null;
352
			}
353
		}
354
		throw new TInvalidDataValueException('eventhandler_bad_offset', $offset);
355
	}
356
357
	/**
358
	 * This is a convenience method for setting the data of the managed event handler.
359
	 * This cannot set the event handler but can set the data.
360
	 * - Index '2' will set the data associated with the managed event handler.
361
	 * @param mixed $offset Only accepts '2' to set the data associated with the event handler.
362
	 * @param mixed $value The data being set.
363
	 * @throws TInvalidOperationException When trying to set the handler elements.
364
	 * @throws TInvalidDataValueException When $offset is not a property of the managed event handler.
365
	 */
366
	public function offsetSet(mixed $offset, mixed $value): void
367
	{
368
		if (is_numeric($offset)) {
369
			$offset = (int) $offset;
370
			if ($offset === 2) {
371
				$this->setData($value);
372
				return;
373
			}
374
		}
375
		if ($offset === null || is_numeric($offset) && ($offset == 0 || $offset == 1)) {
376
			throw new TInvalidOperationException('eventhandler_no_set_handler', $offset);
377
		}
378
		throw new TInvalidDataValueException('eventhandler_bad_offset', $offset);
379
	}
380
381
	/**
382
	 * This is a convenience method for resetting the data to null.  The Handler cannot
383
	 * be unset.  However, the data can be unset.  The only valid index for unsetting
384
	 * the handler data is '2'.
385
	 * @param mixed $offset Only accepts '2' for the data element.
386
	 * @throws TInvalidOperationException When trying to set the handler elements.
387
	 * @throws TInvalidDataValueException When $offset is not a property of the managed event handler.
388
	 */
389
	public function offsetUnset(mixed $offset): void
390
	{
391
		if (is_numeric($offset)) {
392
			$offset = (int) $offset;
393
			if ($offset === 2) {
394
				$this->setData(null);
395
				return;
396
			}
397
		}
398
		if ($offset === null || is_numeric($offset) && ($offset == 0 || $offset == 1)) {
399
			throw new TInvalidOperationException('eventhandler_no_set_handler', $offset);
400
		}
401
		throw new TInvalidDataValueException('eventhandler_bad_offset', $offset);
402
	}
403
}
404