Passed
Push — php8 ( bd75dc...d8afb4 )
by Fabio
08:15
created

TComponent::__clone()   C

Complexity

Conditions 15
Paths 8

Size

Total Lines 30
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 19
c 0
b 0
f 0
nc 8
nop 0
dl 0
loc 30
rs 5.9166

How to fix   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
 * TComponent, TPropertyValue classes
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 *
7
 * Global Events, intra-object events, Class behaviors, expanded behaviors
8
 * @author Brad Anderson <[email protected]>
9
 *
10
 * @author Qiang Xue <[email protected]>
11
 * @link https://github.com/pradosoft/prado
12
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
13
 */
14
15
namespace Prado;
16
17
use Prado\Util\IBaseBehavior;
18
use Prado\Web\Javascripts\TJavaScriptLiteral;
19
use Prado\Web\Javascripts\TJavaScriptString;
20
use Prado\Util\TCallChain;
21
use Prado\Util\IBehavior;
22
use Prado\Util\IDynamicMethods;
23
use Prado\Util\IClassBehavior;
24
use Prado\Util\TClassBehaviorEventParameter;
25
use Prado\Exceptions\TApplicationException;
26
use Prado\Exceptions\TInvalidOperationException;
27
use Prado\Exceptions\TInvalidDataTypeException;
28
use Prado\Exceptions\TInvalidDataValueException;
29
use Prado\Collections\TWeakCallableCollection;
30
use Prado\Collections\TPriorityMap;
31
32
/**
33
 * TComponent class
34
 *
35
 * TComponent is the base class for all PRADO components.
36
 * TComponent implements the protocol of defining, using properties, behaviors,
37
 * and events.
38
 *
39
 * A property is defined by a getter method, and/or a setter method.
40
 * Properties can be accessed in the way like accessing normal object members.
41
 * Reading or writing a property will cause the invocation of the corresponding
42
 * getter or setter method, e.g.,
43
 * <code>
44
 * $a=$this->Text;     // equivalent to $a=$this->getText();
45
 * $this->Text='abc';  // equivalent to $this->setText('abc');
46
 * </code>
47
 * The signatures of getter and setter methods are as follows,
48
 * <code>
49
 * // getter, defines a readable property 'Text'
50
 * function getText() { ... }
51
 * // setter, defines a writable property 'Text', with $value being the value to be set to the property
52
 * function setText($value) { ... }
53
 * </code>
54
 * Property names are case-insensitive. It is recommended that they are written
55
 * in the format of concatenated words, with the first letter of each word
56
 * capitalized (e.g. DisplayMode, ItemStyle).
57
 *
58
 * Javascript Get and Set
59
 *
60
 * Since Prado 3.2 a new class of javascript-friendly properties have been introduced
61
 * to better deal with potential security problems like cross-site scripting issues.
62
 * All the data that gets sent clientside inside a javascript block is now encoded by default.
63
 * Sometimes there's the need to bypass this encoding and be able to send raw javascript code.
64
 * This new class of javascript-friendly properties are identified by their name
65
 * starting with 'js' (case insensitive):
66
 * <code>
67
 * // getter, defines a readable property 'Text'
68
 * function getJsText() { ... }
69
 * // setter, defines a writable property 'Text', with $value being the value to be set to the property
70
 * function setJsText(TJavaScriptLiteral $value) { ... }
71
 * </code>
72
 * Js-friendly properties can be accessed using both their Js-less name and their Js-enabled name:
73
 * <code>
74
 * // set some simple text as property value
75
 * $component->Text = 'text';
76
 * // set some javascript code as property value
77
 * $component->JsText = 'raw javascript';
78
 * </code>
79
 * In the first case, the property value will automatically gets encoded when sent
80
 * clientside inside a javascript block.
81
 * In the second case, the property will be 'marked' as being a safe javascript
82
 * statement and will not be encoded when rendered inside a javascript block.
83
 * This special handling makes use of the {@link TJavaScriptLiteral} class.
84
 *
85
 * Events
86
 *
87
 * An event is defined by the presence of a method whose name starts with 'on'.
88
 * The event name is the method name and is thus case-insensitive.
89
 * An event can be attached with one or several methods (called event handlers).
90
 * An event can be raised by calling {@link raiseEvent} method, upon which
91
 * the attached event handlers will be invoked automatically in the order they
92
 * are attached to the event. Event handlers must have the following signature,
93
 * <code>
94
 * function eventHandlerFuncName($sender, $param) { ... }
95
 * </code>
96
 * where $sender refers to the object who is responsible for the raising of the event,
97
 * and $param refers to a structure that may contain event-specific information.
98
 * To raise an event (assuming named as 'Click') of a component, use
99
 * <code>
100
 * $component->raiseEvent('OnClick');
101
 * $component->raiseEvent('OnClick', $this, $param);
102
 * </code>
103
 * To attach an event handler to an event, use one of the following ways,
104
 * <code>
105
 * $component->OnClick = $callback;
106
 * $component->OnClick->add($callback);
107
 * $component->attachEventHandler('OnClick', $callback);
108
 * </code>
109
 * The first two ways make use of the fact that $component->OnClick refers to
110
 * the event handler list {@link TWeakCallableCollection} for the 'OnClick' event.
111
 * The variable $callback contains the definition of the event handler that can
112
 * be either:
113
 *
114
 * a string referring to a global function name
115
 * <code>
116
 * $component->OnClick = 'buttonClicked';
117
 * // will cause the following function to be called
118
 * buttonClicked($sender, $param);
119
 * </code>
120
 *
121
 * an array whose first element refers to an object and second element a method name/path that is reachable by the object
122
 * <code>
123
 * $component->OnClick = [$object, 'buttonClicked'];
124
 * // will cause the following function to be called
125
 * $object->buttonClicked($sender, param);
126
 *
127
 * // the method can also be expressed using the PRADO namespace format
128
 * $component->OnClick = [$object, 'MainContent.SubmitButton.buttonClicked'];
129
 * // will cause the following function to be called
130
 * $object->MainContent->SubmitButton->buttonClicked($sender, $param);
131
 * </code
132
 *
133
 * Global and dynamic events
134
 *
135
 * With the addition of behaviors, a more expansive event model is needed.  There
136
 * are two new event types (global and dynamic events) as well as a more comprehensive
137
 * behavior model that includes class wide behaviors.
138
 *
139
 * A global event is defined by all events whose name starts with 'fx'.
140
 * The event name is potentially a method name and is thus case-insensitive. All 'fx' events
141
 * are valid as the whole 'fx' event/method space is global in nature. Any object may patch into
142
 * any global event by defining that event as a method. Global events have priorities
143
 * just like 'on' events; so as to be able to order the event execution. Due to the
144
 * nature of all events which start with 'fx' being valid, in effect, every object
145
 * has every 'fx' global event. It is simply an issue of tapping into the desired
146
 * global event.
147
 *
148
 * A global event that starts with 'fx' can be called even if the object does not
149
 * implement the method of the global event.  A call to a non-existing 'fx' method
150
 * will, at minimal, function and return null.  If a method argument list has a first
151
 * parameter, it will be returned instead of null.  This allows filtering and chaining.
152
 * 'fx' methods do not automatically install and uninstall. To install and uninstall an
153
 * object's global event listeners, call the object's {@link listen} and
154
 * {@link unlisten} methods, respectively.  An object may auto-install its global event
155
 * during {@link __construct} by overriding {@link getAutoGlobalListen} and returning true.
156
 *
157
 * As of PHP version 5.3, nulled objects without code references will still continue to persist
158
 * in the global event queue because {@link __destruct} is not automatically called.  In the common
159
 * __destruct method, if an object is listening to global events, then {@link unlisten} is called.
160
 * {@link unlisten} is required to be manually called before an object is
161
 * left without references if it is currently listening to any global events. This includes
162
 * class wide behaviors.  This is corrected in PHP 7.4.0 with WeakReferences and {@link
163
 * TWeakCallableCollection}
164
 *
165
 * An object that contains a method that starts with 'fx' will have those functions
166
 * automatically receive those events of the same name after {@link listen} is called on the object.
167
 *
168
 * An object may listen to a global event without defining an 'fx' method of the same name by
169
 * adding an object method to the global event list.  For example
170
 * <code>
171
 * $component->fxGlobalCheck=$callback;
172
 * $component->fxGlobalCheck->add($callback);
173
 * $component->attachEventHandler('fxGlobalCheck', [$object, 'someMethod']);
174
 * </code>
175
 *
176
 * Events between Objects and their behaviors, Dynamic Events
177
 *
178
 * An intra-object/behavior event is defined by methods that start with 'dy'.  Just as with
179
 * 'fx' global events, every object has every dynamic event.  Any call to a method that
180
 * starts with 'dy' will be handled, regardless of whether it is implemented.  These
181
 * events are for communicating with attached behaviors.
182
 *
183
 * Dynamic events can be used in a variety of ways.  They can be used to tell behaviors
184
 * when a non-behavior method is called.  Dynamic events could be used as data filters.
185
 * They could also be used to specify when a piece of code is to be run, eg. should the
186
 * loop process be performed on a particular piece of data.  In this way, some control
187
 * is handed to the behaviors over the process and/or data.
188
 *
189
 * If there are no handlers for an 'fx' or 'dy' event, it will return the first
190
 * parameter of the argument list.  If there are no arguments, these events
191
 * will return null.  If there are handlers an 'fx' method will be called directly
192
 * within the object.  Global 'fx' events are triggered by calling {@link raiseEvent}.
193
 * For dynamic events where there are behaviors that respond to the dynamic events, a
194
 * {@link TCallChain} is developed.  A call chain allows the behavior dynamic event
195
 * implementations to call further implementing behaviors within a chain.
196
 *
197
 * If an object implements {@link IDynamicMethods}, all global and object dynamic
198
 * events will be sent to {@link __dycall}.  In the case of global events, all
199
 * global events will trigger this method.  In the case of behaviors, all undefined
200
 * dynamic events  which are called will be passed through to this method.
201
 *
202
 *
203
 * Behaviors
204
 *
205
 * There are two types of behaviors.  There are individual object behaviors and
206
 * there are class wide behaviors.  Class behaviors depend upon object behaviors.
207
 *
208
 * When a new class implements {@link IBehavior} or {@link IClassBehavior} or
209
 * extends {@link TBehavior} or {@link TClassBehavior}, it may be attached to an
210
 * object by calling the object's {@link attachBehavior}.  The behaviors associated
211
 * name can then be used to {@link enableBehavior} or {@link disableBehavior}
212
 * the specific behavior.
213
 *
214
 * All behaviors may be turned on and off via {@link enableBehaviors} and
215
 * {@link disableBehaviors}, respectively.  To check if behaviors are on or off
216
 * a call to {@link getBehaviorsEnabled} will provide the variable.
217
 *
218
 * Attaching and detaching whole sets of behaviors is done using
219
 * {@link attachBehaviors} and {@link detachBehaviors}.  {@link clearBehaviors}
220
 * removes all of an object's behaviors.
221
 *
222
 * {@link asa} returns a behavior of a specific name.  {@link isa} is the
223
 * behavior inclusive function that acts as the PHP operator {@link instanceof}.
224
 * A behavior could provide the functionality of a specific class thus causing
225
 * the host object to act similarly to a completely different class.  A behavior
226
 * would then implement {@link IInstanceCheck} to provide the identity of the
227
 * different class.
228
 *
229
 * Class behaviors are similar to object behaviors except that the class behavior
230
 * is the implementation for all instances of the class.  A class behavior
231
 * will have the object upon which is being called be prepended to the parameter
232
 * list.  This way the object is known across the class behavior implementation.
233
 *
234
 * Class behaviors are attached using {@link attachClassBehavior} and detached
235
 * using {@link detachClassBehavior}.  Class behaviors are important in that
236
 * they will be applied to all new instances of a particular class.  In this way
237
 * class behaviors become default behaviors to a new instances of a class in
238
 * {@link __construct}.  Detaching a class behavior will remove the behavior
239
 * from the default set of behaviors created for an object when the object
240
 * is instanced.
241
 *
242
 * Class behaviors are also added to all existing instances via the global 'fx'
243
 * event mechanism.  When a new class behavior is added, the event
244
 * {@link fxAttachClassBehavior} is raised and all existing instances that are
245
 * listening to this global event (primarily after {@link listen} is called)
246
 * will have this new behavior attached.  A similar process is used when
247
 * detaching class behaviors.  Any objects listening to the global 'fx' event
248
 * {@link fxDetachClassBehavior} will have a class behavior removed.
249
 *
250
 * Dynamic Intra-Object Events
251
 *
252
 * Dynamic events start with 'dy'.  This mechanism is used to allow objects
253
 * to communicate with their behaviors directly.  The entire 'dy' event space
254
 * is valid.  All attached, enabled behaviors that implement a dynamic event
255
 * are called when the host object calls the dynamic event.  If there is no
256
 * implementation or behaviors, this returns null when no parameters are
257
 * supplied and will return the first parameter when there is at least one
258
 * parameter in the dynamic event.
259
 * <code>
260
 *	 null == $this->dyBehaviorEvent();
261
 *	 5 == $this->dyBehaviorEvent(5); //when no behaviors implement this dynamic event
262
 * </code>
263
 *
264
 * Dynamic events can be chained together within behaviors to allow for data
265
 * filtering. Dynamic events are implemented within behaviors by defining the
266
 * event as a method.
267
 * <code>
268
 * class TObjectBehavior extends TBehavior {
269
 *     public function dyBehaviorEvent($param1, $callchain) {
270
 *			//Do something, eg:  $param1 += 13;
271
 *			return $callchain->dyBehaviorEvent($param1);
272
 *     }
273
 * }
274
 * </code>
275
 * This implementation of a behavior and dynamic event will flow through to the
276
 * next behavior implementing the dynamic event.  The first parameter is always
277
 * return when it is supplied.  Otherwise a dynamic event returns null.
278
 *
279
 * In the case of a class behavior, the object is also prepended to the dynamic
280
 * event.
281
 * <code>
282
 * class TObjectClassBehavior extends TClassBehavior {
283
 *     public function dyBehaviorEvent($hostobject, $param1, $callchain) {
284
 *			//Do something, eg:  $param1 += $hostobject->getNumber();
285
 *			return $callchain->dyBehaviorEvent($param1);
286
 *     }
287
 * }
288
 * </code>
289
 * When calling a dynamic event, only the parameters are passed.  The host object
290
 * and the call chain are built into the framework.
291
 *
292
 * Global Event and Dynamic event catching
293
 *
294
 * Given that all global 'fx' events and dynamic 'dy' events are valid and
295
 * operational, there is a mechanism for catching events called that are not
296
 * implemented (similar to the built-in PHP method {@link __call}).  When
297
 * a dynamic or global event is called but a behavior does not implement it,
298
 * yet desires to know when an undefined dynamic event is run, the behavior
299
 * implements the interface {@link IDynamicMethods} and method {@link __dycall}.
300
 *
301
 * In the case of dynamic events, {@link __dycall} is supplied with the method
302
 * name and its parameters.  When a global event is raised, via {@link raiseEvent},
303
 * the method is the event name and the parameters are supplied.
304
 *
305
 * When implemented, this catch-all mechanism is called for event global event event
306
 * when implemented outside of a behavior.  Within a behavior, it will also be called
307
 * when the object to which the behavior is attached calls any unimplemented dynamic
308
 * event.  This is the fall-back mechanism for informing a class and/or behavior
309
 * of when an global and/or undefined dynamic event is executed.
310
 *
311
 * @author Qiang Xue <[email protected]>
312
 * @author Brad Anderson <[email protected]>
313
 * @since 3.0
314
 * @method void dyClone()
315
 * @method void dyWakeUp()
316
 * @method void dyListen(array $globalEvents)
317
 * @method void dyUnlisten(array $globalEvents)
318
 * @method string dyPreRaiseEvent(string $name, mixed $sender, \Prado\TEventParameter $param, null|numeric $responsetype, null|function $postfunction)
319
 * @method dyIntraRaiseEventTestHandler(callable $handler, mixed $sender, \Prado\TEventParameter $param, string $name)
320
 * @method bool dyIntraRaiseEventPostHandler(string $name, mixed $sender, \Prado\TEventParameter $param, callable $handler, $response)
321
 * @method array dyPostRaiseEvent(array $responses, string $name, mixed $sender, \Prado\TEventParameter $param, null|numeric $responsetype, null|function $postfunction)
322
 * @method string dyEvaluateExpressionFilter(string $statements)
323
 * @method string dyEvaluateStatementsFilter(string $statements)
324
 * @method dyCreatedOnTemplate(\Prado\TComponent $parent)
325
 * @method void dyAddParsedObject(\Prado\TComponent|string $object)
326
 * @method void dyAttachBehavior(string $name, IBehavior $behavior)
327
 * @method void dyDetachBehavior(string $name, IBehavior $behavior)
328
 * @method void dyEnableBehavior(string $name, IBehavior $behavior)
329
 * @method void dyDisableBehavior(string $name, IBehavior $behavior)
330
 * @method void dyEnableBehaviors()
331
 * @method void dyDisableBehaviors()
332
 */
