Event::trigger()   C
last analyzed

Complexity

Conditions 12
Paths 169

Size

Total Lines 63

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 63
ccs 29
cts 29
cp 1
rs 5.9206
c 0
b 0
f 0
cc 12
nc 169
nop 3
crap 12

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This software package is licensed under AGPL or Commercial license.
5
 *
6
 * @package maslosoft/mangan
7
 * @licence AGPL or Commercial
8
 * @copyright Copyright (c) Piotr Masełkowski <[email protected]>
9
 * @copyright Copyright (c) Maslosoft
10
 * @copyright Copyright (c) Others as mentioned in code
11
 * @link https://maslosoft.com/mangan/
12
 */
13
14
namespace Maslosoft\Mangan\Events;
15
16
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
17
use Maslosoft\Addendum\Utilities\ClassChecker;
18
use Maslosoft\Hi5Edit\Models\Href;
19
use Maslosoft\Mangan\Interfaces\EntityManagerInterface;
20
use Maslosoft\Mangan\Interfaces\Events\EventInterface;
21
use Maslosoft\Mangan\Meta\ManganMeta;
22
use ReflectionClass;
23
use UnexpectedValueException;
24
25
/**
26
 * This is based on Yii 2 Events
27
 */
28
/**
29
 * @link http://www.yiiframework.com/
30
 * @copyright Copyright (c) 2008 Yii Software LLC
31
 * @license http://www.yiiframework.com/license/
32
 */
33
34
/**
35
 * Event is the base class for all event classes.
36
 *
37
 * It encapsulates the parameters associated with an event.
38
 * The [[sender]] property describes who raises the event.
39
 * And the [[handled]] property indicates if the event is handled.
40
 * If an event handler sets [[handled]] to be true, the rest of the
41
 * uninvoked handlers will no longer be called to handle the event.
42
 *
43
 * Additionally, when attaching an event handler, extra data may be passed
44
 * and be available via the [[data]] property when the event handler is invoked.
45
 *
46
 * @author Qiang Xue <[email protected]>
47
 * @since 2.0
48
 */
