Completed
Push — main ( 169704 )
by
unknown
24s queued 19s
created

TComponent   F

Complexity

Total Complexity 316

Size/Duplication

Total Lines 1549
Duplicated Lines 0 %

Test Coverage

Coverage 93.18%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 459
c 2
b 0
f 0
dl 0
loc 1549
ccs 437
cts 469
cp 0.9318
rs 2
wmc 316

50 Methods

Rating   Name   Duplication   Size   Complexity  
B getClassHierarchy() 0 19 8
A filter_prado_fx() 0 3 1
A hasProperty() 0 3 2
A __destruct() 0 4 2
A getSubProperty() 0 7 2
A getClassFxEvents() 0 10 2
A setSubProperty() 0 10 2
A getAutoGlobalListen() 0 3 1
B canSetProperty() 0 12 9
D __set() 0 32 23
D __call() 0 60 29
A listen() 0 22 4
C __unset() 0 27 16
A unlisten() 0 22 4
B canGetProperty() 0 12 9
A getListeningToGlobalEvents() 0 3 1
A __construct() 0 11 4
C __get() 0 36 17
A _getZappableSleepProps() 0 13 5
B hasEvent() 0 12 11
A addParsedObject() 0 3 1
A createdOnTemplate() 0 4 1
D raiseEvent() 0 96 29
B detachClassBehavior() 0 23 8
A __sleep() 0 7 1
A disableBehaviors() 0 5 2
A evaluateStatements() 0 13 3
A detachBehavior() 0 9 3
A fxAttachClassBehavior() 0 4 2
D __isset() 0 23 18
C attachClassBehavior() 0 31 12
A disableBehavior() 0 11 4
A fxDetachClassBehavior() 0 4 2
A evaluateExpression() 0 7 2
A attachBehaviors() 0 7 3
B hasMethod() 0 13 10
A instanceBehavior() 0 9 5
A attachBehavior() 0 10 2
A attachEventHandler() 0 3 1
A enableBehavior() 0 11 4
C getEventHandlers() 0 22 12
A getBehaviors() 0 3 2
A clearBehaviors() 0 7 3
B hasEventHandler() 0 17 11
A detachBehaviors() 0 8 5
A detachEventHandler() 0 10 3
B isa() 0 21 11
A getBehaviorsEnabled() 0 3 1
A enableBehaviors() 0 5 2
A asa() 0 3 1

How to fix   Complexity   

Complex Class

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

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

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

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 dyListen(array $globalEvents)
315
 * @method void dyUnlisten(array $globalEvents)
316
 * @method string dyPreRaiseEvent(string $name, mixed $sender, \Prado\TEventParameter $param, null|numeric $responsetype, null|function $postfunction)
317
 * @method dyIntraRaiseEventTestHandler(callable $handler, mixed $sender, \Prado\TEventParameter $param, string $name)
318
 * @method bool dyIntraRaiseEventPostHandler(string $name, mixed $sender, \Prado\TEventParameter $param, callable $handler, $response)
319
 * @method array dyPostRaiseEvent(array $responses, string $name, mixed $sender, \Prado\TEventParameter $param, null|numeric $responsetype, null|function $postfunction)
320
 * @method string dyEvaluateExpressionFilter(string $statements)
321
 * @method string dyEvaluateStatementsFilter(string $statements)
322
 * @method dyCreatedOnTemplate(\Prado\TComponent $parent)
323
 * @method void dyAddParsedObject(\Prado\TComponent|string $object)
324
 * @method void dyAttachBehavior(string $name, IBehavior $behavior)
325
 * @method void dyDetachBehavior(string $name, IBehavior $behavior)
326
 * @method void dyEnableBehavior(string $name, IBehavior $behavior)
327
 * @method void dyDisableBehavior(string $name, IBehavior $behavior)
328
 * @method void dyEnableBehaviors()
329
 * @method void dyDisableBehaviors()
330
 */
331
class TComponent
332
{
333
	/**
334
	 * @var array event handler lists
335
	 */
336
	protected $_e = [];
337
338
	/**
339
	 * @var bool if listening is enabled.  Automatically turned on or off in
340
	 * constructor according to {@link getAutoGlobalListen}.  Default false, off
341
	 */
342
	protected $_listeningenabled = false;
343
344
	/**
345
	 * @var array static registered global event handler lists
346 194
	 */
347
	private static $_ue = [];
348 194
349 38
	/**
350
	 * @var bool if object behaviors are on or off.  default true, on
351
	 */
352 194
	protected $_behaviorsenabled = true;
353 194
354 194
	/**
355 194
	 * @var TPriorityMap list of object behaviors
356
	 */
357
	protected $_m;
358 194
359
	/**
360
	 * @var array static global class behaviors, these behaviors are added upon instantiation of a class
361
	 */
362
	private static $_um = [];
363
364
365
	/**
366
	 * @const string the name of the global {@link raiseEvent} listener
367
	 */
368
	public const GLOBAL_RAISE_EVENT_LISTENER = 'fxGlobalListener';
369
370
371
	/**
372
	 * The common __construct
373 183
	 * If desired by the new object, this will auto install and listen to global event functions
374
	 * as defined by the object via 'fx' methods. This also attaches any predefined behaviors.
375 183
	 * This function installs all class behaviors in a class hierarchy from the deepest subclass
376
	 * through each parent to the top most class, TComponent.
377
	 */
378
	public function __construct()
379
	{
380
		if ($this->getAutoGlobalListen()) {
381
			$this->listen();
382
		}
383
384
		$classes = $this->getClassHierarchy(true);
385
		array_pop($classes);
386 620
		foreach ($classes as $class) {
387
			if (isset(self::$_um[$class])) {
388 620
				$this->attachBehaviors(self::$_um[$class]);
389
			}
390
		}
391 620
	}
392
393
394
	/**
395
	 * Tells TComponent whether or not to automatically listen to global events.
396
	 * Defaults to false because PHP variable cleanup is affected if this is true.
397
	 * When unsetting a variable that is listening to global events, {@link unlisten}
398
	 * must explicitly be called when cleaning variables allocation or else the global
399 38
	 * event registry will contain references to the old object. This is true for PHP 5.4
400
	 *
401 38
	 * Override this method by a subclass to change the setting.  When set to true, this
402
	 * will enable {@link __construct} to call {@link listen}.
403
	 *
404
	 * @return bool whether or not to auto listen to global events during {@link __construct}, default false
405
	 */
406
	public function getAutoGlobalListen()
407
	{
408
		return false;
409
	}
410
411 194
412
	/**
413 194
	 * The common __destruct
414 194
	 * This unlistens from the global event routines if listening
415 194
	 *
416 192
	 * PHP 5.3 does not __destruct objects when they are nulled and thus unlisten must be
417
	 * called must be explicitly called. PHP 7.4.0 uses WeakReferences and this will be called
418 194
	 * automatically.
419 194
	 */
420
	public function __destruct()
421 1
	{
422
		if ($this->_listeningenabled) {
423
			$this->unlisten();
424
		}
425
	}
426
427
428
	/**
429
	 * This utility function is a private array filter method.  The array values
430
	 * that start with 'fx' are filtered in.
431
	 * @param mixed $name
432
	 */
433
	private function filter_prado_fx($name)
434
	{
435
		return strncasecmp($name, 'fx', 2) === 0;
436
	}
437
438
439 38
	/**
440
	 * This returns an array of the class name and the names of all its parents.  The base object last,
441 38
	 * {@link TComponent}, and the deepest subclass is first.
442
	 * @param bool $lowercase optional should the names be all lowercase true/false
443
	 * @return string[] array of strings being the class hierarchy of $this.
444
	 */
445 38
	public function getClassHierarchy($lowercase = false)
446
	{
447 38
		static $_classhierarchy = [];
448 38
		$class = get_class($this);
449
		if (isset($_classhierarchy[$class]) && isset($_classhierarchy[$class][$lowercase ? 1 : 0])) {
450
			return $_classhierarchy[$class][$lowercase ? 1 : 0];
451 38
		}
452 3
		$classes = array_values(class_implements($class));
453 3
		array_push($classes, $class);
454
		while ($class = get_parent_class($class)) {
455
			array_push($classes, $class);
456 38
		}
457
		if ($lowercase) {
458 38
			$classes = array_map('strtolower', $classes);
459
		}
460 38
		$_classhierarchy[$class] = $_classhierarchy[$class] ?? [];
461
		$_classhierarchy[$class][$lowercase ? 1 : 0] = $classes;
462
463
		return $classes;
464
	}
465
466
	/**
467
	 * This caches the 'fx' events for classes.
468
	 * @param object $class
469
	 * @return string[] fx events from a specific class
470
	 */
471
	protected function getClassFxEvents($class)
472
	{
473
		static $_classfx = [];
474
		$className = get_class($class);
475
		if (isset($_classfx[$className])) {
476 38
			return $_classfx[$className];
477
		}
478 38
		$fx = array_filter(get_class_methods($class), [$this, 'filter_prado_fx']);
479 3
		$_classfx[$className] = $fx;
480
		return $fx;
481
	}
482 38
483
	/**
484 38
	 * This adds an object's fx event handlers into the global broadcaster to listen into any
485 38
	 * broadcast global events called through {@link raiseEvent}
486
	 *
487
	 * Behaviors may implement the function:
488 38
	 * <code>
489 3
	 *	public function dyListen($globalEvents[, $chain]) {
490 3
	 * 		$this->listen(); //eg
491
	 * }
492
	 * </code>
493 38
	 * to be executed when listen is called.  All attached behaviors are notified through dyListen.
494
	 *
495 38
	 * @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...
496
	 */
497 38
	public function listen()
498
	{
499
		if ($this->_listeningenabled) {
500
			return;
501
		}
502
503
		$fx = $this->getClassFxEvents($this);
504 4
505
		foreach ($fx as $func) {
506 4
			$this->getEventHandlers($func)->add([$this, $func]);
507
		}
508
509
		if (is_a($this, 'Prado\\Util\\IDynamicMethods')) {
510
			$this->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, [$this, '__dycall']);
511
			array_push($fx, TComponent::GLOBAL_RAISE_EVENT_LISTENER);
512
		}
513
514
		$this->_listeningenabled = true;
515
516
		$this->dyListen($fx);
517
518
		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...
519
	}