333
class TComponent
334
{
335
	/**
336
	 * @var array event handler lists
337
	 */
338
	protected $_e = [];
339
340
	/**
341
	 * @var bool if listening is enabled.  Automatically turned on or off in
342
	 * constructor according to {@link getAutoGlobalListen}.  Default false, off
343
	 */
344
	protected $_listeningenabled = false;
345
346
	/**
347
	 * @var array static registered global event handler lists
348
	 */
349
	private static $_ue = [];
350
351
	/**
352
	 * @var bool if object behaviors are on or off.  default true, on
353
	 */
354
	protected $_behaviorsenabled = true;
355
356
	/**
357
	 * @var TPriorityMap list of object behaviors
358
	 */
359
	protected $_m;
360
361
	/**
362
	 * @var array static global class behaviors, these behaviors are added upon instantiation of a class
363
	 */
364
	private static $_um = [];
365
366
367
	/**
368
	 * @const string the name of the global {@link raiseEvent} listener
369
	 */
370
	public const GLOBAL_RAISE_EVENT_LISTENER = 'fxGlobalListener';
371
372
373
	/**
374
	 * The common __construct
375
	 * If desired by the new object, this will auto install and listen to global event functions
376
	 * as defined by the object via 'fx' methods. This also attaches any predefined behaviors.
377
	 * This function installs all class behaviors in a class hierarchy from the deepest subclass
378
	 * through each parent to the top most class, TComponent.
379
	 */
380
	public function __construct()
381
	{
382
		if ($this->getAutoGlobalListen()) {
383
			$this->listen();
384
		}
385
386
		$classes = $this->getClassHierarchy(true);
387
		array_pop($classes);
388
		foreach ($classes as $class) {
389
			if (isset(self::$_um[$class])) {
390
				$this->attachBehaviors(self::$_um[$class]);
391
			}
392
		}
393
	}
394
395
	/**
396
	 * The common __clone magic method from PHP's "clone".
397
	 * This reattaches the behaviors to the cloned object.
398
	 * IBehavior objects are cloned, IClassBehaviors are not.
399
	 * Clone object events are scrubbed of the old object behaviors' events.
400
	 * To finalize the behaviors, dyClone is raised.
401
	 * @since 4.2.3
402
	 */
403
	public function __clone()
404
	{
405
		foreach ($this->_e as $event => $handlers) {
406
			$this->_e[$event] = clone $handlers;
407
		}
408
		$behaviorArray = array_values(($this->_m !== null) ? $this->_m->toArray() : []);
409
		if (count($behaviorArray) && count($this->_e)) {
410
			$behaviorArray = array_combine(array_map('spl_object_id', $behaviorArray), $behaviorArray);
411
			foreach ($this->_e as $event => $handlers) {
412
				foreach ($handlers->toArray() as $handler) {
413
					$a = is_array($handler);
414
					if ($a && array_key_exists(spl_object_id($handler[0]), $behaviorArray) || !$a && array_key_exists(spl_object_id($handler), $behaviorArray)) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($a && array_key_exists(...ndler), $behaviorArray), Probably Intended Meaning: $a && (array_key_exists(...dler), $behaviorArray))
Loading history...
415
						$handlers->remove($handler);
416
					}
417
				}
418
			}
419
		}
420
		if ($this->_m !== null) {
421
			$behaviors = $this->_m;
422
			$this->_m = new TPriorityMap();
423
			foreach ($behaviors->getPriorities() as $priority) {
424
				foreach ($behaviors->itemsAtPriority($priority) as $name => $behavior) {
425
					if (!($behavior instanceof \Prado\Util\IClassBehavior)) { //not a class behavior
426
						$behavior = clone $behavior;
427
					}
428
					$this->attachBehavior($name, $behavior, $priority);
429
				}
430
			}
431
		}
432
		$this->callBehaviorsMethod('dyClone', $return);
433
	}
434
435
	/**
436
	 * The common __wakeup magic method from PHP's "unserialize".
437
	 * This reattaches the behaviors to the reconstructed object.
438
	 * Any global class behaviors are used rather than their unserialized copy.
439
	 * Any global behaviors not found in the object will be added.
440
	 * To finalize the behaviors, dyWakeUp is raised.
441
	 * If a TModule needs to add events to an object during unserialization,
442
	 * the module can use a small IClassBehavior [implementing dyWakeUp]
443
	 * (adding the event[s]) attached to the class with {@link
444
	 * attachClassBehavior} prior to unserialization.
445
	 * @since 4.2.3
446
	 */
447
	public function __wakeup()
448
	{
449
		$classes = $this->getClassHierarchy(true);
450
		array_pop($classes);
451
		$classBehaviors = [];
452
		if ($this->_m !== null) {
453
			$behaviors = $this->_m;
454
			$this->_m = new TPriorityMap();
455
			foreach ($behaviors->getPriorities() as $priority) {
456
				foreach ($behaviors->itemsAtPriority($priority) as $name => $behavior) {
457
					if ($behavior instanceof \Prado\Util\IClassBehavior) {
458
						foreach ($classes as $class) {
459
							if (isset(self::$_um[$class]) && array_key_exists($name, self::$_um[$class])) {
460
								$behavior = self::$_um[$class][$name]->getBehavior();
461
								break;
462
							}
463
						}
464
					}
465
					$classBehaviors[$name] = $name;
466
					$this->attachBehavior($name, $behavior, $priority);
467
				}
468
			}
469
		}
470
		foreach ($classes as $class) {
471
			if (isset(self::$_um[$class])) {
472
				foreach (self::$_um[$class] as $name => $behavior) {
473
					if (!array_key_exists($name, $classBehaviors)) {
474
						$this->attachBehaviors([$behavior]);
475
					}
476
				}
477
			}
478
		}
479
		$this->callBehaviorsMethod('dyWakeUp', $return);
480
	}