49
class Event implements EventInterface
50
{
51
52
	/**
53
	 * @var string the event name. This property is set by [[Component::trigger()]] and [[trigger()]].
54
	 * Event handlers may use this property to check what event it is handling.
55
	 */
56
	public $name;
57
58
	/**
59
	 * @var object the sender of this event. If not set, this property will be
60
	 * set as the object whose "trigger()" method is called.
61
	 * This property may also be a `null` when this event is a
62
	 * class-level event which is triggered in a static context.
63
	 */
64
	public $sender;
65
66
	/**
67
	 * @var boolean whether the event is handled. Defaults to false.
68
	 * When a handler sets this to be true, the event processing will stop and
69
	 * ignore the rest of the uninvoked event handlers.
70
	 */
71
	public $handled = false;
72
73
	/**
74
	 * @var mixed the data that is passed to [[Component::on()]] when attaching an event handler.
75
	 * Note that this varies according to which event handler is currently executing.
76
	 */
77
	public $data;
78
79
	/**
80
	 * Array of events. This contains all event registered for application.
81
	 * @var EventInterface[]
82
	 */
83
	private static $events = [];
84
85
	/**
86
	 * Array containing partial classes for class. This holds traits, interfaces,
87
	 * parent classes names for class.
88
	 *
89
	 * Structure is like below:
90
	 * ```
91
	 * $partials = [
92
	 * 		'\Vendor\Package\ClassOne' => [
93
	 * 			'\Vendor\Package\TraitOne',
94
	 * 			'\Vendor\Package\TraitTwo',
95
	 * 			'\Vendor\Package\InterfaceX',
96
	 * 			'\Vendor\Package\AbstractSix'
97
	 * 		],
98
	 * 		'\Vendor\Package\ClassTwo' => [
99
	 * 			'\Vendor\Package\TraitBig',
100
	 * 			'\Vendor\Package\TraitSmall',
101
	 * 			'\Vendor\Package\InterfaceY',
102
	 * 			'\Vendor\Package\AbstractGuy'
103
	 * 		],
104
	 * ];
105
	 * ```
106
	 *
107
	 * @var string[][]
108
	 */
109
	private static $partials = [];
110
111
	/**
112
	 * Propagated properties cache. It contains only properties which should
113
	 * propagate, others are skipped, thus value is always true.
114
	 *
115
	 * Structure is like follow:
116
	 * ```
117
	 * $propagated = [
118
	 * 		'\Vendor\Package\ClassOne' => [
119
	 * 			'fieldOne' => true,
120
	 * 			'fieldTwo' => true
121
	 * 		],
122
	 * 		'\Vendor\Package\ClassTwo' => [
123
	 * 			'otherFieldOne' => true,
124
	 * 			'otherFieldTwo' => true
125
	 * 		],
126
	 *
127
	 * ];
128
	 * ```
129
	 * @var bool[]
130
	 */
131
	private static $propagated = [];
132
133
	/**
134
	 * Attaches an event handler to a class-level event.
135
	 *
136
	 * When a class-level event is triggered, event handlers attached
137
	 * to that class and all parent classes will be invoked.
138
	 *
139
	 * For example, the following code attaches an event handler to document's
140
	 * `afterInsert` event:
141
	 *
142
	 * ~~~
143
	 * Event::on($model, EntityManager::EventAfterInsert, function ($event) {
144
	 * 		var_dump(get_class($event->sender) . ' is inserted.');
145
	 * });
146
	 * ~~~
147
	 *
148
	 * The handler will be invoked for every successful document insertion.
149
	 *
150
	 * **NOTE:** Each call will attach new event handler. When placing event
151
	 * initialization in class constructors etc. ensure that it is evaluated once,
152
	 * or it might trigger same event handler multiple times.
153
	 *
154
	 * @param AnnotatedInterface|object|string $model the object specifying the class-level event.
155
	 * @param string $name the event name.
156
	 * @param callable $handler the event handler.
157
	 * @param mixed $data the data to be passed to the event handler when the event is triggered.
158
	 * When the event handler is invoked, this data can be accessed via [[Event::data]].
159
	 * @param boolean $append whether to append new event handler to the end of the existing
160
	 * handler list. If false, the new handler will be inserted at the beginning of the existing
161
	 * handler list.
162
	 * @see off()
163
	 */
164 23
	public static function on($model, $name, $handler, $data = null, $append = true)
165
	{
166 23
		$class = self::getName($model);
167 23
		if ($append || empty(self::$events[$name][$class]))
168
		{
169 23
			self::$events[$name][$class][] = [$handler, $data];
170
		}
171
		else
172
		{
173
			array_unshift(self::$events[$name][$class], [$handler, $data]);
174
		}
175 23
	}
176
177
	/**
178
	 * Detaches an event handler from a class-level event.
179
	 *
180
	 * This method is the opposite of [[on()]].
181
	 *
182
	 * @param AnnotatedInterface|object|string $model the object specifying the class-level event.
183
	 * @param string $name the event name.
184
	 * @param callable $handler the event handler to be removed.
185
	 * If it is null, all handlers attached to the named event will be removed.
186
	 * @return boolean whether a handler is found and detached.
187
	 * @see on()
188
	 */
189 10
	public static function off($model, $name, $handler = null)
190
	{
191 10
		$class = self::getName($model);
192 10
		if (empty(self::$events[$name][$class]))
193
		{
194
			return false;
195
		}
196 10
		if ($handler === null)
197
		{
198
			unset(self::$events[$name][$class]);
199
			return true;
200
		}
201
		else
202
		{
203 10
			$removed = false;
204 10
			foreach (self::$events[$name][$class] as $i => $event)
205
			{
206 10
				if ($event[0] === $handler)
207
				{
208 10
					unset(self::$events[$name][$class][$i]);
209 10
					$removed = true;
210
				}
211
			}
212 10
			if ($removed)
213
			{
214 10
				self::$events[$name][$class] = array_values(self::$events[$name][$class]);
215
			}
216 10
			return $removed;
217
		}
218
	}
219
220
	/**
221
	 * Triggers a class-level event.
222
	 * This method will cause invocation of event handlers that are attached to the named event
223
	 * for the specified class and all its parent classes.
224
	 * @param AnnotatedInterface|string $model the object specifying the class-level event.
225
	 * @param string $name the event name.
226
	 * @param ModelEvent $event the event parameter. If not set, a default `ModelEvent` object will be created.
227
	 * @return bool True if event was triggered.
228
	 */
229 158
	public static function trigger($model, $name, &$event = null)
230
	{
231 158
		$wasTriggered = false;
232 158
		if (empty(self::$events[$name]))
233
		{
234 155
			return self::propagate($model, $name, $event);
235
		}
236 134
		if ($event === null)
237
		{
238 130
			$event = new ModelEvent();
239
		}
240 134
		$event->handled = false;
241 134
		$event->name = $name;
242
243 134
		if ($event->sender === null && is_object($model))
244
		{
245 134
			$event->sender = $model;
246
		}
247 134
		if(is_object($model))
248
		{
249 134
			$event->currentTarget = $model;
250
		}
251 134
		$className = self::getName($model);
252
253
		// Partials holds parts of class, this include interfaces and traits
254 134
		$allPartials = self::getPartials($className);
255
256
		// Filter out empty partials
257 134
		$partials = [];
258 134
		foreach ($allPartials as $className)
259
		{
260 134
			if (empty(self::$events[$name][$className]))
261
			{
262 134
				continue;
263
			}
264 134
			$partials[] = $className;
265
		}
266
267
		// Trigger all partial events if applicable
268 134
		foreach ($partials as $className)
269
		{
270 134
			foreach (self::$events[$name][$className] as $handler)
271
			{
272
				// Assign source for easier debugging or other uses
273 134
				$event->source = $className;
274
275 134
				$event->data = $handler[1];
276 134
				$event->usedHandler = $handler[0];
277 134
				call_user_func($handler[0], $event);
278 134
				$wasTriggered = true;
279
280
281
				// Event was handled, return true
282 134
				if ($event->handled)
283
				{
284 3
					return true;
285
				}
286
			}
287
		}
288
289
		// Propagate events to sub objects
290 134
		return self::propagate($model, $name, $event) || $wasTriggered;
291
	}
292
293
	/**
294
	 * Triggers a class-level event and checks if it's valid.
295
	 * If don't have event handler returns true. If event handler is set, return true if `Event::isValid`.
296
	 * This method will cause invocation of event handlers that are attached to the named event
297
	 * for the specified class and all its parent classes.
298
	 * @param AnnotatedInterface|string $model the object specifying the class-level event.
299
	 * @param string $name the event name.
300
	 * @param ModelEvent $event the event parameter. If not set, a default [[ModelEvent]] object will be created.
301
	 * @return bool True if event was triggered and is valid.
302
	 */
303 157
	public static function valid($model, $name, $event = null)
304
	{
305 157
		if (Event::trigger($model, $name, $event))
306
		{
307 130
			return $event->isValid;
308
		}
309
		else
310
		{
311 86
			return true;
312
		}
313
	}
314
315
	/**
316
	 * Triggers a class-level event and checks if it's handled.
317
	 * If don't have event handler returns true. If event handler is set, return true if `Event::handled`.
318
	 * This method will cause invocation of event handlers that are attached to the named event
319
	 * for the specified class and all its parent classes.
320
	 * @param AnnotatedInterface|string $model the object specifying the class-level event.
321
	 * @param string $name the event name.
322
	 * @param ModelEvent $event the event parameter. If not set, a default [[Event]] object will be created.
323
	 * @return bool|null True if handled, false otherways, null if not triggered
324
	 */
325 1
	public static function handled($model, $name, $event = null)
326
	{
327 1
		if (Event::trigger($model, $name, $event))
328
		{
329 1
			return $event->handled;
330
		}
331
		return true;
332
	}
333
334
	/**
335
	 * Check if model has event handler.
336
	 * **IMPORTANT**: It does not check for propagated events
337
	 *
338
	 * @param AnnotatedInterface|string $class the object specifying the class-level event
339
	 * @param string $name the event name.
340
	 * @return bool True if has handler
341
	 */
342 108
	public static function hasHandler($class, $name)
343
	{
344
		// Partials holds parts of class, this include interfaces and traits
345 108
		$partials = self::getPartials(self::getName($class));
346 108
		foreach ($partials as $className)
347
		{
348 108
			if (!empty(self::$events[$name][$className]))
349
			{
350 108
				return true;
351
			}
352
		}
353 2
		return false;
354
	}
355
356
	/**
357
	 * Get class name
358
	 * @param AnnotatedInterface|object|string $class
359
	 * @return string
360
	 */
361 134
	private static function getName($class)
362
	{
363 134
		if (is_object($class))
364
		{
365 134
			$class = get_class($class);
366
		}
367
		else
368
		{
369 3
			if (!ClassChecker::exists($class))
370
			{
371
				throw new UnexpectedValueException(sprintf("Class `%s` not found", $class));
372
			}
373
		}
374 134
		return ltrim($class, '\\');
375
	}
376
377
	/**
378
	 * Propagate event
379
	 * @param AnnotatedInterface|string $model
380
	 * @param string $name
381
	 * @param ModelEvent|null $event
382
	 */
383 158
	private static function propagate($model, $name, &$event = null)
384
	{
385 158
		$wasTriggered = false;
386 158
		if ($event && !$event->propagate())
387
		{
388 1
			return false;
389
		}
390
391 157
		foreach (self::getPropagatedProperties($model) as $property => $propagate)
392
		{
393 39
			if(!is_object($model))
394
			{
395
				continue;
396
			}
397 39
			if (empty($model->$property))
398
			{
399
				// Property is empty, skip
400 12
				continue;
401
			}
402
			// Trigger for arrays
403 35
			if (is_array($model->$property))
404
			{
405 14
				foreach ($model->$property as $object)
406
				{
407 14
					$wasTriggered = self::trigger($object, $name, $event) || $wasTriggered;
408
				}
409 14
				continue;
410
			}
411 26
			$wasTriggered = self::trigger($model->$property, $name, $event) || $wasTriggered;
412
		}
413 157
		return $wasTriggered;
414
	}
415
416
	/**
417
	 * Get properties which should be propagated.
418
	 * NOTE: This is cached, as it might be called numerous times
419
	 * @param object|string $model
420
	 * @return bool[]
421
	 */
422 157
	private static function getPropagatedProperties($model)
423
	{
424 157
		$key = get_class($model);
425 157
		if (empty(self::$propagated[$key]))
426
		{
427 157
			$propagated = [];
428 157
			foreach (ManganMeta::create($model)->properties('propagateEvents') as $name => $isPropagated)
429
			{
430 157
				if (!$isPropagated)
431
				{
432 157
					continue;
433
				}
434 28
				$propagated[$name] = true;
435
			}
436 157
			self::$propagated[$key] = $propagated;
437
		}
438 157
		return self::$propagated[$key];
439
	}
440
441
	/**
442
	 * Get class/interface/trait names from which class is composed.
443
	 *
444
	 * @param string $className
445
	 * @return array
446
	 */
447 134
	public static function getPartials($className)
448
	{
449 134
		if (array_key_exists($className, self::$partials))
450
		{
451
			return self::$partials[$className];
452
		}
453 134
		if(!ClassChecker::exists($className))
454
		{
455
			self::$partials[$className] = [];
456
			return [];
457
		}
458 134
		$partials = [];
459
		// Iterate over traits
460 134
		foreach ((new ReflectionClass($className))->getTraitNames() as $trait)
461
		{
462 24
			$partials[] = $trait;
463
		}
464
465
		// Iterate over interfaces to get partials
466 134
		foreach ((new ReflectionClass($className))->getInterfaceNames() as $interface)
467
		{
468 134
			$partials[] = $interface;
469
		}
470
471
		// Iterate over parent classes
472
		do
473
		{
474 134
			$partials[] = $className;
475
476
			// Iterate over traits of parent class
477 134
			foreach ((new ReflectionClass($className))->getTraitNames() as $trait)
478
			{
479 69
				$partials[] = $trait;
480
			}
481
482
			// Iterate over interfaces of parent class
483 134
			foreach ((new ReflectionClass($className))->getInterfaceNames() as $interface)
484
			{
485 134
				$partials[] = $interface;
486
			}
487
488
		}
489 134
		while (($className = get_parent_class($className)) !== false);
490 134
		$partials = array_unique($partials);
491 134
		sort($partials);
492 134
		self::$partials[$className] = $partials;
493 134
		return $partials;
494
	}
495
496
	protected static function destroyEvents()
497
	{
498
		self::$events = [];
499
	}
500
501
}
502