520
521
	/**
522
	 * this removes an object's fx events from the global broadcaster
523
	 *
524
	 * Behaviors may implement the function:
525
	 * <code>
526
	 *	public function dyUnlisten($globalEvents[, $chain]) {
527
	 * 		$this->behaviorUnlisten(); //eg
528
	 * }
529
	 * </code>
530
	 * to be executed when listen is called.  All attached behaviors are notified through dyUnlisten.
531
	 *
532
	 * @return numeric the number of global events that were unregistered from the global event registry
533 211
	 */
534
	public function unlisten()
535 211
	{
536 211
		if (!$this->_listeningenabled) {
537 4
			return;
538 4
		}
539 4
540
		$fx = $this->getClassFxEvents($this);
541
542
		foreach ($fx as $func) {
543
			$this->detachEventHandler($func, [$this, $func]);
544
		}
545
546
		if (is_a($this, 'Prado\\Util\\IDynamicMethods')) {
547
			$this->detachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, [$this, '__dycall']);
548 4
			array_push($fx, TComponent::GLOBAL_RAISE_EVENT_LISTENER);
549
		}
550
551
		$this->_listeningenabled = false;
552
553 211
		$this->dyUnlisten($fx);
554 27
555 27
		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...
556 27
	}
557 24
558 3
	/**
559 3
	 * Gets the state of listening to global events
560 2
	 * @return bool is Listening to global broadcast enabled
561
	 */
562 24
	public function getListeningToGlobalEvents()
563
	{
564
		return $this->_listeningenabled;
565 27
	}
566 27
567
568
	/**
569 8
	 * Calls a method.
570 8
	 * Do not call this method directly. This is a PHP magic method that we override
571 8
	 * to allow behaviors, dynamic events (intra-object/behavior events),
572 3
	 * undefined dynamic and global events, and
573
	 * to allow using the following syntax to call a property setter or getter.
574 8
	 * <code>
575
	 * $this->getPropertyName($value); // if there's a $this->getjsPropertyName() method
576
	 * $this->setPropertyName($value); // if there's a $this->setjsPropertyName() method
577
	 * </code>
578
	 *
579
	 * Additional object behaviors override class behaviors.
580 211
	 * dynamic and global events do not fail even if they aren't implemented.
581 211
	 * Any intra-object/behavior dynamic events that are not implemented by the behavior
582 6
	 * return the first function paramater or null when no parameters are specified.
583
	 *
584 211
	 * @param string $method method name that doesn't exist and is being called on the object
585
	 * @param mixed $args method parameters
586
	 * @throws TInvalidOperationException If the property is not defined or read-only or
587
	 * 		method is undefined
588 4
	 * @return mixed result of the method call, or false if 'fx' or 'dy' function but
589 4
	 *		is not found in the class, otherwise it runs
590
	 */
591
	public function __call($method, $args)
592
	{
593
		$getset = substr($method, 0, 3);
594
		if (($getset == 'get') || ($getset == 'set')) {
595
			$propname = substr($method, 3);
596
			$jsmethod = $getset . 'js' . $propname;
597
			if (method_exists($this, $jsmethod)) {
598
				if (count($args) > 0) {
599
					if ($args[0] && !($args[0] instanceof TJavaScriptString)) {
600
						$args[0] = new TJavaScriptString($args[0]);
601
					}
602
				}
603
				return call_user_func_array([$this, $jsmethod], $args);
604
			}
605
606
			if (($getset == 'set') && method_exists($this, 'getjs' . $propname)) {
607
				throw new TInvalidOperationException('component_property_readonly', get_class($this), $method);
608
			}
609
		}
610
611
		if ($this->_m !== null && $this->_behaviorsenabled) {
612
			if (strncasecmp($method, 'dy', 2) === 0) {
613
				$callchain = null;
614
				foreach ($this->_m->toArray() as $behavior) {
615
					if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && (method_exists($behavior, $method) || ($behavior instanceof IDynamicMethods))) {
616
						$behavior_args = $args;
617 95
						if ($behavior instanceof IClassBehavior) {
618
							array_unshift($behavior_args, $this);
619 95
						}
620
						if (!$callchain) {
621 84
							$callchain = new TCallChain($method);
622 25
						}
623
						$callchain->addCall([$behavior, $method], $behavior_args);
624 2
					}
625 23
				}
626
				if ($callchain) {
627 13
					return call_user_func_array([$callchain, 'call'], $args);
628 13
				}
629 12
			} else {
630
				foreach ($this->_m->toArray() as $behavior) {
631 13
					if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && method_exists($behavior, $method)) {
632 14
						if ($behavior instanceof IClassBehavior) {
633
							array_unshift($args, $this);
634 3
						}
635 3
						return call_user_func_array([$behavior, $method], $args);
636 1
					}
637
				}
638 3
			}
639 13
		}
640
641 13
		if (strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
642 9
			if ($this instanceof IDynamicMethods) {
643 7
				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

643
				return $this->/** @scrutinizer ignore-call */ __dycall($method, $args);
Loading history...
644 4
			}
645 4
			return $args[0] ?? null;
646 4
		}
647 4
648
		// don't thrown an exception for __magicMethods() or any other weird methods natively implemented by php
649
		if (!method_exists($this, $method)) {
650
			throw new TApplicationException('component_method_undefined', get_class($this), $method);
651
		}
652 4
	}
653
654
655
	/**
656
	 * Returns a property value or an event handler list by property or event name.
657
	 * Do not call this method. This is a PHP magic method that we override
658
	 * to allow using the following syntax to read a property:
659
	 * <code>
660
	 * $value=$component->PropertyName;
661
	 * $value=$component->jsPropertyName; // return JavaScript literal
662
	 * </code>
663
	 * and to obtain the event handler list for an event,
664
	 * <code>
665
	 * $eventHandlerList=$component->EventName;
666
	 * </code>
667
	 * This will also return the global event handler list when specifing an 'fx'
668
	 * event,
669
	 * <code>
670 77
	 * $globalEventHandlerList=$component->fxEventName;
671
	 * </code>
672 77
	 * When behaviors are enabled, this will return the behavior of a specific
673 76
	 * name, a property of a behavior, or an object 'on' event defined by the behavior.
674 2
	 * @param string $name the property name or the event name
675
	 * @throws TInvalidOperationException if the property/event is not defined.
676 76
	 * @return mixed the property value or the event handler list as {@link TWeakCallableCollection}
677 5
	 */
678 1
	public function __get($name)
679 1
	{
680
		if (method_exists($this, $getter = 'get' . $name)) {
681 1
			// getting a property
682 4
			return $this->$getter();
683 3
		} elseif (method_exists($this, $jsgetter = 'getjs' . $name)) {
684 4
			// getting a javascript property
685 4
			return (string) $this->$jsgetter();
686 4
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
687 4
			// getting an event (handler list)
688 4
			$name = strtolower($name);
689 4
			if (!isset($this->_e[$name])) {
690 4
				$this->_e[$name] = new TWeakCallableCollection();
691
			}
692
			return $this->_e[$name];
693 4
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
694 4
			// getting a global event (handler list)
695
			$name = strtolower($name);
696
			if (!isset(self::$_ue[$name])) {
697
				self::$_ue[$name] = new TWeakCallableCollection();
698 1
			}
699 1
			return self::$_ue[$name];
700
		} elseif ($this->_behaviorsenabled) {
701 1
			// getting a behavior property/event (handler list)
702
			if (isset($this->_m[$name])) {
703
				return $this->_m[$name];
704
			} elseif ($this->_m !== null) {
705
				foreach ($this->_m->toArray() as $behavior) {
706
					if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) &&
707
						(property_exists($behavior, $name) || $behavior->canGetProperty($name) || $behavior->hasEvent($name))) {
708
						return $behavior->$name;
709
					}
710
				}
711
			}