481
482
483
	/**
484
	 * Tells TComponent whether or not to automatically listen to global events.
485
	 * Defaults to false because PHP variable cleanup is affected if this is true.
486
	 * When unsetting a variable that is listening to global events, {@link unlisten}
487
	 * must explicitly be called when cleaning variables allocation or else the global
488
	 * event registry will contain references to the old object. This is true for PHP 5.4
489
	 *
490
	 * Override this method by a subclass to change the setting.  When set to true, this
491
	 * will enable {@link __construct} to call {@link listen}.
492
	 *
493
	 * @return bool whether or not to auto listen to global events during {@link __construct}, default false
494
	 */
495
	public function getAutoGlobalListen()
496
	{
497
		return false;
498
	}
499
500
501
	/**
502
	 * The common __destruct
503
	 * This unlistens from the global event routines if listening
504
	 *
505
	 * PHP 5.3 does not __destruct objects when they are nulled and thus unlisten must be
506
	 * called must be explicitly called. PHP 7.4.0 uses WeakReferences and this will be called
507
	 * automatically.
508
	 */
509
	public function __destruct()
510
	{
511
		if ($this->_listeningenabled) {
512
			$this->unlisten();
513
		}
514
	}
515
516
517
	/**
518
	 * This utility function is a private array filter method.  The array values
519
	 * that start with 'fx' are filtered in.
520
	 * @param mixed $name
521
	 */
522
	private function filter_prado_fx($name)
523
	{
524
		return strncasecmp($name, 'fx', 2) === 0;
525
	}
526
527
528
	/**
529
	 * This returns an array of the class name and the names of all its parents.  The base object last,
530
	 * {@link TComponent}, and the deepest subclass is first.
531
	 * @param bool $lowercase optional should the names be all lowercase true/false
532
	 * @return string[] array of strings being the class hierarchy of $this.
533
	 */
534
	public function getClassHierarchy($lowercase = false)
535
	{
536
		static $_classhierarchy = [];
537
		$class = get_class($this);
538
		if (isset($_classhierarchy[$class]) && isset($_classhierarchy[$class][$lowercase ? 1 : 0])) {
539
			return $_classhierarchy[$class][$lowercase ? 1 : 0];
540
		}
541
		$classes = array_values(class_implements($class));
542
		array_push($classes, $class);
543
		while ($class = get_parent_class($class)) {
544
			array_push($classes, $class);
545
		}
546
		if ($lowercase) {
547
			$classes = array_map('strtolower', $classes);
548
		}
549
		$_classhierarchy[$class] ??= [];
550
		$_classhierarchy[$class][$lowercase ? 1 : 0] = $classes;
551
552
		return $classes;
553
	}
554
555
	/**
556
	 * This caches the 'fx' events for classes.
557
	 * @param object $class
558
	 * @return string[] fx events from a specific class
559
	 */
560
	protected function getClassFxEvents($class)
561
	{
562
		static $_classfx = [];
563
		$className = get_class($class);
564
		if (isset($_classfx[$className])) {
565
			return $_classfx[$className];
566
		}
567
		$fx = array_filter(get_class_methods($class), [$this, 'filter_prado_fx']);
568
		$_classfx[$className] = $fx;
569
		return $fx;
570
	}
571
572
	/**
573
	 * This adds an object's fx event handlers into the global broadcaster to listen into any
574
	 * broadcast global events called through {@link raiseEvent}
575
	 *
576
	 * Behaviors may implement the function:
577
	 * <code>
578
	 *	public function dyListen($globalEvents[, $chain]) {
579
	 * 		$this->listen(); //eg
580
	 * }
581
	 * </code>
582
	 * to be executed when listen is called.  All attached behaviors are notified through dyListen.
583
	 *
584
	 * @return numeric the number of global events that were registered to the global event registry
0 ignored issues
show
Bug introduced by
The type Prado\numeric was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
585
	 */
586
	public function listen()
587
	{
588
		if ($this->_listeningenabled) {
589
			return;
590
		}
591
592
		$fx = $this->getClassFxEvents($this);
593
594
		foreach ($fx as $func) {
595
			$this->getEventHandlers($func)->add([$this, $func]);
596
		}
597
598
		if (is_a($this, IDynamicMethods::class)) {
599
			$this->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, [$this, '__dycall']);
600
			array_push($fx, TComponent::GLOBAL_RAISE_EVENT_LISTENER);
601
		}
602
603
		$this->_listeningenabled = true;
604
605
		$this->dyListen($fx);
606
607
		return count($fx);
0 ignored issues
show
Bug Best Practice introduced by
The expression return count($fx) returns the type integer which is incompatible with the documented return type Prado\numeric.
Loading history...
608
	}
609
610
	/**
611
	 * this removes an object's fx events from the global broadcaster
612
	 *
613
	 * Behaviors may implement the function:
614
	 * <code>
615
	 *	public function dyUnlisten($globalEvents[, $chain]) {
616
	 * 		$this->behaviorUnlisten(); //eg
617
	 * }
618
	 * </code>
619
	 * to be executed when listen is called.  All attached behaviors are notified through dyUnlisten.
620
	 *
621
	 * @return numeric the number of global events that were unregistered from the global event registry
622
	 */
623
	public function unlisten()
624
	{
625
		if (!$this->_listeningenabled) {
626
			return;
627
		}
628
629
		$fx = $this->getClassFxEvents($this);
630
631
		foreach ($fx as $func) {
632
			$this->detachEventHandler($func, [$this, $func]);
633
		}
634
635
		if (is_a($this, IDynamicMethods::class)) {
636
			$this->detachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, [$this, '__dycall']);
637
			array_push($fx, TComponent::GLOBAL_RAISE_EVENT_LISTENER);
638
		}
639
640
		$this->_listeningenabled = false;
641
642
		$this->dyUnlisten($fx);
643
644
		return count($fx);
0 ignored issues
show
Bug Best Practice introduced by
The expression return count($fx) returns the type integer which is incompatible with the documented return type Prado\numeric.
Loading history...
645
	}
646
647
	/**
648
	 * Gets the state of listening to global events
649
	 * @return bool is Listening to global broadcast enabled
650
	 */
651
	public function getListeningToGlobalEvents()
652
	{
653
		return $this->_listeningenabled;
654
	}
655
656
657
	/**
658
	 * Calls a method.
659
	 * Do not call this method directly. This is a PHP magic method that we override
660
	 * to allow behaviors, dynamic events (intra-object/behavior events),
661
	 * undefined dynamic and global events, and
662
	 * to allow using the following syntax to call a property setter or getter.
663
	 * <code>
664
	 * $this->getPropertyName($value); // if there's a $this->getjsPropertyName() method
665
	 * $this->setPropertyName($value); // if there's a $this->setjsPropertyName() method
666
	 * </code>
667
	 *
668
	 * Additional object behaviors override class behaviors.
669
	 * dynamic and global events do not fail even if they aren't implemented.
670
	 * Any intra-object/behavior dynamic events that are not implemented by the behavior
671
	 * return the first function paramater or null when no parameters are specified.
672
	 *
673
	 * @param string $method method name that doesn't exist and is being called on the object
674
	 * @param mixed $args method parameters
675
	 * @throws TInvalidOperationException If the property is not defined or read-only or
676
	 * 		method is undefined
677
	 * @return mixed result of the method call, or false if 'fx' or 'dy' function but
678
	 *		is not found in the class, otherwise it runs
679
	 */
680
	public function __call($method, $args)
681
	{
682
		$getset = substr($method, 0, 3);
683
		if (($getset == 'get') || ($getset == 'set')) {
684
			$propname = substr($method, 3);
685
			$jsmethod = $getset . 'js' . $propname;
686
			if (method_exists($this, $jsmethod)) {
687
				if (count($args) > 0) {
688
					if ($args[0] && !($args[0] instanceof TJavaScriptString)) {
689
						$args[0] = new TJavaScriptString($args[0]);
690
					}
691
				}
692
				return $this->$jsmethod(...$args);
693
			}
694
695
			if (($getset == 'set') && method_exists($this, 'getjs' . $propname)) {
696
				throw new TInvalidOperationException('component_property_readonly', get_class($this), $method);
697
			}
698
		}
699
		if ($this->callBehaviorsMethod($method, $return, ...$args)) {
700
			return $return;
701
		}
702
703
		// don't thrown an exception for __magicMethods() or any other weird methods natively implemented by php
704
		if (!method_exists($this, $method)) {
705
			throw new TApplicationException('component_method_undefined', get_class($this), $method);
706
		}
707
	}
708
709
710
	/**
711
	 * Returns a property value or an event handler list by property or event name.
712
	 * Do not call this method. This is a PHP magic method that we override
713
	 * to allow using the following syntax to read a property:
714
	 * <code>
715
	 * $value=$component->PropertyName;
716
	 * $value=$component->jsPropertyName; // return JavaScript literal
717
	 * </code>
718
	 * and to obtain the event handler list for an event,
719
	 * <code>
720
	 * $eventHandlerList=$component->EventName;
721
	 * </code>
722
	 * This will also return the global event handler list when specifing an 'fx'
723
	 * event,
724
	 * <code>
725
	 * $globalEventHandlerList=$component->fxEventName;
726
	 * </code>
727
	 * When behaviors are enabled, this will return the behavior of a specific
728
	 * name, a property of a behavior, or an object 'on' event defined by the behavior.
729
	 * @param string $name the property name or the event name
730
	 * @throws TInvalidOperationException if the property/event is not defined.
731
	 * @return mixed the property value or the event handler list as {@link TWeakCallableCollection}
732
	 */
733
	public function __get($name)
734
	{
735
		if (method_exists($this, $getter = 'get' . $name)) {
736
			// getting a property
737
			return $this->$getter();
738
		} elseif (method_exists($this, $jsgetter = 'getjs' . $name)) {
739
			// getting a javascript property
740
			return (string) $this->$jsgetter();
741
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
742
			// getting an event (handler list)
743
			$name = strtolower($name);
744
			if (!isset($this->_e[$name])) {
745
				$this->_e[$name] = new TWeakCallableCollection();
746
			}
747
			return $this->_e[$name];
748
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
749
			// getting a global event (handler list)
750
			$name = strtolower($name);
751
			if (!isset(self::$_ue[$name])) {
752
				self::$_ue[$name] = new TWeakCallableCollection();
753
			}
754
			return self::$_ue[$name];
755
		} elseif ($this->_behaviorsenabled) {
756
			// getting a behavior property/event (handler list)
757
			if (isset($this->_m[$name])) {
758
				return $this->_m[$name];
759
			} elseif ($this->_m !== null) {
760
				foreach ($this->_m->toArray() as $behavior) {
761
					if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) &&
762
						(property_exists($behavior, $name) || $behavior->canGetProperty($name) || $behavior->hasEvent($name))) {
763
						return $behavior->$name;
764
					}
765
				}
766
			}
767
		}
768
		throw new TInvalidOperationException('component_property_undefined', get_class($this), $name);
769
	}
770
771
	/**
772
	 * Sets value of a component property.
773
	 * Do not call this method. This is a PHP magic method that we override
774
	 * to allow using the following syntax to set a property or attach an event handler.
775
	 * <code>
776
	 * $this->PropertyName=$value;
777
	 * $this->jsPropertyName=$value; // $value will be treated as a JavaScript literal
778
	 * $this->EventName=$handler;
779
	 * $this->fxEventName=$handler; //global event listener
780
	 * </code>
781
	 * When behaviors are enabled, this will also set a behaviors properties and events.
782
	 * @param string $name the property name or event name
783
	 * @param mixed $value the property value or event handler
784
	 * @throws TInvalidOperationException If the property is not defined or read-only.
785
	 */
786
	public function __set($name, $value)