712
		}
713
		throw new TInvalidOperationException('component_property_undefined', get_class($this), $name);
714
	}
715
716
	/**
717 5
	 * Sets value of a component property.
718
	 * Do not call this method. This is a PHP magic method that we override
719 5
	 * to allow using the following syntax to set a property or attach an event handler.
720 5
	 * <code>
721 3
	 * $this->PropertyName=$value;
722 2
	 * $this->jsPropertyName=$value; // $value will be treated as a JavaScript literal
723 1
	 * $this->EventName=$handler;
724 1
	 * $this->fxEventName=$handler; //global event listener
725 1
	 * </code>
726 1
	 * When behaviors are enabled, this will also set a behaviors properties and events.
727 1
	 * @param string $name the property name or event name
728 1
	 * @param mixed $value the property value or event handler
729 1
	 * @throws TInvalidOperationException If the property is not defined or read-only.
730 1
	 */
731 1
	public function __set($name, $value)
732
	{
733 1
		if (method_exists($this, $setter = 'set' . $name)) {
734 1
			if (strncasecmp($name, 'js', 2) === 0 && $value && !($value instanceof TJavaScriptLiteral)) {
735 1
				$value = new TJavaScriptLiteral($value);
736
			}
737
			return $this->$setter($value);
738
		} elseif (method_exists($this, $jssetter = 'setjs' . $name)) {
739 1
			if ($value && !($value instanceof TJavaScriptString)) {
740
				$value = new TJavaScriptString($value);
741
			}
742
			return $this->$jssetter($value);
743
		} elseif ((strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) || strncasecmp($name, 'fx', 2) === 0) {
744
			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...
745
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->_behaviorsenabled) {
746
			$sets = 0;
747
			foreach ($this->_m->toArray() as $behavior) {
748
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) &&
749
					(property_exists($behavior, $name) || $behavior->canSetProperty($name) || $behavior->hasEvent($name))) {
750
					$behavior->$name = $value;
751
					$sets++;
752
				}
753 3
			}
754
			if ($sets) {
755 3
				return $value;
756 2
			}
757 3
		}
758 1
759 2
		if (method_exists($this, 'get' . $name) || method_exists($this, 'getjs' . $name)) {
760 2
			throw new TInvalidOperationException('component_property_readonly', get_class($this), $name);
761 2
		} else {
762 1
			throw new TInvalidOperationException('component_property_undefined', get_class($this), $name);
763 2
		}
764 2
	}
765 1
766
	/**
767 2
	 * Checks if a property value is null, there are no events in the object
768 2
	 * event list or global event list registered under the name, and, if
769 2
	 * behaviors are enabled,
770 2
	 * Do not call this method. This is a PHP magic method that we override
771 2
	 * to allow using isset() to detect if a component property is set or not.
772
	 * This also works for global events.  When behaviors are enabled, it
773
	 * will check for a behavior of the specified name, and also check
774 2
	 * the behavior for events and properties.
775 2
	 * @param string $name the property name or the event name
776
	 * @since 3.2.3
777
	 */
778 1
	public function __isset($name)
779 1
	{
780
		if (method_exists($this, $getter = 'get' . $name)) {
781 3
			return $this->$getter() !== null;
782
		} elseif (method_exists($this, $jsgetter = 'getjs' . $name)) {
783
			return $this->$jsgetter() !== null;
784
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
785
			$name = strtolower($name);
786
			return isset($this->_e[$name]) && $this->_e[$name]->getCount();
787
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
788
			$name = strtolower($name);
789
			return isset(self::$_ue[$name]) && self::$_ue[$name]->getCount();
790 1
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->_behaviorsenabled) {
791
			if (isset($this->_m[$name])) {
792 1
				return true;
793
			}
794
			foreach ($this->_m->toArray() as $behavior) {
795
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && (property_exists($behavior, $name) || $behavior->canGetProperty($name) || $behavior->hasEvent($name))) {
796
					return isset($behavior->$name);
797
				}
798
			}
799
		}
800
		return false;
801
	}
802
803
	/**
804 9
	 * Sets a component property to be null.  Clears the object or global
805
	 * events. When enabled, loops through all behaviors and unsets the
806 9
	 * property or event.
807 6
	 * Do not call this method. This is a PHP magic method that we override
808 8
	 * to allow using unset() to set a component property to be null.
809 2
	 * @param string $name the property name or the event name
810 2
	 * @throws TInvalidOperationException if the property is read only.
811 2
	 * @since 3.2.3
812
	 */
813
	public function __unset($name)
814
	{
815 8
		if (method_exists($this, $setter = 'set' . $name)) {
816
			$this->$setter(null);
817
		} elseif (method_exists($this, $jssetter = 'setjs' . $name)) {
818
			$this->$jssetter(null);
819
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
820
			$this->_e[strtolower($name)]->clear();
821
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
822
			$this->getEventHandlers($name)->remove([$this, $name]);
823
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->_behaviorsenabled) {
824
			if (isset($this->_m[$name])) {
825
				$this->detachBehavior($name);
826
			} else {
827 11
				$unset = 0;
828
				foreach ($this->_m->toArray() as $behavior) {
829 11
					if ((!($behavior instanceof IBehavior) || $behavior->getEnabled())) {
830 8
						unset($behavior->$name);
831 7
						$unset++;
832 2
					}
833 2
				}
834 2
				if (!$unset && method_exists($this, 'get' . $name)) {
835
					throw new TInvalidOperationException('component_property_readonly', get_class($this), $name);
836
				}
837
			}
838 7
		} elseif (method_exists($this, 'get' . $name)) {
839
			throw new TInvalidOperationException('component_property_readonly', get_class($this), $name);
840
		}
841
	}
842
843
	/**
844
	 * Determines whether a property is defined.
845
	 * A property is defined if there is a getter or setter method
846
	 * defined in the class. Note, property names are case-insensitive.
847
	 * @param string $name the property name
848
	 * @return bool whether the property is defined
849
	 */
850
	public function hasProperty($name)
851 4
	{
852
		return $this->canGetProperty($name) || $this->canSetProperty($name);
853 4
	}
854 4
855 4
	/**
856
	 * Determines whether a property can be read.
857 4
	 * A property can be read if the class has a getter method
858
	 * for the property name. Note, property name is case-insensitive.
859
	 * This also checks for getjs.  When enabled, it loops through all
860
	 * active behaviors for the get property when undefined by the object.
861
	 * @param string $name the property name
862
	 * @return bool whether the property can be read
863
	 */
864
	public function canGetProperty($name)
865
	{
866
		if (method_exists($this, 'get' . $name) || method_exists($this, 'getjs' . $name)) {
867
			return true;
868
		} elseif ($this->_m !== null && $this->_behaviorsenabled) {
869
			foreach ($this->_m->toArray() as $behavior) {
870 2
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->canGetProperty($name)) {
871
					return true;
872 2
				}
873 2
			}
874 1
		}
875
		return false;
876 2
	}
877 2
878
	/**
879 2
	 * Determines whether a property can be set.
880 2
	 * A property can be written if the class has a setter method
881
	 * for the property name. Note, property name is case-insensitive.
882
	 * This also checks for setjs.  When enabled, it loops through all
883
	 * active behaviors for the set property when undefined by the object.
884
	 * @param string $name the property name
885
	 * @return bool whether the property can be written
886
	 */
887
	public function canSetProperty($name)
888
	{
889
		if (method_exists($this, 'set' . $name) || method_exists($this, 'setjs' . $name)) {
890
			return true;
891
		} elseif ($this->_m !== null && $this->_behaviorsenabled) {
892
			foreach ($this->_m->toArray() as $behavior) {
893
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->canSetProperty($name)) {
894 176
					return true;
895
				}
896 176
			}
897 176
		}
898 3
		return false;
899 3
	}
900 3
901 3
	/**
902
	 * Evaluates a property path.
903
	 * A property path is a sequence of property names concatenated by '.' character.
904
	 * For example, 'Parent.Page' refers to the 'Page' property of the component's
905 3
	 * 'Parent' property value (which should be a component also).
906
	 * When a property is not defined by an object, this also loops through all
907
	 * active behaviors of the object.
908
	 * @param string $path property path
909
	 * @return mixed the property path value
910
	 */
911
	public function getSubProperty($path)
912
	{
913
		$object = $this;
914
		foreach (explode('.', $path) as $property) {
915 211
			$object = $object->$property;
916
		}
917 211
		return $object;
918 211
	}
919 211
920
	/**
921 178
	 * Sets a value to a property path.
922 54
	 * A property path is a sequence of property names concatenated by '.' character.
923 177
	 * For example, 'Parent.Page' refers to the 'Page' property of the component's
924 3
	 * 'Parent' property value (which should be a component also).
925 3
	 * When a property is not defined by an object, this also loops through all
926 3
	 * active behaviors of the object.
927
	 * @param string $path property path
928
	 * @param mixed $value the property path value
929
	 */
930
	public function setSubProperty($path, $value)
931 176
	{
932
		$object = $this;
933
		if (($pos = strrpos($path, '.')) === false) {
934
			$property = $path;
935
		} else {
936
			$object = $this->getSubProperty(substr($path, 0, $pos));
937
			$property = substr($path, $pos + 1);
938
		}
939
		$object->$property = $value;
940
	}
941 92
942
	/**
943 92
	 * Determines whether a method is defined. When behaviors are enabled, this
944 63
	 * will loop through all enabled behaviors checking for the method as well.
945 63
	 * Nested behaviors within behaviors are not supported but the nested behavior can
946 14
	 * affect the primary behavior like any behavior affects their owner.
947
	 * Note, method name are case-insensitive.
948 63
	 * @param string $name the method name
949 38
	 * @return bool
950 38
	 * @since 4.2.2
951 38
	 */
952 5
	public function hasMethod($name)
953
	{
954 38
		if (method_exists($this, $name) || strncasecmp($name, 'fx', 2) === 0 || strncasecmp($name, 'dy', 2) === 0) {
955 6
			return true;
956 6
		} elseif ($this->_m !== null && $this->_behaviorsenabled) {
957 6
			foreach ($this->_m->toArray() as $behavior) {
958 6
				//method_exists($behavior, $name) rather than $behavior->hasMethod($name) b/c only one layer is supported, @4.2.2
959
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && method_exists($behavior, $name)) {
960
					return true;
961
				}
962 4
			}
963
		}
964
		return false;
965
	}
966
967
	/**
968
	 * Determines whether an event is defined.
969
	 * An event is defined if the class has a method whose name is the event name
970
	 * prefixed with 'on', 'fx', or 'dy'.
971
	 * Every object responds to every 'fx' and 'dy' event as they are in a universally
972
	 * accepted event space.  'on' event must be declared by the object.
973
	 * When enabled, this will loop through all active behaviors for 'on' events
974
	 * defined by the behavior.
975
	 * Note, event name is case-insensitive.
976
	 * @param string $name the event name
977
	 * @return bool
978
	 */
979
	public function hasEvent($name)
980
	{
981
		if ((strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) || strncasecmp($name, 'fx', 2) === 0 || strncasecmp($name, 'dy', 2) === 0) {
982
			return true;
983
		} elseif ($this->_m !== null && $this->_behaviorsenabled) {
984
			foreach ($this->_m->toArray() as $behavior) {
985
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->hasEvent($name)) {
986
					return true;
987
				}
988
			}
989
		}
990
		return false;
991
	}
992
993
	/**
994
	 * Checks if an event has any handlers.  This function also checks through all
995
	 * the behaviors for 'on' events when behaviors are enabled.
996
	 * 'dy' dynamic events are not handled by this function.
997
	 * @param string $name the event name
998
	 * @return bool whether an event has been attached one or several handlers
999
	 */
1000
	public function hasEventHandler($name)
1001
	{
1002
		$name = strtolower($name);
1003
		if (strncasecmp($name, 'fx', 2) === 0) {
1004
			return isset(self::$_ue[$name]) && self::$_ue[$name]->getCount() > 0;
1005
		} else {
1006
			if (isset($this->_e[$name]) && $this->_e[$name]->getCount() > 0) {
1007
				return true;
1008
			} elseif ($this->_m !== null && $this->_behaviorsenabled) {
1009
				foreach ($this->_m->toArray() as $behavior) {
1010 44
					if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->hasEventHandler($name)) {
1011
						return true;
1012 44
					}
1013 44
				}
1014
			}
1015
		}
1016
		return false;
1017
	}
1018
1019
	/**
1020
	 * Returns the list of attached event handlers for an 'on' or 'fx' event.   This function also
1021
	 * checks through all the behaviors for 'on' event lists when behaviors are enabled.
1022
	 * @param mixed $name
1023
	 * @throws TInvalidOperationException if the event is not defined
1024
	 * @return TWeakCallableCollection list of attached event handlers for an event
1025 38
	 */
1026
	public function getEventHandlers($name)
1027 38
	{
1028
		if (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
1029 38
			$name = strtolower($name);
1030 38
			if (!isset($this->_e[$name])) {
1031 1
				$this->_e[$name] = new TWeakCallableCollection();
1032
			}
1033
			return $this->_e[$name];
1034 2
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
1035
			$name = strtolower($name);
1036
			if (!isset(self::$_ue[$name])) {
1037
				self::$_ue[$name] = new TWeakCallableCollection();
1038
			}
1039
			return self::$_ue[$name];
1040
		} elseif ($this->_m !== null && $this->_behaviorsenabled) {
1041
			foreach ($this->_m->toArray() as $behavior) {
1042
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->hasEvent($name)) {
1043
					return $behavior->getEventHandlers($name);
1044
				}
1045
			}
1046
		}
1047
		throw new TInvalidOperationException('component_event_undefined', get_class($this), $name);
1048
	}
1049
1050
	/**
1051
	 * Attaches an event handler to an event.
1052
	 *
1053
	 * The handler must be a valid PHP callback, i.e., a string referring to
1054
	 * a global function name, or an array containing two elements with
1055
	 * the first element being an object and the second element a method name
1056
	 * of the object. In Prado, you can also use method path to refer to
1057
	 * an event handler. For example, array($object,'Parent.buttonClicked')
1058
	 * uses a method path that refers to the method $object->Parent->buttonClicked(...).
1059
	 *
1060
	 * The event handler must be of the following signature,
1061
	 * <code>
1062
	 * function handlerName($sender, $param) {}
1063
	 * function handlerName($sender, $param, $name) {}
1064
	 * </code>
1065
	 * where $sender represents the object that raises the event,
1066
	 * and $param is the event parameter. $name refers to the event name
1067
	 * being handled.
1068
	 *
1069
	 * This is a convenient method to add an event handler.
1070
	 * It is equivalent to {@link getEventHandlers}($name)->add($handler).
1071
	 * For complete management of event handlers, use {@link getEventHandlers}
1072
	 * to get the event handler list first, and then do various
1073
	 * {@link TWeakCallableCollection} operations to append, insert or remove
1074
	 * event handlers. You may also do these operations like
1075
	 * getting and setting properties, e.g.,
1076
	 * <code>
1077
	 * $component->OnClick[]=array($object,'buttonClicked');
1078
	 * $component->OnClick->insertAt(0,array($object,'buttonClicked'));
1079
	 * </code>
1080
	 * which are equivalent to the following
1081
	 * <code>
1082
	 * $component->getEventHandlers('OnClick')->add(array($object,'buttonClicked'));
1083
	 * $component->getEventHandlers('OnClick')->insertAt(0,array($object,'buttonClicked'));
1084
	 * </code>
1085
	 *
1086
	 * Due to the nature of {@link getEventHandlers}, any active behaviors defining
1087
	 * new 'on' events, this method will pass through to the behavior transparently.
1088
	 *
1089
	 * @param string $name the event name
1090
	 * @param callable $handler the event handler
1091
	 * @param null|numeric $priority the priority of the handler, defaults to null which translates into the
1092
	 * default priority of 10.0 within {@link TWeakCallableCollection}
1093
	 * @throws TInvalidOperationException if the event does not exist
1094
	 */
1095
	public function attachEventHandler($name, $handler, $priority = null)
1096
	{
1097
		$this->getEventHandlers($name)->add($handler, $priority);
1098
	}
1099
1100
	/**
1101
	 * Detaches an existing event handler.
1102
	 * This method is the opposite of {@link attachEventHandler}.  It will detach
1103
	 * any 'on' events defined by an objects active behaviors as well.
1104
	 * @param string $name event name
1105
	 * @param callable $handler the event handler to be removed
1106
	 * @param null|false|numeric $priority the priority of the handler, defaults to false which translates
1107
	 * to an item of any priority within {@link TWeakCallableCollection}; null means the default priority
1108
	 * @return bool if the removal is successful
1109 182
	 */
1110
	public function detachEventHandler($name, $handler, $priority = false)
1111 182
	{
1112 182
		if ($this->hasEventHandler($name)) {
1113 1
			try {
1114 1
				$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

1114
				$this->getEventHandlers($name)->remove($handler, /** @scrutinizer ignore-type */ $priority);
Loading history...
1115
				return true;
1116
			} catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1117 182
			}
1118 181
		}
1119
		return false;
1120
	}