787
	{
788
		if (method_exists($this, $setter = 'set' . $name)) {
789
			if (strncasecmp($name, 'js', 2) === 0 && $value && !($value instanceof TJavaScriptLiteral)) {
790
				$value = new TJavaScriptLiteral($value);
791
			}
792
			return $this->$setter($value);
793
		} elseif (method_exists($this, $jssetter = 'setjs' . $name)) {
794
			if ($value && !($value instanceof TJavaScriptString)) {
795
				$value = new TJavaScriptString($value);
796
			}
797
			return $this->$jssetter($value);
798
		} elseif ((strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) || strncasecmp($name, 'fx', 2) === 0) {
799
			return $this->attachEventHandler($name, $value);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->attachEventHandler($name, $value) targeting Prado\TComponent::attachEventHandler() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
800
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->_behaviorsenabled) {
801
			$sets = 0;
802
			foreach ($this->_m->toArray() as $behavior) {
803
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) &&
804
					(property_exists($behavior, $name) || $behavior->canSetProperty($name) || $behavior->hasEvent($name))) {
805
					$behavior->$name = $value;
806
					$sets++;
807
				}
808
			}
809
			if ($sets) {
810
				return $value;
811
			}
812
		}
813
814
		if (method_exists($this, 'get' . $name) || method_exists($this, 'getjs' . $name)) {
815
			throw new TInvalidOperationException('component_property_readonly', get_class($this), $name);
816
		} else {
817
			throw new TInvalidOperationException('component_property_undefined', get_class($this), $name);
818
		}
819
	}
820
821
	/**
822
	 * Checks if a property value is null, there are no events in the object
823
	 * event list or global event list registered under the name, and, if
824
	 * behaviors are enabled,
825
	 * Do not call this method. This is a PHP magic method that we override
826
	 * to allow using isset() to detect if a component property is set or not.
827
	 * This also works for global events.  When behaviors are enabled, it
828
	 * will check for a behavior of the specified name, and also check
829
	 * the behavior for events and properties.
830
	 * @param string $name the property name or the event name
831
	 * @since 3.2.3
832
	 */
833
	public function __isset($name)
834
	{
835
		if (method_exists($this, $getter = 'get' . $name)) {
836
			return $this->$getter() !== null;
837
		} elseif (method_exists($this, $jsgetter = 'getjs' . $name)) {
838
			return $this->$jsgetter() !== null;
839
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
840
			$name = strtolower($name);
841
			return isset($this->_e[$name]) && $this->_e[$name]->getCount();
842
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
843
			$name = strtolower($name);
844
			return isset(self::$_ue[$name]) && self::$_ue[$name]->getCount();
845
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->_behaviorsenabled) {
846
			if (isset($this->_m[$name])) {
847
				return true;
848
			}
849
			foreach ($this->_m->toArray() as $behavior) {
850
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && (property_exists($behavior, $name) || $behavior->canGetProperty($name) || $behavior->hasEvent($name))) {
851
					return isset($behavior->$name);
852
				}
853
			}
854
		}
855
		return false;
856
	}
857
858
	/**
859
	 * Sets a component property to be null.  Clears the object or global
860
	 * events. When enabled, loops through all behaviors and unsets the
861
	 * property or event.
862
	 * Do not call this method. This is a PHP magic method that we override
863
	 * to allow using unset() to set a component property to be null.
864
	 * @param string $name the property name or the event name
865
	 * @throws TInvalidOperationException if the property is read only.
866
	 * @since 3.2.3
867
	 */
868
	public function __unset($name)
869
	{
870
		if (method_exists($this, $setter = 'set' . $name)) {
871
			$this->$setter(null);
872
		} elseif (method_exists($this, $jssetter = 'setjs' . $name)) {
873
			$this->$jssetter(null);
874
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
875
			$this->_e[strtolower($name)]->clear();
876
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
877
			$this->getEventHandlers($name)->remove([$this, $name]);
878
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->_behaviorsenabled) {
879
			if (isset($this->_m[$name])) {
880
				$this->detachBehavior($name);
881
			} else {
882
				$unset = 0;
883
				foreach ($this->_m->toArray() as $behavior) {
884
					if ((!($behavior instanceof IBehavior) || $behavior->getEnabled())) {
885
						unset($behavior->$name);
886
						$unset++;
887
					}
888
				}
889
				if (!$unset && method_exists($this, 'get' . $name)) {
890
					throw new TInvalidOperationException('component_property_readonly', get_class($this), $name);
891
				}
892
			}
893
		} elseif (method_exists($this, 'get' . $name)) {
894
			throw new TInvalidOperationException('component_property_readonly', get_class($this), $name);
895
		}
896
	}
897
898
	/**
899
	 * Determines whether a property is defined.
900
	 * A property is defined if there is a getter or setter method
901
	 * defined in the class. Note, property names are case-insensitive.
902
	 * @param string $name the property name
903
	 * @return bool whether the property is defined
904
	 */
905
	public function hasProperty($name)
906
	{
907
		return $this->canGetProperty($name) || $this->canSetProperty($name);
908
	}
909
910
	/**
911
	 * Determines whether a property can be read.
912
	 * A property can be read if the class has a getter method
913
	 * for the property name. Note, property name is case-insensitive.
914
	 * This also checks for getjs.  When enabled, it loops through all
915
	 * active behaviors for the get property when undefined by the object.
916
	 * @param string $name the property name
917
	 * @return bool whether the property can be read
918
	 */
919
	public function canGetProperty($name)
920
	{
921
		if (method_exists($this, 'get' . $name) || method_exists($this, 'getjs' . $name)) {
922
			return true;
923
		} elseif ($this->_m !== null && $this->_behaviorsenabled) {
924
			foreach ($this->_m->toArray() as $behavior) {
925
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->canGetProperty($name)) {
926
					return true;
927
				}
928
			}
929
		}
930
		return false;
931
	}
932
933
	/**
934
	 * Determines whether a property can be set.
935
	 * A property can be written if the class has a setter method
936
	 * for the property name. Note, property name is case-insensitive.
937
	 * This also checks for setjs.  When enabled, it loops through all
938
	 * active behaviors for the set property when undefined by the object.
939
	 * @param string $name the property name
940
	 * @return bool whether the property can be written
941
	 */
942
	public function canSetProperty($name)
943
	{
944
		if (method_exists($this, 'set' . $name) || method_exists($this, 'setjs' . $name)) {
945
			return true;
946
		} elseif ($this->_m !== null && $this->_behaviorsenabled) {
947
			foreach ($this->_m->toArray() as $behavior) {
948
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->canSetProperty($name)) {
949
					return true;
950
				}
951
			}
952
		}
953
		return false;
954
	}
955
956
	/**
957
	 * Evaluates a property path.
958
	 * A property path is a sequence of property names concatenated by '.' character.
959
	 * For example, 'Parent.Page' refers to the 'Page' property of the component's
960
	 * 'Parent' property value (which should be a component also).
961
	 * When a property is not defined by an object, this also loops through all
962
	 * active behaviors of the object.
963
	 * @param string $path property path
964
	 * @return mixed the property path value
965
	 */
966
	public function getSubProperty($path)
967
	{
968
		$object = $this;
969
		foreach (explode('.', $path) as $property) {
970
			$object = $object->$property;
971
		}
972
		return $object;
973
	}
974
975
	/**
976
	 * Sets a value to a property path.
977
	 * A property path is a sequence of property names concatenated by '.' character.
978
	 * For example, 'Parent.Page' refers to the 'Page' property of the component's
979
	 * 'Parent' property value (which should be a component also).
980
	 * When a property is not defined by an object, this also loops through all
981
	 * active behaviors of the object.
982
	 * @param string $path property path
983
	 * @param mixed $value the property path value
984
	 */
985
	public function setSubProperty($path, $value)
986
	{
987
		$object = $this;
988
		if (($pos = strrpos($path, '.')) === false) {
989
			$property = $path;
990
		} else {
991
			$object = $this->getSubProperty(substr($path, 0, $pos));
992
			$property = substr($path, $pos + 1);
993
		}
994
		$object->$property = $value;
995
	}
996
997
	/**
998
	 * Calls a method on a component's behaviors.  When the method is a
999
	 * dynamic event, it is raised with all the behaviors.  When a class implements
1000
	 * a dynamic event (eg. for patching), the class can customize raising the
1001
	 * dynamic event with the classes behaviors using this method.
1002
	 * Dynamic [dy] and global [fx] events call {@link __dycall} when $this
1003
	 * implements IDynamicMethods.  Finally, this catches all unexecuted
1004
	 * Dynamic [dy] and global [fx] events and returns the first $args parameter;
1005
	 * acting as a passthrough (filter) of the first $args parameter. In dy/fx methods,
1006
	 * there can be no $args parameters, the first parameter used as a pass through
1007
	 * filter, or act as a return variable with the first $args parameter being
1008
	 * the default return value.
1009
	 * @param string $method The method being called or dynamic/global event being raised.
1010
	 * @param mixed &$return The return value.
1011
	 * @param array $args The arguments to the method being called.
1012
	 * @return bool Was the method handled.
1013
	 * @since 4.2.3
1014
	 */
1015
	public function callBehaviorsMethod($method, &$return, ...$args): bool
1016
	{
1017
		if ($this->_m !== null && $this->_behaviorsenabled) {
1018
			if (strncasecmp($method, 'dy', 2) === 0) {
1019
				$classArgs = $callchain = null;
1020
				foreach ($this->_m->toArray() as $behavior) {
1021
					if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && (method_exists($behavior, $method) || ($behavior instanceof IDynamicMethods))) {
1022
						if ($classArgs === null) {
1023
							$classArgs = $args;
1024
							array_unshift($classArgs, $this);
1025
						}
1026
						if (!$callchain) {
1027
							$callchain = new TCallChain($method);
1028
						}
1029
						$callchain->addCall([$behavior, $method], ($behavior instanceof IClassBehavior) ? $classArgs : $args);
1030
					}
1031
				}
1032
				if ($callchain) {
1033
					$return = $callchain->call(...$args);
1034
					return true;
1035
				}
1036
			} else {
1037
				foreach ($this->_m->toArray() as $behavior) {
1038
					if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && method_exists($behavior, $method)) {
1039
						if ($behavior instanceof IClassBehavior) {
1040
							array_unshift($args, $this);
1041
						}
1042
						$return = $behavior->$method(...$args);
1043
						return true;
1044
					}
1045
				}
1046
			}
1047
		}
1048
		if (strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1049
			if ($this instanceof IDynamicMethods) {
1050
				$return = $this->__dycall($method, $args);
0 ignored issues
show
Bug introduced by
The method __dycall() does not exist on Prado\TComponent. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1050
				/** @scrutinizer ignore-call */ 
1051
    $return = $this->__dycall($method, $args);
Loading history...
1051
				return true;
1052
			}
1053
			$return = $args[0] ?? null;
1054
			return true;
1055
		}
1056
		return false;
1057
	}
1058
1059
	/**
1060
	 * Determines whether a method is defined. When behaviors are enabled, this
1061
	 * will loop through all enabled behaviors checking for the method as well.
1062
	 * Nested behaviors within behaviors are not supported but the nested behavior can
1063
	 * affect the primary behavior like any behavior affects their owner.
1064
	 * Note, method name are case-insensitive.
1065
	 * @param string $name the method name
1066
	 * @return bool
1067
	 * @since 4.2.2
1068
	 */
1069
	public function hasMethod($name)