1121 182
1122 182
	/**
1123
	 * Raises an event.  This raises both inter-object 'on' events and global 'fx' events.
1124 182
	 * This method represents the happening of an event and will
1125
	 * invoke all attached event handlers for the event in {@link TWeakCallableCollection} order.
1126 182
	 * This method does not handle intra-object/behavior dynamic 'dy' events.
1127 60
	 *
1128 60
	 * There are ways to handle event responses.  By default {@link EVENT_RESULT_FILTER},
1129 60
	 * all event responses are stored in an array, filtered for null responses, and returned.
1130 1
	 * If {@link EVENT_RESULT_ALL} is specified, all returned results will be stored along
1131 1
	 * with the sender and param in an array
1132
	 * <code>
1133 60
	 * 		$result[] = array('sender'=>$sender,'param'=>$param,'response'=>$response);
1134 60
	 * </code>
1135 59
	 *
1136
	 * If {@link EVENT_RESULT_FEED_FORWARD} is specified, then each handler result is then
1137
	 * fed forward as the parameters for the next event.  This allows for events to filter data
1138
	 * directly by affecting the event parameters
1139 59
	 *
1140
	 * If a callable function is set in the response type or the post function filter is specified then the
1141
	 * result of each called event handler is post processed by the callable function.  Used in
1142
	 * combination with {@link EVENT_RESULT_FEED_FORWARD}, any event (and its result) can be chained.
1143
	 *
1144
	 * When raising a global 'fx' event, registered handlers in the global event list for
1145
	 * {@link GLOBAL_RAISE_EVENT_LISTENER} are always added into the set of event handlers.  In this way,
1146
	 * these global events are always raised for every global 'fx' event.  The registered handlers for global
1147
	 * raiseEvent events have priorities.  Any registered global raiseEvent event handlers with a priority less than zero
1148
	 * are added before the main event handlers being raised and any registered global raiseEvent event handlers
1149
	 * with a priority equal or greater than zero are added after the main event handlers being raised.  In this way
1150
	 * all {@link GLOBAL_RAISE_EVENT_LISTENER} handlers are always called for every raised 'fx' event.
1151
	 *
1152
	 * Behaviors may implement the following functions:
1153
	 * <code>
1154
	 *	public function dyPreRaiseEvent($name,$sender,$param,$responsetype,$postfunction[, $chain]) {
1155 59
	 *  	return $name; //eg, the event name may be filtered/changed
1156 59
	 *  }
1157 59
	 *	public function dyIntraRaiseEventTestHandler($handler,$sender,$param,$name[, $chain]) {
1158
	 *  	return true; //should this particular handler be executed?  true/false
1159
	 *  }
1160 59
	 *  public function dyIntraRaiseEventPostHandler($name,$sender,$param,$handler,$response[, $chain]) {
1161 1
	 *		//contains the per handler response
1162 1
	 *  }
1163
	 *  public function dyPostRaiseEvent($responses,$name,$sender,$param,$responsetype,$postfunction[, $chain]) {
1164 59
	 *		return $responses;
1165 59
	 *  }
1166 1
	 * </code>
1167
	 * to be executed when raiseEvent is called.  The 'intra' dynamic events are called per handler in
1168 59
	 * the handler loop.
1169
	 *
1170
	 * dyPreRaiseEvent has the effect of being able to change the event being raised.  This intra
1171 59
	 * object/behavior event returns the name of the desired event to be raised.  It will pass through
1172
	 * if no dynamic event is specified, or if the original event name is returned.
1173
	 * dyIntraRaiseEventTestHandler returns true or false as to whether a specific handler should be
1174
	 * called for a specific raised event (and associated event arguments)
1175
	 * dyIntraRaiseEventPostHandler does not return anything.  This allows behaviors to access the results
1176
	 * of an event handler in the per handler loop.
1177
	 * dyPostRaiseEvent returns the responses.  This allows for any post processing of the event
1178 59
	 * results from the sum of all event handlers
1179
	 *
1180 59
	 * When handling a catch-all {@link __dycall}, the method name is the name of the event
1181 1
	 * and the parameters are the sender, the param, and then the name of the event.
1182
	 *
1183
	 * @param string $name the event name
1184 59
	 * @param mixed $sender the event sender object
1185 2
	 * @param \Prado\TEventParameter $param the event parameter
1186
	 * @param null|numeric $responsetype how the results of the event are tabulated.  default: {@link EVENT_RESULT_FILTER}  The default filters out
1187 58
	 *		null responses. optional
1188
	 * @param null|callable $postfunction any per handler filtering of the response result needed is passed through
1189
	 *		this if not null. default: null.  optional
1190 59
	 * @throws TInvalidOperationException if the event is undefined
1191 60
	 * @throws TInvalidDataValueException If an event handler is invalid
1192
	 * @return mixed the results of the event
1193
	 */
1194 173
	public function raiseEvent($name, $sender, $param, $responsetype = null, $postfunction = null)
1195 1
	{
1196
		$p = $param;
0 ignored issues
show
Unused Code introduced by
The assignment to $p is dead and can be removed.
Loading history...
1197
		if (is_callable($responsetype)) {
1198 182
			$postfunction = $responsetype;
1199 181
			$responsetype = null;
1200
		}
1201
1202 182
		if ($responsetype === null) {
1203
			$responsetype = TEventResults::EVENT_RESULT_FILTER;
1204 182
		}
1205
1206
		$name = strtolower($name);
1207
		$responses = [];
1208
1209
		$name = $this->dyPreRaiseEvent($name, $sender, $param, $responsetype, $postfunction);
0 ignored issues
show
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

1209
		$name = $this->dyPreRaiseEvent($name, $sender, $param, /** @scrutinizer ignore-type */ $responsetype, $postfunction);
Loading history...
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

1209
		$name = $this->dyPreRaiseEvent($name, $sender, $param, $responsetype, /** @scrutinizer ignore-type */ $postfunction);
Loading history...
1210
1211
		if ($this->hasEventHandler($name) || $this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER)) {
1212
			$handlers = $this->getEventHandlers($name);
1213
			$handlerArray = $handlers->toArray();
1214
			if (strncasecmp($name, 'fx', 2) === 0 && $this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER)) {
1215
				$globalhandlers = $this->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER);
1216
				$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

1216
				$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

1216
				$handlerArray = array_merge($globalhandlers->toArrayBelowPriority(0), $handlerArray, $globalhandlers->toArrayAbovePriority(/** @scrutinizer ignore-type */ 0));
Loading history...
1217
			}
1218
			$response = null;
1219
			foreach ($handlerArray as $handler) {
1220
				if ($this->dyIntraRaiseEventTestHandler($handler, $sender, $param, $name) === false) {
1221
					continue;
1222
				}
1223
1224 2
				if (is_string($handler)) {
1225
					if (($pos = strrpos($handler, '.')) !== false) {
1226 2
						$object = $this->getSubProperty(substr($handler, 0, $pos));
1227
						$method = substr($handler, $pos + 1);
1228 2
						if (method_exists($object, $method) || strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1229
							if ($method == '__dycall') {
1230
								$response = $object->__dycall($name, [$sender, $param, $name]);
1231 2
							} else {
1232 1
								$response = $object->$method($sender, $param, $name);
1233 1
							}
1234
						} else {
1235
							throw new TInvalidDataValueException('component_eventhandler_invalid', get_class($this), $name, $handler);
1236
						}
1237
					} else {
1238
						$response = call_user_func($handler, $sender, $param, $name);
1239
					}
1240
				} elseif (is_callable($handler, true)) {
1241
					[$object, $method] = $handler;
1242
					if (is_string($object)) {
1243
						$response = call_user_func($handler, $sender, $param, $name);
1244
					} else {
1245
						if (($pos = strrpos($method, '.')) !== false) {
1246
							$object = $object->getSubProperty(substr($method, 0, $pos));
1247
							$method = substr($method, $pos + 1);
1248
						}
1249
						if (method_exists($object, $method) || strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1250
							if ($method == '__dycall') {
1251
								$response = $object->__dycall($name, [$sender, $param, $name]);
1252
							} else {
1253
								$response = $object->$method($sender, $param, $name);
1254 2
							}
1255
						} else {
1256 2
							throw new TInvalidDataValueException('component_eventhandler_invalid', get_class($this), $name, $handler[1]);
1257
						}
1258 2
					}
1259 2
				} else {
1260
					throw new TInvalidDataValueException('component_eventhandler_invalid', get_class($this), $name, gettype($handler));
1261
				}
1262 2
1263 2
				$this->dyIntraRaiseEventPostHandler($name, $sender, $param, $handler, $response);
1264 2
1265 1
				if ($postfunction) {
1266 1
					$response = call_user_func_array($postfunction, [$sender, $param, $this, $response]);
1267
				}
1268
1269
				if ($responsetype & TEventResults::EVENT_RESULT_ALL) {
1270
					$responses[] = ['sender' => $sender, 'param' => $param, 'response' => $response];
1271
				} else {
1272
					$responses[] = $response;
1273
				}
1274
1275
				if ($response !== null && ($responsetype & TEventResults::EVENT_RESULT_FEED_FORWARD)) {
1276
					$param = $response;
1277
				}
1278
			}
1279
		} elseif (strncasecmp($name, 'on', 2) === 0 && !$this->hasEvent($name)) {
1280
			throw new TInvalidOperationException('component_event_undefined', get_class($this), $name);
1281
		}
1282
1283
		if ($responsetype & TEventResults::EVENT_RESULT_FILTER) {
1284
			$responses = array_filter($responses);
1285
		}
1286
1287
		$responses = $this->dyPostRaiseEvent($responses, $name, $sender, $param, $responsetype, $postfunction);
0 ignored issues
show
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

1287
		$responses = $this->dyPostRaiseEvent($responses, $name, $sender, $param, /** @scrutinizer ignore-type */ $responsetype, $postfunction);
Loading history...
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

1287
		$responses = $this->dyPostRaiseEvent($responses, $name, $sender, $param, $responsetype, /** @scrutinizer ignore-type */ $postfunction);
Loading history...
1288
1289 1
		return $responses;
1290
	}
1291 1
1292 1
	/**
1293 1
	 * Evaluates a PHP expression in the context of this control.
1294
	 *
1295
	 * Behaviors may implement the function:
1296
	 * <code>
1297
	 *	public function dyEvaluateExpressionFilter($expression, $chain) {
1298
	 * 		return $chain->dyEvaluateExpressionFilter(str_replace('foo', 'bar', $expression)); //example
1299
	 * }
1300
	 * </code>
1301
	 * to be executed when evaluateExpression is called.  All attached behaviors are notified through
1302
	 * dyEvaluateExpressionFilter.  The chaining is important in this function due to the filtering
1303
	 * pass-through effect.
1304
	 *
1305
	 * @param string $expression PHP expression
1306
	 * @throws TInvalidOperationException if the expression is invalid
1307
	 * @return mixed the expression result
1308
	 */
1309
	public function evaluateExpression($expression)
1310
	{
1311
		$expression = $this->dyEvaluateExpressionFilter($expression);
1312 1
		try {
1313
			return eval("return $expression;");
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1314 1
		} 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...
1315 1
			throw new TInvalidOperationException('component_expression_invalid', get_class($this), $expression, $e->getMessage());
1316
		}
1317
	}
1318
1319
	/**
1320
	 * Evaluates a list of PHP statements.
1321
	 *
1322
	 * Behaviors may implement the function:
1323
	 * <code>
1324
	 *	public function dyEvaluateStatementsFilter($statements, $chain) {
1325
	 * 		return $chain->dyEvaluateStatementsFilter(str_replace('foo', 'bar', $statements)); //example
1326 6
	 * }
1327
	 * </code>
1328 6
	 * to be executed when evaluateStatements is called.  All attached behaviors are notified through
1329 6
	 * dyEvaluateStatementsFilter.  The chaining is important in this function due to the filtering
1330
	 * pass-through effect.
1331 1
	 *
1332
	 * @param string $statements PHP statements
1333
	 * @throws TInvalidOperationException if the statements are invalid
1334
	 * @return string content echoed or printed by the PHP statements
1335
	 */
1336
	public function evaluateStatements($statements)
1337
	{
1338
		$statements = $this->dyEvaluateStatementsFilter($statements);
1339
		try {
1340
			ob_start();
1341
			if (eval($statements) === false) {
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1342 6
				throw new \Exception('');
1343
			}
1344 6
			$content = ob_get_contents();
1345 6
			ob_end_clean();
1346
			return $content;
1347 1
		} catch (\Exception $e) {
1348
			throw new TInvalidOperationException('component_statements_invalid', get_class($this), $statements, $e->getMessage());
1349
		}
1350
	}
1351
1352
	/**
1353
	 * This method is invoked after the component is instantiated by a template.
1354
	 * When this method is invoked, the component's properties have been initialized.
1355
	 * The default implementation of this method will invoke
1356
	 * the potential parent component's {@link addParsedObject}.
1357
	 * This method can be overridden.
1358
	 *
1359
	 * Behaviors may implement the function:
1360
	 * <code>
1361
	 *	public function dyCreatedOnTemplate($parent, $chain) {
1362
	 * 		return $chain->dyCreatedOnTemplate($parent); //example
1363
	 *  }
1364
	 * </code>
1365
	 * to be executed when createdOnTemplate is called.  All attached behaviors are notified through
1366
	 * dyCreatedOnTemplate.
1367
	 *
1368 6
	 * @param \Prado\TComponent $parent potential parent of this control
1369
	 * @see addParsedObject
1370 6
	 */
1371 5
	public function createdOnTemplate($parent)
1372
	{
1373 6
		$parent = $this->dyCreatedOnTemplate($parent);
1374
		$parent->addParsedObject($this);
1375
	}
1376
1377 6
	/**
1378
	 * Processes an object that is created during parsing template.
1379
	 * The object can be either a component or a static text string.
1380 6
	 * This method can be overridden to customize the handling of newly created objects in template.
1381 6
	 * Only framework developers and control developers should use this method.
1382 1
	 *
1383
	 * Behaviors may implement the function:
1384 6
	 * <code>
1385 6
	 *	public function dyAddParsedObject($object[, $chain]) {
1386
	 *  }
1387 6
	 * </code>
1388 1
	 * to be executed when addParsedObject is called.  All attached behaviors are notified through
1389
	 * dyAddParsedObject.
1390 6
	 *
1391 6
	 * @param \Prado\TComponent|string $object text string or component parsed and instantiated in template
1392 6
	 * @see createdOnTemplate
1393 6
	 */
1394
	public function addParsedObject($object)
1395
	{
1396
		$this->dyAddParsedObject($object);
1397
	}
1398
1399
1400
	/**
1401
	 *This is the method registered for all instanced objects should a class behavior be added after
1402
	 * the class is instanced.  Only when the class to which the behavior is being added is in this
1403
	 * object's class hierarchy, via {@link getClassHierarchy}, is the behavior added to this instance.
1404
	 * @param mixed $sender the application
1405
	 * @param TClassBehaviorEventParameter $param
1406
	 * @since 3.2.3
1407
	 */
1408
	public function fxAttachClassBehavior($sender, $param)
1409 6
	{
1410
		if ($this->isa($param->getClass())) {
1411 6
			return $this->attachBehavior($param->getName(), $param->getBehavior(), $param->getPriority());
1412 5
		}
1413
	}
1414 6
1415
1416
	/**
1417
	 *	This is the method registered for all instanced objects should a class behavior be removed after
1418 6
	 * the class is instanced.  Only when the class to which the behavior is being added is in this
1419 6
	 * object's class hierarchy, via {@link getClassHierarchy}, is the behavior removed from this instance.
1420
	 * @param mixed $sender the application
1421
	 * @param TClassBehaviorEventParameter $param
1422 6
	 * @since 3.2.3
1423
	 */
1424
	public function fxDetachClassBehavior($sender, $param)
1425 6
	{
1426 6
		if ($this->isa($param->getClass())) {
1427 6
			return $this->detachBehavior($param->getName(), $param->getPriority());
1428 6
		}
1429 6
	}
1430
1431
	/**
1432
	 * instanceBehavior is an internal method that takes a Behavior Object, a class name, or array of
1433
	 * ['class' => 'MyBehavior', 'property1' => 'Value1'...] and creates a Behavior in return. eg.
1434
	 * <code>
1435
	 *		$b = $this->instanceBehavior('MyBehavior');
1436
	 * 		$b = $this->instanceBehavior(['class' => 'MyBehavior', 'property1' => 'Value1']);
1437
	 * 		$b = $this->instanceBehavior(new MyBehavior);
1438
	 * </code>
1439 8
	 * @param array|IBaseBehavior|string $behavior string, Behavior, or array of ['class' => 'MyBehavior', 'property1' => 'Value1' ...].
1440
	 * @throws TInvalidDataTypeException if the behavior is not an {@link IBaseBehavior}
1441 8
	 * @return IBaseBehavior&TComponent an instance of $behavior or $behavior itself
1442
	 * @since 4.2.0
1443
	 */
1444
	protected static function instanceBehavior($behavior)
1445
	{
1446
		if (is_string($behavior) || (is_array($behavior) && isset($behavior['class']))) {
1447
			$behavior = Prado::createComponent($behavior);
1448
		}
1449
		if (!($behavior instanceof IBaseBehavior)) {
1450
			throw new TInvalidDataTypeException('component_not_a_behavior', get_class($behavior));
1451
		}
1452
		return $behavior;
1453
	}
1454
1455
1456
	/**
1457
	 *	This will add a class behavior to all classes instanced (that are listening) and future newly instanced objects.
1458
	 * This registers the behavior for future instances and pushes the changes to all the instances that are listening as well.
1459
	 * The universal class behaviors are stored in an inverted stack with the latest class behavior being at the first position in the array.
1460
	 * This is done so class behaviors are added last first.
1461
	 * @param string $name name the key of the class behavior
1462
	 * @param object|string $behavior class behavior or name of the object behavior per instance
1463 6
	 * @param null|array|IBaseBehavior|string $class string of class or class on which to attach this behavior.  Defaults to null which will error
1464
	 *	but more important, if this is on PHP 5.3 it will use Late Static Binding to derive the class
1465 6
	 * it should extend.
1466 6
	 * <code>
1467
	 * TPanel::attachClassBehavior('javascripts', new TJsPanelClassBehavior());
1468 6
	 * TApplication::attachClassBehavior('jpegize', 'Prado\\Util\\Behaviors\\TJPEGizeAssetBehavior', 'Prado\\Web\\TFileAsset');
1469 6
	 * </code>
1470 6
	 * An array is used to initialize values of the behavior. eg. ['class' => '\\MyBehavior', 'property' => 'value'].
1471 2
	 * @param null|numeric $priority priority of behavior, default: null the default
1472
	 *  priority of the {@link TWeakCallableCollection}  Optional.
1473
	 * @throws TInvalidOperationException if the class behavior is being added to a
1474 6
	 *  {@link TComponent}; due to recursion.
1475 6
	 * @throws TInvalidOperationException if the class behavior is already defined
1476 1
	 * @return array|object the behavior if its an IClassBehavior and an array of all
1477
	 * behaviors that have been attached from 'fxAttachClassBehavior' when the Class
1478 6
	 * Behavior being attached is a per instance IBehavior.
1479 6
	 * @since 3.2.3
1480
	 */