1070
	{
1071
		if (method_exists($this, $name) || strncasecmp($name, 'fx', 2) === 0 || strncasecmp($name, 'dy', 2) === 0) {
1072
			return true;
1073
		} elseif ($this->_m !== null && $this->_behaviorsenabled) {
1074
			foreach ($this->_m->toArray() as $behavior) {
1075
				//method_exists($behavior, $name) rather than $behavior->hasMethod($name) b/c only one layer is supported, @4.2.2
1076
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && method_exists($behavior, $name)) {
1077
					return true;
1078
				}
1079
			}
1080
		}
1081
		return false;
1082
	}
1083
1084
	/**
1085
	 * Determines whether an event is defined.
1086
	 * An event is defined if the class has a method whose name is the event name
1087
	 * prefixed with 'on', 'fx', or 'dy'.
1088
	 * Every object responds to every 'fx' and 'dy' event as they are in a universally
1089
	 * accepted event space.  'on' event must be declared by the object.
1090
	 * When enabled, this will loop through all active behaviors for 'on' events
1091
	 * defined by the behavior.
1092
	 * Note, event name is case-insensitive.
1093
	 * @param string $name the event name
1094
	 * @return bool
1095
	 */
1096
	public function hasEvent($name)
1097
	{
1098
		if ((strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) || strncasecmp($name, 'fx', 2) === 0 || strncasecmp($name, 'dy', 2) === 0) {
1099
			return true;
1100
		} elseif ($this->_m !== null && $this->_behaviorsenabled) {
1101
			foreach ($this->_m->toArray() as $behavior) {
1102
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->hasEvent($name)) {
1103
					return true;
1104
				}
1105
			}
1106
		}
1107
		return false;
1108
	}
1109
1110
	/**
1111
	 * Checks if an event has any handlers.  This function also checks through all
1112
	 * the behaviors for 'on' events when behaviors are enabled.
1113
	 * 'dy' dynamic events are not handled by this function.
1114
	 * @param string $name the event name
1115
	 * @return bool whether an event has been attached one or several handlers
1116
	 */
1117
	public function hasEventHandler($name)
1118
	{
1119
		$name = strtolower($name);
1120
		if (strncasecmp($name, 'fx', 2) === 0) {
1121
			return isset(self::$_ue[$name]) && self::$_ue[$name]->getCount() > 0;
1122
		} else {
1123
			if (isset($this->_e[$name]) && $this->_e[$name]->getCount() > 0) {
1124
				return true;
1125
			} elseif ($this->_m !== null && $this->_behaviorsenabled) {
1126
				foreach ($this->_m->toArray() as $behavior) {
1127
					if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->hasEventHandler($name)) {
1128
						return true;
1129
					}
1130
				}
1131
			}
1132
		}
1133
		return false;
1134
	}
1135
1136
	/**
1137
	 * Returns the list of attached event handlers for an 'on' or 'fx' event.   This function also
1138
	 * checks through all the behaviors for 'on' event lists when behaviors are enabled.
1139
	 * @param mixed $name
1140
	 * @throws TInvalidOperationException if the event is not defined
1141
	 * @return TWeakCallableCollection list of attached event handlers for an event
1142
	 */
1143
	public function getEventHandlers($name)
1144
	{
1145
		if (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
1146
			$name = strtolower($name);
1147
			if (!isset($this->_e[$name])) {
1148
				$this->_e[$name] = new TWeakCallableCollection();
1149
			}
1150
			return $this->_e[$name];
1151
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
1152
			$name = strtolower($name);
1153
			if (!isset(self::$_ue[$name])) {
1154
				self::$_ue[$name] = new TWeakCallableCollection();
1155
			}
1156
			return self::$_ue[$name];
1157
		} elseif ($this->_m !== null && $this->_behaviorsenabled) {
1158
			foreach ($this->_m->toArray() as $behavior) {
1159
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->hasEvent($name)) {
1160
					return $behavior->getEventHandlers($name);
1161
				}
1162
			}
1163
		}
1164
		throw new TInvalidOperationException('component_event_undefined', get_class($this), $name);
1165
	}
1166
1167
	/**
1168
	 * Attaches an event handler to an event.
1169
	 *
1170
	 * The handler must be a valid PHP callback, i.e., a string referring to
1171
	 * a global function name, or an array containing two elements with
1172
	 * the first element being an object and the second element a method name
1173
	 * of the object. In Prado, you can also use method path to refer to
1174
	 * an event handler. For example, array($object,'Parent.buttonClicked')
1175
	 * uses a method path that refers to the method $object->Parent->buttonClicked(...).
1176
	 *
1177
	 * The event handler must be of the following signature,
1178
	 * <code>
1179
	 * function handlerName($sender, $param) {}
1180
	 * function handlerName($sender, $param, $name) {}
1181
	 * </code>
1182
	 * where $sender represents the object that raises the event,
1183
	 * and $param is the event parameter. $name refers to the event name
1184
	 * being handled.
1185
	 *
1186
	 * This is a convenient method to add an event handler.
1187
	 * It is equivalent to {@link getEventHandlers}($name)->add($handler).
1188
	 * For complete management of event handlers, use {@link getEventHandlers}
1189
	 * to get the event handler list first, and then do various
1190
	 * {@link TWeakCallableCollection} operations to append, insert or remove
1191
	 * event handlers. You may also do these operations like
1192
	 * getting and setting properties, e.g.,
1193
	 * <code>
1194
	 * $component->OnClick[]=array($object,'buttonClicked');
1195
	 * $component->OnClick->insertAt(0,array($object,'buttonClicked'));
1196
	 * </code>
1197
	 * which are equivalent to the following
1198
	 * <code>
1199
	 * $component->getEventHandlers('OnClick')->add(array($object,'buttonClicked'));
1200
	 * $component->getEventHandlers('OnClick')->insertAt(0,array($object,'buttonClicked'));
1201
	 * </code>
1202
	 *
1203
	 * Due to the nature of {@link getEventHandlers}, any active behaviors defining
1204
	 * new 'on' events, this method will pass through to the behavior transparently.
1205
	 *
1206
	 * @param string $name the event name
1207
	 * @param callable $handler the event handler
1208
	 * @param null|numeric $priority the priority of the handler, defaults to null which translates into the
1209
	 * default priority of 10.0 within {@link TWeakCallableCollection}
1210
	 * @throws TInvalidOperationException if the event does not exist
1211
	 */
1212
	public function attachEventHandler($name, $handler, $priority = null)
1213
	{
1214
		$this->getEventHandlers($name)->add($handler, $priority);
1215
	}
1216
1217
	/**
1218
	 * Detaches an existing event handler.
1219
	 * This method is the opposite of {@link attachEventHandler}.  It will detach
1220
	 * any 'on' events defined by an objects active behaviors as well.
1221
	 * @param string $name event name
1222
	 * @param callable $handler the event handler to be removed
1223
	 * @param null|false|numeric $priority the priority of the handler, defaults to false which translates
1224
	 * to an item of any priority within {@link TWeakCallableCollection}; null means the default priority
1225
	 * @return bool if the removal is successful
1226
	 */
1227
	public function detachEventHandler($name, $handler, $priority = false)
1228
	{
1229
		if ($this->hasEventHandler($name)) {
1230
			try {
1231
				$this->getEventHandlers($name)->remove($handler, $priority);
0 ignored issues
show
Bug introduced by
It seems like $priority can also be of type Prado\numeric; however, parameter $priority of Prado\Collections\TPriorityList::remove() does only seem to accept boolean|double|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1231
				$this->getEventHandlers($name)->remove($handler, /** @scrutinizer ignore-type */ $priority);
Loading history...
1232
				return true;
1233
			} catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1234
			}
1235
		}
1236
		return false;
1237
	}
1238
1239
	/**
1240
	 * Raises an event.  This raises both inter-object 'on' events and global 'fx' events.
1241
	 * This method represents the happening of an event and will
1242
	 * invoke all attached event handlers for the event in {@link TWeakCallableCollection} order.
1243
	 * This method does not handle intra-object/behavior dynamic 'dy' events.
1244
	 *
1245
	 * There are ways to handle event responses.  By default {@link EVENT_RESULT_FILTER},
1246
	 * all event responses are stored in an array, filtered for null responses, and returned.
1247
	 * If {@link EVENT_RESULT_ALL} is specified, all returned results will be stored along
1248
	 * with the sender and param in an array
1249
	 * <code>
1250
	 * 		$result[] = array('sender'=>$sender,'param'=>$param,'response'=>$response);
1251
	 * </code>
1252
	 *
1253
	 * If {@link EVENT_RESULT_FEED_FORWARD} is specified, then each handler result is then
1254
	 * fed forward as the parameters for the next event.  This allows for events to filter data
1255
	 * directly by affecting the event parameters
1256
	 *
1257
	 * If a callable function is set in the response type or the post function filter is specified then the
1258
	 * result of each called event handler is post processed by the callable function.  Used in
1259
	 * combination with {@link EVENT_RESULT_FEED_FORWARD}, any event (and its result) can be chained.
1260
	 *
1261
	 * When raising a global 'fx' event, registered handlers in the global event list for
1262
	 * {@link GLOBAL_RAISE_EVENT_LISTENER} are always added into the set of event handlers.  In this way,
1263
	 * these global events are always raised for every global 'fx' event.  The registered handlers for global
1264
	 * raiseEvent events have priorities.  Any registered global raiseEvent event handlers with a priority less than zero
1265
	 * are added before the main event handlers being raised and any registered global raiseEvent event handlers
1266
	 * with a priority equal or greater than zero are added after the main event handlers being raised.  In this way
1267
	 * all {@link GLOBAL_RAISE_EVENT_LISTENER} handlers are always called for every raised 'fx' event.
1268
	 *
1269
	 * Behaviors may implement the following functions:
1270
	 * <code>
1271
	 *	public function dyPreRaiseEvent($name,$sender,$param,$responsetype,$postfunction[, $chain]) {
1272
	 *  	return $name; //eg, the event name may be filtered/changed
1273
	 *  }
1274
	 *	public function dyIntraRaiseEventTestHandler($handler,$sender,$param,$name[, $chain]) {
1275
	 *  	return true; //should this particular handler be executed?  true/false
1276
	 *  }
1277
	 *  public function dyIntraRaiseEventPostHandler($name,$sender,$param,$handler,$response[, $chain]) {
1278
	 *		//contains the per handler response
1279
	 *  }
1280
	 *  public function dyPostRaiseEvent($responses,$name,$sender,$param,$responsetype,$postfunction[, $chain]) {
1281
	 *		return $responses;
1282
	 *  }
1283
	 * </code>
1284
	 * to be executed when raiseEvent is called.  The 'intra' dynamic events are called per handler in
1285
	 * the handler loop.
1286
	 *
1287
	 * dyPreRaiseEvent has the effect of being able to change the event being raised.  This intra
1288
	 * object/behavior event returns the name of the desired event to be raised.  It will pass through
1289
	 * if no dynamic event is specified, or if the original event name is returned.
1290
	 * dyIntraRaiseEventTestHandler returns true or false as to whether a specific handler should be
1291
	 * called for a specific raised event (and associated event arguments)
1292
	 * dyIntraRaiseEventPostHandler does not return anything.  This allows behaviors to access the results
1293
	 * of an event handler in the per handler loop.
1294
	 * dyPostRaiseEvent returns the responses.  This allows for any post processing of the event
1295
	 * results from the sum of all event handlers
1296
	 *
1297
	 * When handling a catch-all {@link __dycall}, the method name is the name of the event
1298
	 * and the parameters are the sender, the param, and then the name of the event.
1299
	 *
1300
	 * @param string $name the event name
1301
	 * @param mixed $sender the event sender object
1302
	 * @param \Prado\TEventParameter $param the event parameter
1303
	 * @param null|numeric $responsetype how the results of the event are tabulated.  default: {@link EVENT_RESULT_FILTER}  The default filters out
1304
	 *		null responses. optional
1305
	 * @param null|callable $postfunction any per handler filtering of the response result needed is passed through
1306
	 *		this if not null. default: null.  optional
1307
	 * @throws TInvalidOperationException if the event is undefined
1308
	 * @throws TInvalidDataValueException If an event handler is invalid
1309
	 * @return mixed the results of the event
1310
	 */
1311
	public function raiseEvent($name, $sender, $param, $responsetype = null, $postfunction = null)
1312
	{
1313
		$p = $param;
0 ignored issues
show
Unused Code introduced by
The assignment to $p is dead and can be removed.
Loading history...
1314
		if (is_callable($responsetype)) {
1315
			$postfunction = $responsetype;
1316
			$responsetype = null;
1317
		}
1318
1319
		if ($responsetype === null) {
1320
			$responsetype = TEventResults::EVENT_RESULT_FILTER;
1321
		}
1322
1323
		$name = strtolower($name);
1324
		$responses = [];
1325
1326
		$name = $this->dyPreRaiseEvent($name, $sender, $param, $responsetype, $postfunction);
0 ignored issues
show
Bug introduced by
It seems like $postfunction can also be of type callable; however, parameter $postfunction of Prado\TComponent::dyPreRaiseEvent() does only seem to accept Prado\function|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1326
		$name = $this->dyPreRaiseEvent($name, $sender, $param, $responsetype, /** @scrutinizer ignore-type */ $postfunction);
Loading history...
Bug introduced by
It seems like $responsetype can also be of type integer; however, parameter $responsetype of Prado\TComponent::dyPreRaiseEvent() does only seem to accept Prado\numeric|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1326
		$name = $this->dyPreRaiseEvent($name, $sender, $param, /** @scrutinizer ignore-type */ $responsetype, $postfunction);
Loading history...
1327
1328
		if ($this->hasEventHandler($name) || $this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER)) {
1329
			$handlers = $this->getEventHandlers($name);
1330
			$handlerArray = $handlers->toArray();
1331
			if (strncasecmp($name, 'fx', 2) === 0 && $this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER)) {
1332
				$globalhandlers = $this->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER);
1333
				$handlerArray = array_merge($globalhandlers->toArrayBelowPriority(0), $handlerArray, $globalhandlers->toArrayAbovePriority(0));
0 ignored issues
show
Bug introduced by
0 of type integer is incompatible with the type Prado\Collections\numeric expected by parameter $priority of Prado\Collections\TWeakC...:toArrayBelowPriority(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1333
				$handlerArray = array_merge($globalhandlers->toArrayBelowPriority(/** @scrutinizer ignore-type */ 0), $handlerArray, $globalhandlers->toArrayAbovePriority(0));
Loading history...
Bug introduced by
0 of type integer is incompatible with the type Prado\Collections\numeric expected by parameter $priority of Prado\Collections\TWeakC...:toArrayAbovePriority(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1333
				$handlerArray = array_merge($globalhandlers->toArrayBelowPriority(0), $handlerArray, $globalhandlers->toArrayAbovePriority(/** @scrutinizer ignore-type */ 0));
Loading history...
1334
			}
1335
			$response = null;
1336
			foreach ($handlerArray as $handler) {
1337
				if ($this->dyIntraRaiseEventTestHandler($handler, $sender, $param, $name) === false) {
1338
					continue;
1339
				}
1340
1341
				if (is_string($handler)) {
1342
					if (($pos = strrpos($handler, '.')) !== false) {
1343
						$object = $this->getSubProperty(substr($handler, 0, $pos));
1344
						$method = substr($handler, $pos + 1);
1345
						if (method_exists($object, $method) || strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1346
							if ($method == '__dycall') {
1347
								$response = $object->__dycall($name, [$sender, $param, $name]);
1348
							} else {
1349
								$response = $object->$method($sender, $param, $name);
1350
							}
1351
						} else {
1352
							throw new TInvalidDataValueException('component_eventhandler_invalid', get_class($this), $name, $handler);
1353
						}
1354
					} else {
1355
						$response = call_user_func($handler, $sender, $param, $name);
1356
					}
1357
				} elseif (is_callable($handler, true)) {
1358
					[$object, $method] = $handler;
1359
					if (is_string($object)) {
1360
						$response = call_user_func($handler, $sender, $param, $name);
1361
					} else {
1362
						if (($pos = strrpos($method, '.')) !== false) {
1363
							$object = $object->getSubProperty(substr($method, 0, $pos));
1364
							$method = substr($method, $pos + 1);
1365
						}
1366
						if (method_exists($object, $method) || strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1367
							if ($method == '__dycall') {
1368
								$response = $object->__dycall($name, [$sender, $param, $name]);
1369
							} else {
1370
								$response = $object->$method($sender, $param, $name);
1371
							}
1372
						} else {
1373
							throw new TInvalidDataValueException('component_eventhandler_invalid', get_class($this), $name, $handler[1]);
1374
						}
1375
					}
1376
				} else {
1377
					throw new TInvalidDataValueException('component_eventhandler_invalid', get_class($this), $name, gettype($handler));
1378
				}
1379
1380
				$this->dyIntraRaiseEventPostHandler($name, $sender, $param, $handler, $response);
1381
1382
				if ($postfunction) {
1383
					$response = call_user_func_array($postfunction, [$sender, $param, $this, $response]);
1384
				}
1385
1386
				if ($responsetype & TEventResults::EVENT_RESULT_ALL) {
1387
					$responses[] = ['sender' => $sender, 'param' => $param, 'response' => $response];
1388
				} else {
1389
					$responses[] = $response;
1390
				}
1391
1392
				if ($response !== null && ($responsetype & TEventResults::EVENT_RESULT_FEED_FORWARD)) {
1393
					$param = $response;
1394
				}
1395
			}
1396
		} elseif (strncasecmp($name, 'on', 2) === 0 && !$this->hasEvent($name)) {
1397
			throw new TInvalidOperationException('component_event_undefined', get_class($this), $name);
1398
		}
1399
1400
		if ($responsetype & TEventResults::EVENT_RESULT_FILTER) {
1401
			$responses = array_filter($responses);
1402
		}
1403
1404
		$responses = $this->dyPostRaiseEvent($responses, $name, $sender, $param, $responsetype, $postfunction);
0 ignored issues
show
Bug introduced by
It seems like $postfunction can also be of type callable; however, parameter $postfunction of Prado\TComponent::dyPostRaiseEvent() does only seem to accept Prado\function|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1404
		$responses = $this->dyPostRaiseEvent($responses, $name, $sender, $param, $responsetype, /** @scrutinizer ignore-type */ $postfunction);
Loading history...
Bug introduced by
It seems like $responsetype can also be of type integer; however, parameter $responsetype of Prado\TComponent::dyPostRaiseEvent() does only seem to accept Prado\numeric|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1404
		$responses = $this->dyPostRaiseEvent($responses, $name, $sender, $param, /** @scrutinizer ignore-type */ $responsetype, $postfunction);
Loading history...
1405
1406
		return $responses;
1407
	}
1408
1409
	/**
1410
	 * Evaluates a PHP expression in the context of this control.
1411
	 *
1412
	 * Behaviors may implement the function:
1413
	 * <code>
1414
	 *	public function dyEvaluateExpressionFilter($expression, $chain) {
1415
	 * 		return $chain->dyEvaluateExpressionFilter(str_replace('foo', 'bar', $expression)); //example
1416
	 * }
1417
	 * </code>
1418
	 * to be executed when evaluateExpression is called.  All attached behaviors are notified through
1419
	 * dyEvaluateExpressionFilter.  The chaining is important in this function due to the filtering
1420
	 * pass-through effect.
1421
	 *
1422
	 * @param string $expression PHP expression
1423
	 * @throws TInvalidOperationException if the expression is invalid
1424
	 * @return mixed the expression result
1425
	 */
1426
	public function evaluateExpression($expression)
1427
	{
1428
		$expression = $this->dyEvaluateExpressionFilter($expression);
1429
		try {
1430
			return eval("return $expression;");
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1431
		} catch (\Exception $e) {
0 ignored issues
show
Unused Code introduced by
catch (\Exception $e) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1432
			throw new TInvalidOperationException('component_expression_invalid', get_class($this), $expression, $e->getMessage());
1433
		}
1434
	}
1435
1436
	/**
1437
	 * Evaluates a list of PHP statements.
1438
	 *
1439
	 * Behaviors may implement the function:
1440
	 * <code>
1441
	 *	public function dyEvaluateStatementsFilter($statements, $chain) {
1442
	 * 		return $chain->dyEvaluateStatementsFilter(str_replace('foo', 'bar', $statements)); //example
1443
	 * }
1444
	 * </code>
1445
	 * to be executed when evaluateStatements is called.  All attached behaviors are notified through
1446
	 * dyEvaluateStatementsFilter.  The chaining is important in this function due to the filtering
1447
	 * pass-through effect.
1448
	 *
1449
	 * @param string $statements PHP statements
1450
	 * @throws TInvalidOperationException if the statements are invalid
1451
	 * @return string content echoed or printed by the PHP statements
1452
	 */
1453
	public function evaluateStatements($statements)
1454
	{
1455
		$statements = $this->dyEvaluateStatementsFilter($statements);
1456
		try {
1457
			ob_start();
1458
			if (eval($statements) === false) {
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1459
				throw new \Exception('');
1460
			}
1461
			$content = ob_get_contents();
1462
			ob_end_clean();
1463
			return $content;
1464
		} catch (\Exception $e) {
1465
			throw new TInvalidOperationException('component_statements_invalid', get_class($this), $statements, $e->getMessage());
1466
		}
1467
	}
1468
1469
	/**
1470
	 * This method is invoked after the component is instantiated by a template.
1471
	 * When this method is invoked, the component's properties have been initialized.
1472
	 * The default implementation of this method will invoke
1473
	 * the potential parent component's {@link addParsedObject}.
1474
	 * This method can be overridden.
1475
	 *
1476
	 * Behaviors may implement the function:
1477
	 * <code>
1478
	 *	public function dyCreatedOnTemplate($parent, $chain) {
1479
	 * 		return $chain->dyCreatedOnTemplate($parent); //example
1480
	 *  }
1481
	 * </code>
1482
	 * to be executed when createdOnTemplate is called.  All attached behaviors are notified through
1483
	 * dyCreatedOnTemplate.
1484
	 *
1485
	 * @param \Prado\TComponent $parent potential parent of this control
1486
	 * @see addParsedObject
1487
	 */
1488
	public function createdOnTemplate($parent)
1489
	{
1490
		$parent = $this->dyCreatedOnTemplate($parent);
1491
		$parent->addParsedObject($this);
1492
	}
1493
1494
	/**
1495
	 * Processes an object that is created during parsing template.
1496
	 * The object can be either a component or a static text string.
1497
	 * This method can be overridden to customize the handling of newly created objects in template.
1498
	 * Only framework developers and control developers should use this method.
1499
	 *
1500
	 * Behaviors may implement the function:
1501
	 * <code>
1502
	 *	public function dyAddParsedObject($object[, $chain]) {
1503
	 *  }
1504
	 * </code>
1505
	 * to be executed when addParsedObject is called.  All attached behaviors are notified through
1506
	 * dyAddParsedObject.
1507
	 *
1508
	 * @param \Prado\TComponent|string $object text string or component parsed and instantiated in template
1509
	 * @see createdOnTemplate
1510
	 */