1481
	public static function attachClassBehavior($name, $behavior, $class = null, $priority = null)
1482
	{
1483 6
		if (!$class && function_exists('get_called_class')) {
1484
			$class = get_called_class();
1485
		}
1486
		if (!$class) {
1487
			throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
1488
		}
1489
1490
		if (!is_string($name)) {
0 ignored issues
show
introduced by
The condition is_string($name) is always true.
Loading history...
1491
			$name = get_class($name);
1492
		}
1493
		$class = strtolower($class);
1494 37
		if ($class === 'prado\\tcomponent') {
1495
			throw new TInvalidOperationException('component_no_tcomponent_class_behaviors');
1496 37
		}
1497 6
		if (is_object($behavior) && $behavior instanceof IBehavior) {
1498 4
			throw new TInvalidOperationException('component_tbehavior_cannot_attach_as_class_behavior');
1499
		}
1500 6
		if (empty(self::$_um[$class])) {
1501
			self::$_um[$class] = [];
1502
		}
1503 37
		if (isset(self::$_um[$class][$name])) {
1504
			throw new TInvalidOperationException('component_class_behavior_defined', $class, $name);
1505
		}
1506
		$behaviorObject = self::instanceBehavior($behavior);
1507
		$isClassBehavior = $behaviorObject instanceof \Prado\Util\IClassBehavior;
1508
		$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

1508
		$param = new TClassBehaviorEventParameter($class, $name, /** @scrutinizer ignore-type */ $isClassBehavior ? $behaviorObject : $behavior, $priority);
Loading history...
1509
		self::$_um[$class] = [$name => $param] + self::$_um[$class];
1510
		$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

1510
		/** @scrutinizer ignore-call */ 
1511
  $results = $behaviorObject->raiseEvent('fxAttachClassBehavior', null, $param);
Loading history...
1511
		return $isClassBehavior ? $behaviorObject : $results;
1512
	}
1513 1
1514
1515 1
	/**
1516 1
	 *	This will remove a behavior from a class.  It unregisters it from future instances and
1517 1
	 * pulls the changes from all the instances that are listening as well.
1518
	 * PHP 5.3 uses Late Static Binding to derive the static class upon which this method is called.
1519
	 * @param string $name the key of the class behavior
1520 1
	 * @param string $class class on which to attach this behavior.  Defaults to null.
1521
	 * @param null|false|numeric $priority priority: false is any priority, null is default
1522
	 *		{@link TWeakCallableCollection} priority, and numeric is a specific priority.
1523
	 * @throws TInvalidOperationException if the the class cannot be derived from Late Static Binding and is not
1524 1
	 * not supplied as a parameter.
1525
	 * @return null|array|object the behavior if its an IClassBehavior and an array of all behaviors
1526
	 * that have been detached from 'fxDetachClassBehavior' when the Class Behavior being
1527
	 * attached is a per instance IBehavior.  Null if no behavior of $name to detach.
1528
	 * @since 3.2.3
1529
	 */
1530 1
	public static function detachClassBehavior($name, $class = null, $priority = false)
1531
	{
1532 1
		if (!$class && function_exists('get_called_class')) {
1533 1
			$class = get_called_class();
1534 1
		}
1535
		if (!$class) {
1536 1
			throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
1537
		}
1538 1
1539
		$class = strtolower($class);
1540
		if (!is_string($name)) {
0 ignored issues
show
introduced by
The condition is_string($name) is always true.
Loading history...
1541
			$name = get_class($name);
1542
		}
1543
		if (empty(self::$_um[$class]) || !isset(self::$_um[$class][$name])) {
1544
			return null;
1545
		}
1546
		$param = self::$_um[$class][$name];
1547
		$behavior = $param->getBehavior();
1548
		$behaviorObject = self::instanceBehavior($behavior);
1549
		$isClassBehavior = $behaviorObject instanceof \Prado\Util\IClassBehavior;
1550
		unset(self::$_um[$class][$name]);
1551
		$results = $behaviorObject->raiseEvent('fxDetachClassBehavior', null, $param);
1552
		return $isClassBehavior ? $behaviorObject : $results;
1553
	}
1554
1555
	/**
1556
	 * Returns the named behavior object.
1557
	 * The name 'asa' stands for 'as a'.
1558
	 * @param string $behaviorname the behavior name
1559
	 * @return IBehavior the behavior object, or null if the behavior does not exist
1560
	 * @since 3.2.3
1561 27
	 */
1562
	public function asa($behaviorname)
1563 27
	{
1564 3
		return $this->_m[$behaviorname] ?? null;
1565
	}
1566 27
1567 1
	/**
1568
	 * Returns whether or not the object or any of the behaviors are of a particular class.
1569 27
	 * The name 'isa' stands for 'is a'.  This first checks if $this is an instanceof the class.
1570 26
	 * It then checks each Behavior.  If a behavior implements {@link IInstanceCheck},
1571
	 * then the behavior can determine what it is an instanceof.  If this behavior function returns true,
1572 27
	 * then this method returns true.  If the behavior instance checking function returns false,
1573 27
	 * then no further checking is performed as it is assumed to be correct.
1574
	 *
1575 27
	 * If the behavior instance check function returns nothing or null or the behavior
1576 27
	 * doesn't implement the {@link IInstanceCheck} interface, then the default instanceof occurs.
1577 27
	 * The default isa behavior is to check if the behavior is an instanceof the class.
1578 27
	 *
1579
	 * The behavior {@link IInstanceCheck} is to allow a behavior to have the host object
1580
	 * act as a completely different object.
1581
	 *
1582
	 * @param mixed|string $class class or string
1583
	 * @return bool whether or not the object or a behavior is an instance of a particular class
1584
	 * @since 3.2.3
1585
	 */
1586
	public function isa($class)
1587
	{
1588
		if ($this instanceof $class) {
1589
			return true;
1590
		}
1591
		if ($this->_m !== null && $this->_behaviorsenabled) {
1592
			foreach ($this->_m->toArray() as $behavior) {
1593
				if (($behavior instanceof IBehavior) && !$behavior->getEnabled()) {
1594
					continue;
1595
				}
1596
1597
				$check = null;
1598 18
				if (($behavior->isa('\Prado\Util\IInstanceCheck')) && $check = $behavior->isinstanceof($class, $this)) {
1599
					return true;
1600 18
				}
1601 18
				if ($check === null && ($behavior->isa($class))) {
1602 18
					return true;
1603 18
				}
1604 18
			}
1605 18
		}
1606
		return false;
1607
	}
1608
1609
	/**
1610
	 * Returns all the behaviors attached to the TComponent.  IBehavior[s] may
1611
	 * be attached but not {@link \Prado\Util\IBehavior::getEnabled Enabled}.
1612
	 *
1613
	 * @return array all the behaviors attached to the TComponent
1614
	 * @since 4.2.2
1615
	 */
1616
	public function getBehaviors()
1617
	{
1618
		return isset($this->_m) ? $this->_m->toArray() : [];
1619
	}
1620
1621 8
	/**
1622
	 * Attaches a list of behaviors to the component.
1623 8
	 * Each behavior is indexed by its name and should be an instance of
1624 8
	 * {@link IBehavior}, a string specifying the behavior class, or a
1625 8
	 * {@link TClassBehaviorEventParameter}.
1626
	 * @param array $behaviors list of behaviors to be attached to the component
1627 8
	 * @since 3.2.3
1628
	 */
1629
	public function attachBehaviors($behaviors)
1630
	{
1631
		foreach ($behaviors as $name => $behavior) {
1632
			if ($behavior instanceof TClassBehaviorEventParameter) {
1633
				$this->attachBehavior($behavior->getName(), $behavior->getBehavior(), $behavior->getPriority());
1634
			} else {
1635
				$this->attachBehavior($name, $behavior);
1636
			}
1637
		}
1638
	}
1639
1640
	/**
1641 8
	 * Detaches select behaviors from the component.
1642
	 * Each behavior is indexed by its name and should be an instance of
1643 8
	 * {@link IBehavior}, a string specifying the behavior class, or a
1644 8
	 * {@link TClassBehaviorEventParameter}.
1645 8
	 * @param array $behaviors list of behaviors to be detached from the component
1646
	 * @since 3.2.3
1647 8
	 */
1648
	public function detachBehaviors($behaviors)