1511
	public function addParsedObject($object)
1512
	{
1513
		$this->dyAddParsedObject($object);
1514
	}
1515
1516
1517
	/**
1518
	 *This is the method registered for all instanced objects should a class behavior be added after
1519
	 * the class is instanced.  Only when the class to which the behavior is being added is in this
1520
	 * object's class hierarchy, via {@link getClassHierarchy}, is the behavior added to this instance.
1521
	 * @param mixed $sender the application
1522
	 * @param TClassBehaviorEventParameter $param
1523
	 * @since 3.2.3
1524
	 */
1525
	public function fxAttachClassBehavior($sender, $param)
1526
	{
1527
		if ($this->isa($param->getClass())) {
1528
			return $this->attachBehavior($param->getName(), $param->getBehavior(), $param->getPriority());
1529
		}
1530
	}
1531
1532
1533
	/**
1534
	 *	This is the method registered for all instanced objects should a class behavior be removed after
1535
	 * the class is instanced.  Only when the class to which the behavior is being added is in this
1536
	 * object's class hierarchy, via {@link getClassHierarchy}, is the behavior removed from this instance.
1537
	 * @param mixed $sender the application
1538
	 * @param TClassBehaviorEventParameter $param
1539
	 * @since 3.2.3
1540
	 */
1541
	public function fxDetachClassBehavior($sender, $param)
1542
	{
1543
		if ($this->isa($param->getClass())) {
1544
			return $this->detachBehavior($param->getName(), $param->getPriority());
1545
		}
1546
	}
1547
1548
	/**
1549
	 * instanceBehavior is an internal method that takes a Behavior Object, a class name, or array of
1550
	 * ['class' => 'MyBehavior', 'property1' => 'Value1'...] and creates a Behavior in return. eg.
1551
	 * <code>
1552
	 *		$b = $this->instanceBehavior('MyBehavior');
1553
	 * 		$b = $this->instanceBehavior(['class' => 'MyBehavior', 'property1' => 'Value1']);
1554
	 * 		$b = $this->instanceBehavior(new MyBehavior);
1555
	 * </code>
1556
	 * If the behavior is an array, the key IBaseBehavior::CONFIG_KEY is stripped and used to initialize
1557
	 * the behavior.
1558
	 *
1559
	 * @param array|IBaseBehavior|string $behavior string, Behavior, or array of ['class' => 'MyBehavior', 'property1' => 'Value1' ...].
1560
	 * @throws TInvalidDataTypeException if the behavior is not an {@link IBaseBehavior}
1561
	 * @return IBaseBehavior&TComponent an instance of $behavior or $behavior itself
1562
	 * @since 4.2.0
1563
	 */
1564
	protected static function instanceBehavior($behavior)
1565
	{
1566
		$hasConfig = false;
1567
		$config = null;
1568
		$isArray = false;
1569
		if (is_string($behavior) || (($isArray = is_array($behavior)) && isset($behavior['class']))) {
1570
			if ($isArray && array_key_exists(IBaseBehavior::CONFIG_KEY, $behavior)) {
0 ignored issues
show
Bug introduced by
It seems like $behavior can also be of type string; however, parameter $array of array_key_exists() does only seem to accept ArrayObject|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1570
			if ($isArray && array_key_exists(IBaseBehavior::CONFIG_KEY, /** @scrutinizer ignore-type */ $behavior)) {
Loading history...
1571
				$config = $behavior[IBaseBehavior::CONFIG_KEY];
1572
				unset($behavior[IBaseBehavior::CONFIG_KEY]);
1573
				$hasConfig = true;
1574
			}
1575
			$behavior = Prado::createComponent($behavior);
1576
		}
1577
		if (!($behavior instanceof IBaseBehavior)) {
1578
			throw new TInvalidDataTypeException('component_not_a_behavior', get_class($behavior));
1579
		}
1580
		if ($hasConfig) {
1581
			$behavior->init($config);
1582
		}
1583
		return $behavior;
1584
	}
1585
1586
1587
	/**
1588
	 *	This will add a class behavior to all classes instanced (that are listening) and future newly instanced objects.
1589
	 * This registers the behavior for future instances and pushes the changes to all the instances that are listening as well.
1590
	 * The universal class behaviors are stored in an inverted stack with the latest class behavior being at the first position in the array.
1591
	 * This is done so class behaviors are added last first.
1592
	 * @param string $name name the key of the class behavior
1593
	 * @param object|string $behavior class behavior or name of the object behavior per instance
1594
	 * @param null|array|IBaseBehavior|string $class string of class or class on which to attach this behavior.  Defaults to null which will error
1595
	 *	but more important, if this is on PHP 5.3 it will use Late Static Binding to derive the class
1596
	 * it should extend.
1597
	 * <code>
1598
	 * TPanel::attachClassBehavior('javascripts', new TJsPanelClassBehavior());
1599
	 * TApplication::attachClassBehavior('jpegize', \Prado\Util\Behaviors\TJPEGizeAssetBehavior::class, \Prado\Web\TFileAsset::class);
1600
	 * </code>
1601
	 * An array is used to initialize values of the behavior. eg. ['class' => '\\MyBehavior', 'property' => 'value'].
1602
	 * @param null|numeric $priority priority of behavior, default: null the default
1603
	 *  priority of the {@link TWeakCallableCollection}  Optional.
1604
	 * @throws TInvalidOperationException if the class behavior is being added to a
1605
	 *  {@link TComponent}; due to recursion.
1606
	 * @throws TInvalidOperationException if the class behavior is already defined
1607
	 * @return array|object the behavior if its an IClassBehavior and an array of all
1608
	 * behaviors that have been attached from 'fxAttachClassBehavior' when the Class
1609
	 * Behavior being attached is a per instance IBehavior.
1610
	 * @since 3.2.3
1611
	 */
1612
	public static function attachClassBehavior($name, $behavior, $class = null, $priority = null)
1613
	{
1614
		if (!$class) {
1615
			$class = get_called_class();
1616
		}
1617
		if (!$class) {
1618
			throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
1619
		}
1620
1621
		if (!is_string($name)) {
0 ignored issues
show
introduced by
The condition is_string($name) is always true.
Loading history...
1622
			$name = get_class($name);
1623
		}
1624
		$class = strtolower($class);
1625
		if ($class === 'prado\\tcomponent') {
1626
			throw new TInvalidOperationException('component_no_tcomponent_class_behaviors');
1627
		}
1628
		if (is_object($behavior) && $behavior instanceof IBehavior) {
1629
			throw new TInvalidOperationException('component_tbehavior_cannot_attach_as_class_behavior');
1630
		}
1631
		if (empty(self::$_um[$class])) {
1632
			self::$_um[$class] = [];
1633
		}
1634
		if (isset(self::$_um[$class][$name])) {
1635
			throw new TInvalidOperationException('component_class_behavior_defined', $class, $name);
1636
		}
1637
		$behaviorObject = self::instanceBehavior($behavior);
1638
		$isClassBehavior = $behaviorObject instanceof \Prado\Util\IClassBehavior;
1639
		$param = new TClassBehaviorEventParameter($class, $name, $isClassBehavior ? $behaviorObject : $behavior, $priority);
0 ignored issues
show
Bug introduced by
It seems like $isClassBehavior ? $behaviorObject : $behavior can also be of type string; however, parameter $behavior of Prado\Util\TClassBehavio...arameter::__construct() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1639
		$param = new TClassBehaviorEventParameter($class, $name, /** @scrutinizer ignore-type */ $isClassBehavior ? $behaviorObject : $behavior, $priority);
Loading history...
1640
		self::$_um[$class] = [$name => $param] + self::$_um[$class];
1641
		$results = $behaviorObject->raiseEvent('fxAttachClassBehavior', null, $param);
0 ignored issues
show
Bug introduced by
The method raiseEvent() does not exist on Prado\Util\IBaseBehavior. It seems like you code against a sub-type of Prado\Util\IBaseBehavior such as Prado\Util\TBehavior or Prado\Util\TClassBehavior. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1641
		/** @scrutinizer ignore-call */ 
1642
  $results = $behaviorObject->raiseEvent('fxAttachClassBehavior', null, $param);
Loading history...
1642
		return $isClassBehavior ? $behaviorObject : $results;
1643
	}
1644
1645
1646
	/**
1647
	 *	This will remove a behavior from a class.  It unregisters it from future instances and
1648
	 * pulls the changes from all the instances that are listening as well.
1649
	 * PHP 5.3 uses Late Static Binding to derive the static class upon which this method is called.
1650
	 * @param string $name the key of the class behavior
1651
	 * @param string $class class on which to attach this behavior.  Defaults to null.
1652
	 * @param null|false|numeric $priority priority: false is any priority, null is default
1653
	 *		{@link TWeakCallableCollection} priority, and numeric is a specific priority.
1654
	 * @throws TInvalidOperationException if the the class cannot be derived from Late Static Binding and is not
1655
	 * not supplied as a parameter.
1656
	 * @return null|array|object the behavior if its an IClassBehavior and an array of all behaviors
1657
	 * that have been detached from 'fxDetachClassBehavior' when the Class Behavior being
1658
	 * attached is a per instance IBehavior.  Null if no behavior of $name to detach.
1659
	 * @since 3.2.3
1660
	 */
1661
	public static function detachClassBehavior($name, $class = null, $priority = false)
1662
	{
1663
		if (!$class) {
1664
			$class = get_called_class();
1665
		}
1666
		if (!$class) {
1667
			throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
1668
		}
1669
1670
		$class = strtolower($class);
1671
		if (!is_string($name)) {
0 ignored issues
show
introduced by
The condition is_string($name) is always true.
Loading history...
1672
			$name = get_class($name);
1673
		}
1674
		if (empty(self::$_um[$class]) || !isset(self::$_um[$class][$name])) {
1675
			return null;
1676
		}
1677
		$param = self::$_um[$class][$name];
1678
		$behavior = $param->getBehavior();
1679
		$behaviorObject = self::instanceBehavior($behavior);
1680
		$isClassBehavior = $behaviorObject instanceof \Prado\Util\IClassBehavior;
1681
		unset(self::$_um[$class][$name]);
1682
		$results = $behaviorObject->raiseEvent('fxDetachClassBehavior', null, $param);
1683
		return $isClassBehavior ? $behaviorObject : $results;
1684
	}
1685
1686
	/**
1687
	 * Returns the named behavior object.
1688
	 * The name 'asa' stands for 'as a'.
1689
	 * @param string $behaviorname the behavior name
1690
	 * @return object the behavior object, or null if the behavior does not exist
1691
	 * @since 3.2.3
1692
	 */
1693
	public function asa($behaviorname)
1694
	{
1695
		return $this->_m[$behaviorname] ?? null;
1696
	}
1697
1698
	/**
1699
	 * Returns whether or not the object or any of the behaviors are of a particular class.
1700
	 * The name 'isa' stands for 'is a'.  This first checks if $this is an instanceof the class.
1701
	 * It then checks each Behavior.  If a behavior implements {@link IInstanceCheck},
1702
	 * then the behavior can determine what it is an instanceof.  If this behavior function returns true,
1703
	 * then this method returns true.  If the behavior instance checking function returns false,
1704
	 * then no further checking is performed as it is assumed to be correct.
1705
	 *
1706
	 * If the behavior instance check function returns nothing or null or the behavior
1707
	 * doesn't implement the {@link IInstanceCheck} interface, then the default instanceof occurs.
1708
	 * The default isa behavior is to check if the behavior is an instanceof the class.
1709
	 *
1710
	 * The behavior {@link IInstanceCheck} is to allow a behavior to have the host object
1711
	 * act as a completely different object.
1712
	 *
1713
	 * @param mixed|string $class class or string
1714
	 * @return bool whether or not the object or a behavior is an instance of a particular class
1715
	 * @since 3.2.3
1716
	 */
1717
	public function isa($class)
1718
	{
1719
		if ($this instanceof $class) {
1720
			return true;
1721
		}
1722
		if ($this->_m !== null && $this->_behaviorsenabled) {
1723
			foreach ($this->_m->toArray() as $behavior) {
1724
				if (($behavior instanceof IBehavior) && !$behavior->getEnabled()) {
1725
					continue;
1726
				}
1727
1728
				$check = null;
1729
				if (($behavior->isa(\Prado\Util\IInstanceCheck::class)) && $check = $behavior->isinstanceof($class, $this)) {
1730
					return true;
1731
				}
1732
				if ($check === null && ($behavior->isa($class))) {
1733
					return true;
1734
				}
1735
			}
1736
		}
1737
		return false;
1738
	}
1739
1740
	/**
1741
	 * Returns all the behaviors attached to the TComponent.  IBehavior[s] may
1742
	 * be attached but not {@link \Prado\Util\IBehavior::getEnabled Enabled}.
1743
	 * @return array all the behaviors attached to the TComponent
1744
	 * @since 4.2.2
1745
	 */
1746
	public function getBehaviors()
1747
	{
1748
		return isset($this->_m) ? $this->_m->toArray() : [];
1749
	}
1750
1751
	/**
1752
	 * Attaches a list of behaviors to the component.
1753
	 * Each behavior is indexed by its name and should be an instance of
1754
	 * {@link IBehavior}, a string specifying the behavior class, or a
1755
	 * {@link TClassBehaviorEventParameter}.
1756
	 * @param array $behaviors list of behaviors to be attached to the component
1757
	 * @since 3.2.3
1758
	 */
1759
	public function attachBehaviors($behaviors)
1760
	{
1761
		foreach ($behaviors as $name => $behavior) {
1762
			if ($behavior instanceof TClassBehaviorEventParameter) {
1763
				$this->attachBehavior($behavior->getName(), $behavior->getBehavior(), $behavior->getPriority());
1764
			} else {
1765
				$this->attachBehavior($name, $behavior);
1766
			}
1767
		}
1768
	}
1769
1770
	/**
1771
	 * Detaches select behaviors from the component.
1772
	 * Each behavior is indexed by its name and should be an instance of
1773
	 * {@link IBehavior}, a string specifying the behavior class, or a
1774
	 * {@link TClassBehaviorEventParameter}.
1775
	 * @param array $behaviors list of behaviors to be detached from the component
1776
	 * @since 3.2.3
1777
	 */
1778
	public function detachBehaviors($behaviors)
1779
	{
1780
		if ($this->_m !== null) {
1781
			foreach ($behaviors as $name => $behavior) {
1782
				if ($behavior instanceof TClassBehaviorEventParameter) {
1783
					$this->detachBehavior($behavior->getName(), $behavior->getPriority());
1784
				} else {
1785
					$this->detachBehavior(is_string($behavior) ? $behavior : $name);
1786
				}
1787
			}
1788
		}
1789
	}
1790
1791
	/**
1792
	 * Detaches all behaviors from the component.
1793
	 * @since 3.2.3
1794
	 */
1795
	public function clearBehaviors()
1796
	{
1797
		if ($this->_m !== null) {
1798
			foreach ($this->_m->toArray() as $name => $behavior) {
1799
				$this->detachBehavior($name);
1800
			}
1801
			$this->_m = null;
1802
		}
1803
	}
1804
1805
	/**
1806
	 * Attaches a behavior to this component.
1807
	 * This method will create the behavior object based on the given
1808
	 * configuration. After that, the behavior object will be initialized
1809
	 * by calling its {@link IBehavior::attach} method.
1810
	 *
1811
	 * Already attached behaviors may implement the function:
1812
	 * <code>
1813
	 *	public function dyAttachBehavior($name,$behavior[, $chain]) {
1814
	 *  }
1815
	 * </code>
1816
	 * to be executed when attachBehavior is called.  All attached behaviors are notified through
1817
	 * dyAttachBehavior.
1818
	 *
1819
	 * @param string $name the behavior's name. It should uniquely identify this behavior.
1820
	 * @param array|IBaseBehavior|string $behavior the behavior configuration. This is the name of the Behavior Class
1821
	 * instanced by {@link PradoBase::createComponent}, or is a Behavior, or is an array of
1822
	 * ['class'=>'TBehavior' property1='value 1' property2='value2'...] with the class and properties
1823
	 * with values.
1824
	 * @param null|numeric $priority
1825
	 * @return IBehavior the behavior object
1826
	 * @since 3.2.3
1827
	 */
1828
	public function attachBehavior($name, $behavior, $priority = null)
1829
	{
1830
		$behavior = self::instanceBehavior($behavior);
1831
		if ($this->_m === null) {
1832
			$this->_m = new TPriorityMap();
1833
		}
1834
		$this->_m->add($name, $behavior, $priority);
1835
		$behavior->attach($this);
1836
		$this->dyAttachBehavior($name, $behavior);
1837
		return $behavior;
1838
	}
1839
1840
	/**
1841
	 * Detaches a behavior from the component.
1842
	 * The behavior's {@link IBehavior::detach} method will be invoked.
1843
	 *
1844
	 * Behaviors may implement the function:
1845
	 * <code>
1846
	 *	public function dyDetachBehavior($name,$behavior[, $chain]) {
1847
	 *  }
1848
	 * </code>
1849
	 * to be executed when detachBehavior is called.  All attached behaviors are notified through
1850
	 * dyDetachBehavior.
1851
	 *
1852
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
1853
	 * @param false|numeric $priority the behavior's priority. This defaults to false, aka any priority.
1854
	 * @return null|IBehavior the detached behavior. Null if the behavior does not exist.
1855
	 * @since 3.2.3
1856
	 */
1857
	public function detachBehavior($name, $priority = false)
1858
	{
1859
		if ($this->_m != null && ($behavior = $this->_m->itemAt($name, $priority))) {
1860
			$this->dyDetachBehavior($name, $behavior);
1861
			$behavior->detach($this);
1862
			$this->_m->remove($name, $priority);
1863
			return $behavior;
1864
		}
1865
		return null;
1866
	}
1867
1868
	/**
1869
	 * Enables all behaviors attached to this component independent of the behaviors
1870
	 *
1871
	 * Behaviors may implement the function:
1872
	 * <code>
1873
	 *	public function dyEnableBehaviors($name,$behavior[, $chain]) {
1874
	 *  }
1875
	 * </code>
1876
	 * to be executed when enableBehaviors is called.  All attached behaviors are notified through
1877
	 * dyEnableBehaviors.
1878
	 * @since 3.2.3
1879
	 */
1880
	public function enableBehaviors()
1881
	{
1882
		if (!$this->_behaviorsenabled) {
1883
			$this->_behaviorsenabled = true;
1884
			$this->dyEnableBehaviors();
1885
		}
1886
	}
1887
1888
	/**
1889
	 * Disables all behaviors attached to this component independent of the behaviors
1890
	 *
1891
	 * Behaviors may implement the function:
1892
	 * <code>
1893
	 *	public function dyDisableBehaviors($name,$behavior[, $chain]) {
1894
	 *  }
1895
	 * </code>
1896
	 * to be executed when disableBehaviors is called.  All attached behaviors are notified through
1897
	 * dyDisableBehaviors.
1898
	 * @since 3.2.3
1899
	 */
1900
	public function disableBehaviors()
1901
	{
1902
		if ($this->_behaviorsenabled) {
1903
			$this->dyDisableBehaviors();
1904
			$this->_behaviorsenabled = false;
1905
		}
1906
	}
1907
1908
1909
	/**
1910
	 * Returns if all the behaviors are turned on or off for the object.
1911
	 * @return bool whether or not all behaviors are enabled (true) or not (false)
1912
	 * @since 3.2.3
1913
	 */
1914
	public function getBehaviorsEnabled()
1915
	{
1916
		return $this->_behaviorsenabled;
1917
	}
1918
1919
	/**
1920
	 * Enables an attached object behavior.  This cannot enable or disable whole class behaviors.
1921
	 * A behavior is only effective when it is enabled.
1922
	 * A behavior is enabled when first attached.
1923
	 *
1924
	 * Behaviors may implement the function:
1925
	 * <code>
1926
	 *	public function dyEnableBehavior($name,$behavior[, $chain]) {
1927
	 *  }
1928
	 * </code>
1929
	 * to be executed when enableBehavior is called.  All attached behaviors are notified through
1930
	 * dyEnableBehavior.
1931
	 *
1932
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
1933
	 * @since 3.2.3
1934
	 */
1935
	public function enableBehavior($name)
1936
	{
1937
		if ($this->_m != null && isset($this->_m[$name])) {
1938
			if ($this->_m[$name] instanceof IBehavior) {
1939
				$this->_m[$name]->setEnabled(true);
1940
				$this->dyEnableBehavior($name, $this->_m[$name]);
1941
				return true;
1942
			}
1943
			return false;
1944
		}
1945
		return null;
1946
	}
1947
1948
	/**
1949
	 * Disables an attached behavior.  This cannot enable or disable whole class behaviors.
1950
	 * A behavior is only effective when it is enabled.
1951
	 *
1952
	 * Behaviors may implement the function:
1953
	 * <code>
1954
	 *	public function dyDisableBehavior($name,$behavior[, $chain]) {
1955
	 *  }
1956
	 * </code>
1957
	 * to be executed when disableBehavior is called.  All attached behaviors are notified through
1958
	 * dyDisableBehavior.
1959
	 *
1960
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
1961
	 * @since 3.2.3
1962
	 */
1963
	public function disableBehavior($name)
1964
	{
1965
		if ($this->_m != null && isset($this->_m[$name])) {
1966
			if ($this->_m[$name] instanceof IBehavior) {
1967
				$this->_m[$name]->setEnabled(false);
1968
				$this->dyDisableBehavior($name, $this->_m[$name]);
1969
				return true;
1970
			}
1971
			return false;
1972
		}
1973
		return null;
1974
	}
1975
1976
	/**
1977
	 * Returns an array with the names of all variables of that object that should be serialized.
1978
	 * Do not call this method. This is a PHP magic method that will be called automatically
1979
	 * prior to any serialization.
1980
	 */
1981
	public function __sleep()
1982
	{
1983
		$a = (array) $this;
1984
		$a = array_keys($a);
1985
		$exprops = [];
1986
		$this->_getZappableSleepProps($exprops);
1987
		return array_diff($a, $exprops);
1988
	}
1989
1990
	/**
1991
	 * Returns an array with the names of all variables of this object that should NOT be serialized
1992
	 * because their value is the default one or useless to be cached for the next page loads.
1993
	 * Reimplement in derived classes to add new variables, but remember to  also to call the parent
1994
	 * implementation first.
1995
	 * @param array $exprops by reference
1996
	 */
1997
	protected function _getZappableSleepProps(&$exprops)
1998
	{
1999
		if ($this->_listeningenabled === false) {
2000
			$exprops[] = "\0*\0_listeningenabled";
2001
		}
2002
		if ($this->_behaviorsenabled === true) {
2003
			$exprops[] = "\0*\0_behaviorsenabled";
2004
		}
2005
		$exprops[] = "\0*\0_e";
2006
		if ($this->_m === null) {
2007
			$exprops[] = "\0*\0_m";
2008
		}
2009
	}
2010
}
2011