1649
	{
1650
		if ($this->_m !== null) {
1651
			foreach ($behaviors as $name => $behavior) {
1652
				if ($behavior instanceof TClassBehaviorEventParameter) {
1653
					$this->detachBehavior($behavior->getName(), $behavior->getPriority());
1654
				} else {
1655
					$this->detachBehavior(is_string($behavior) ? $behavior : $name);
1656
				}
1657
			}
1658
		}
1659
	}
1660
1661
	/**
1662
	 * Detaches all behaviors from the component.
1663
	 * @since 3.2.3
1664
	 */
1665
	public function clearBehaviors()
1666
	{
1667
		if ($this->_m !== null) {
1668
			foreach ($this->_m->toArray() as $name => $behavior) {
1669
				$this->detachBehavior($name);
1670
			}
1671
			$this->_m = null;
1672
		}
1673
	}
1674
1675
	/**
1676 16
	 * Attaches a behavior to this component.
1677
	 * This method will create the behavior object based on the given
1678 16
	 * configuration. After that, the behavior object will be initialized
1679 16
	 * by calling its {@link IBehavior::attach} method.
1680 16
	 *
1681 16
	 * Already attached behaviors may implement the function:
1682 16
	 * <code>
1683
	 *	public function dyAttachBehavior($name,$behavior[, $chain]) {
1684 1
	 *  }
1685
	 * </code>
1686 2
	 * to be executed when attachBehavior is called.  All attached behaviors are notified through
1687
	 * dyAttachBehavior.
1688
	 *
1689
	 * @param string $name the behavior's name. It should uniquely identify this behavior.
1690
	 * @param array|IBaseBehavior|string $behavior the behavior configuration. This is the name of the Behavior Class
1691
	 * instanced by {@link PradoBase::createComponent}, or is a Behavior, or is an array of
1692
	 * ['class'=>'TBehavior' property1='value 1' property2='value2'...] with the class and properties
1693
	 * with values.
1694
	 * @param null|numeric $priority
1695
	 * @return IBehavior the behavior object
1696
	 * @since 3.2.3
1697
	 */
1698
	public function attachBehavior($name, $behavior, $priority = null)
1699
	{
1700
		$behavior = self::instanceBehavior($behavior);
1701
		if ($this->_m === null) {
1702
			$this->_m = new TPriorityMap();
1703
		}
1704 16
		$this->_m->add($name, $behavior, $priority);
1705
		$behavior->attach($this);
1706 16
		$this->dyAttachBehavior($name, $behavior);
1707 16
		return $behavior;
1708 16
	}
1709 16
1710 16
	/**
1711
	 * Detaches a behavior from the component.
1712 1
	 * The behavior's {@link IBehavior::detach} method will be invoked.
1713
	 *
1714 2
	 * Behaviors may implement the function:
1715
	 * <code>
1716
	 *	public function dyDetachBehavior($name,$behavior[, $chain]) {
1717
	 *  }
1718
	 * </code>
1719
	 * to be executed when detachBehavior is called.  All attached behaviors are notified through
1720
	 * dyDetachBehavior.
1721
	 *
1722 5
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
1723
	 * @param false|numeric $priority the behavior's priority. This defaults to false, aka any priority.
1724 5
	 * @return null|IBehavior the detached behavior. Null if the behavior does not exist.
1725 5
	 * @since 3.2.3
1726 5
	 */
1727 5
	public function detachBehavior($name, $priority = false)
1728 5
	{
1729
		if ($this->_m != null && ($behavior = $this->_m->itemAt($name, $priority))) {
1730
			$this->dyDetachBehavior($name, $behavior);
1731
			$behavior->detach($this);
1732
			$this->_m->remove($name, $priority);
1733
			return $behavior;
1734
		}
1735
		return null;
1736
	}
1737
1738 5
	/**
1739
	 * Enables all behaviors attached to this component independent of the behaviors
1740 5
	 *
1741 5
	 * Behaviors may implement the function:
1742
	 * <code>
1743 5
	 *	public function dyEnableBehaviors($name,$behavior[, $chain]) {
1744 5
	 *  }
1745
	 * </code>
1746 5
	 * to be executed when enableBehaviors is called.  All attached behaviors are notified through
1747 5
	 * dyEnableBehaviors.
1748
	 * @since 3.2.3
1749 5
	 */
1750 5
	public function enableBehaviors()
1751
	{
1752 5
		if (!$this->_behaviorsenabled) {
1753
			$this->_behaviorsenabled = true;
1754
			$this->dyEnableBehaviors();
1755
		}
1756
	}
1757
1758
	/**
1759
	 * Disables all behaviors attached to this component independent of the behaviors
1760
	 *
1761
	 * Behaviors may implement the function:
1762
	 * <code>
1763
	 *	public function dyDisableBehaviors($name,$behavior[, $chain]) {
1764
	 *  }
1765
	 * </code>
1766
	 * to be executed when disableBehaviors is called.  All attached behaviors are notified through
1767
	 * dyDisableBehaviors.
1768
	 * @since 3.2.3
1769
	 */
1770
	public function disableBehaviors()
1771
	{
1772
		if ($this->_behaviorsenabled) {
1773
			$this->dyDisableBehaviors();
1774
			$this->_behaviorsenabled = false;
1775
		}
1776
	}
1777
1778
1779
	/**
1780
	 * Returns if all the behaviors are turned on or off for the object.
1781
	 * @return bool whether or not all behaviors are enabled (true) or not (false)
1782
	 * @since 3.2.3
1783
	 */
1784
	public function getBehaviorsEnabled()
1785
	{
1786
		return $this->_behaviorsenabled;
1787
	}
1788
1789
	/**
1790
	 * Enables an attached object behavior.  This cannot enable or disable whole class behaviors.
1791
	 * A behavior is only effective when it is enabled.
1792
	 * A behavior is enabled when first attached.
1793
	 *
1794
	 * Behaviors may implement the function:
1795
	 * <code>
1796
	 *	public function dyEnableBehavior($name,$behavior[, $chain]) {
1797
	 *  }
1798
	 * </code>
1799
	 * to be executed when enableBehavior is called.  All attached behaviors are notified through
1800
	 * dyEnableBehavior.
1801
	 *
1802
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
1803
	 * @since 3.2.3
1804
	 */
1805
	public function enableBehavior($name)
1806
	{
1807
		if ($this->_m != null && isset($this->_m[$name])) {
1808
			if ($this->_m[$name] instanceof IBehavior) {
1809
				$this->_m[$name]->setEnabled(true);
1810
				$this->dyEnableBehavior($name, $this->_m[$name]);
1811
				return true;
1812
			}
1813
			return false;
1814
		}
1815
		return null;
1816
	}
1817
1818
	/**
1819
	 * Disables an attached behavior.  This cannot enable or disable whole class behaviors.
1820
	 * A behavior is only effective when it is enabled.
1821
	 *
1822
	 * Behaviors may implement the function:
1823
	 * <code>
1824
	 *	public function dyDisableBehavior($name,$behavior[, $chain]) {
1825
	 *  }
1826
	 * </code>
1827
	 * to be executed when disableBehavior is called.  All attached behaviors are notified through
1828
	 * dyDisableBehavior.
1829
	 *
1830
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
1831
	 * @since 3.2.3
1832
	 */
1833
	public function disableBehavior($name)
1834
	{
1835
		if ($this->_m != null && isset($this->_m[$name])) {
1836
			if ($this->_m[$name] instanceof IBehavior) {
1837
				$this->_m[$name]->setEnabled(false);
1838
				$this->dyDisableBehavior($name, $this->_m[$name]);
1839
				return true;
1840
			}
1841
			return false;
1842
		}
1843
		return null;
1844
	}
1845
1846
	/**
1847
	 * Returns an array with the names of all variables of that object that should be serialized.
1848
	 * Do not call this method. This is a PHP magic method that will be called automatically
1849
	 * prior to any serialization.
1850
	 */
1851
	public function __sleep()
1852
	{
1853
		$a = (array) $this;
1854
		$a = array_keys($a);
1855
		$exprops = [];
1856
		$this->_getZappableSleepProps($exprops);
1857
		return array_diff($a, $exprops);
1858
	}
1859
1860
	/**
1861
	 * Returns an array with the names of all variables of this object that should NOT be serialized
1862
	 * because their value is the default one or useless to be cached for the next page loads.
1863
	 * Reimplement in derived classes to add new variables, but remember to  also to call the parent
1864
	 * implementation first.
1865
	 * @param array $exprops by reference
1866
	 */
1867
	protected function _getZappableSleepProps(&$exprops)
1868
	{
1869
		if ($this->_listeningenabled === false) {
1870
			$exprops[] = "\0*\0_listeningenabled";
1871
		}
1872
		if ($this->_behaviorsenabled === true) {
1873
			$exprops[] = "\0*\0_behaviorsenabled";
1874
		}
1875
		if ($this->_e === []) {
1876
			$exprops[] = "\0*\0_e";
1877
		}
1878
		if ($this->_m === null) {
1879
			$exprops[] = "\0*\0_m";
1880
		}
1881
	}
1882
}
1883