Passed
Push — master ( 3d13ab...202a73 )
by Fabio
05:43
created

TComponent::getClassHierarchy()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 8.512

Importance

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

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

1073
				$this->getEventHandlers($name)->remove($handler, /** @scrutinizer ignore-type */ $priority);
Loading history...
1074
				return true;
1075
			} catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1076
			}
1077
		}
1078
		return false;
1079
	}
1080
1081
	/**
1082
	 * Raises an event.  This raises both inter-object 'on' events and global 'fx' events.
1083
	 * This method represents the happening of an event and will
1084
	 * invoke all attached event handlers for the event in {@link TWeakCallableCollection} order.
1085
	 * This method does not handle intra-object/behavior dynamic 'dy' events.
1086
	 *
1087
	 * There are ways to handle event responses.  By defailt {@link EVENT_RESULT_FILTER},
1088
	 * all event responses are stored in an array, filtered for null responses, and returned.
1089
	 * If {@link EVENT_RESULT_ALL} is specified, all returned results will be stored along
1090
	 * with the sender and param in an array
1091
	 * <code>
1092
	 * 		$result[] = array('sender'=>$sender,'param'=>$param,'response'=>$response);
1093
	 * </code>
1094
	 *
1095
	 * If {@link EVENT_RESULT_FEED_FORWARD} is specified, then each handler result is then
1096
	 * fed forward as the parameters for the next event.  This allows for events to filter data
1097
	 * directly by affecting the event parameters
1098
	 *
1099
	 * If a callable function is set in the response type or the post function filter is specified then the
1100
	 * result of each called event handler is post processed by the callable function.  Used in
1101
	 * combination with {@link EVENT_RESULT_FEED_FORWARD}, any event (and its result) can be chained.
1102
	 *
1103
	 * When raising a global 'fx' event, registered handlers in the global event list for
1104
	 * {@link GLOBAL_RAISE_EVENT_LISTENER} are always added into the set of event handlers.  In this way,
1105
	 * these global events are always raised for every global 'fx' event.  The registered handlers for global
1106
	 * raiseEvent events have priorities.  Any registered global raiseEvent event handlers with a priority less than zero
1107
	 * are added before the main event handlers being raised and any registered global raiseEvent event handlers
1108
	 * with a priority equal or greater than zero are added after the main event handlers being raised.  In this way
1109 182
	 * all {@link GLOBAL_RAISE_EVENT_LISTENER} handlers are always called for every raised 'fx' event.
1110
	 *
1111 182
	 * Behaviors may implement the following functions:
1112 182
	 * <code>
1113 1
	 *	public function dyPreRaiseEvent($name,$sender,$param,$responsetype,$postfunction[, $chain]) {
1114 1
	 *  	return $name; //eg, the event name may be filtered/changed
1115
	 *  }
1116
	 *	public function dyIntraRaiseEventTestHandler($handler,$sender,$param,$name[, $chain]) {
1117 182
	 *  	return true; //should this particular handler be executed?  true/false
1118 181
	 *  }
1119
	 *  public function dyIntraRaiseEventPostHandler($name,$sender,$param,$handler,$response[, $chain]) {
1120
	 *		//contains the per handler response
1121 182
	 *  }
1122 182
	 *  public function dyPostRaiseEvent($responses,$name,$sender,$param,$responsetype,$postfunction[, $chain]) {
1123
	 *		return $responses;
1124 182
	 *  }
1125
	 * </code>
1126 182
	 * to be executed when raiseEvent is called.  The 'intra' dynamic events are called per handler in
1127 60
	 * the handler loop.
1128 60
	 *
1129 60
	 * dyPreRaiseEvent has the effect of being able to change the event being raised.  This intra
1130 1
	 * object/behavior event returns the name of the desired event to be raised.  It will pass through
1131 1
	 * if no dynamic event is specified, or if the original event name is returned.
1132
	 * dyIntraRaiseEventTestHandler returns true or false as to whether a specific handler should be
1133 60
	 * called for a specific raised event (and associated event arguments)
1134 60
	 * dyIntraRaiseEventPostHandler does not return anything.  This allows behaviors to access the results
1135 59
	 * of an event handler in the per handler loop.
1136
	 * dyPostRaiseEvent returns the responses.  This allows for any post processing of the event
1137
	 * results from the sum of all event handlers
1138
	 *
1139 59
	 * When handling a catch-all {@link __dycall}, the method name is the name of the event
1140
	 * and the parameters are the sender, the param, and then the name of the event.
1141
	 *
1142
	 * @param string $name the event name
1143
	 * @param mixed $sender the event sender object
1144
	 * @param \Prado\TEventParameter $param the event parameter
1145
	 * @param null|numeric $responsetype how the results of the event are tabulated.  default: {@link EVENT_RESULT_FILTER}  The default filters out
1146
	 *		null responses. optional
1147
	 * @param null|callable $postfunction any per handler filtering of the response result needed is passed through
1148
	 *		this if not null. default: null.  optional
1149
	 * @throws TInvalidOperationException if the event is undefined
1150
	 * @throws TInvalidDataValueException If an event handler is invalid
1151
	 * @return mixed the results of the event
1152
	 */
1153
	public function raiseEvent($name, $sender, $param, $responsetype = null, $postfunction = null)
1154
	{
1155 59
		$p = $param;
0 ignored issues
show
Unused Code introduced by
The assignment to $p is dead and can be removed.
Loading history...
1156 59
		if (is_callable($responsetype)) {
1157 59
			$postfunction = $responsetype;
1158
			$responsetype = null;
1159
		}
1160 59
1161 1
		if ($responsetype === null) {
1162 1
			$responsetype = TEventResults::EVENT_RESULT_FILTER;
1163
		}
1164 59
1165 59
		$name = strtolower($name);
1166 1
		$responses = [];
1167
1168 59
		$name = $this->dyPreRaiseEvent($name, $sender, $param, $responsetype, $postfunction);
0 ignored issues
show
Bug introduced by
It seems like $postfunction can also be of type callable; however, parameter $postfunction of Prado\TComponent::dyPreRaiseEvent() does only seem to accept Prado\function|null, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

1168
		$name = $this->dyPreRaiseEvent($name, $sender, $param, /** @scrutinizer ignore-type */ $responsetype, $postfunction);
Loading history...
1169
1170
		if ($this->hasEventHandler($name) || $this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER)) {
1171 59
			$handlers = $this->getEventHandlers($name);
1172
			$handlerArray = $handlers->toArray();
1173
			if (strncasecmp($name, 'fx', 2) === 0 && $this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER)) {
1174
				$globalhandlers = $this->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER);
1175
				$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...:toArrayAbovePriority(). ( Ignorable by Annotation )

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

1175
				$handlerArray = array_merge($globalhandlers->toArrayBelowPriority(0), $handlerArray, $globalhandlers->toArrayAbovePriority(/** @scrutinizer ignore-type */ 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...:toArrayBelowPriority(). ( Ignorable by Annotation )

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

1175
				$handlerArray = array_merge($globalhandlers->toArrayBelowPriority(/** @scrutinizer ignore-type */ 0), $handlerArray, $globalhandlers->toArrayAbovePriority(0));
Loading history...
1176
			}
1177
			$response = null;
1178 59
			foreach ($handlerArray as $handler) {
1179
				if ($this->dyIntraRaiseEventTestHandler($handler, $sender, $param, $name) === false) {
1180 59
					continue;
1181 1
				}
1182
1183
				if (is_string($handler)) {
1184 59
					if (($pos = strrpos($handler, '.')) !== false) {
1185 2
						$object = $this->getSubProperty(substr($handler, 0, $pos));
1186
						$method = substr($handler, $pos + 1);
1187 58
						if (method_exists($object, $method) || strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1188
							if ($method == '__dycall') {
1189
								$response = $object->__dycall($name, [$sender, $param, $name]);
1190 59
							} else {
1191 60
								$response = $object->$method($sender, $param, $name);
1192
							}
1193
						} else {
1194 173
							throw new TInvalidDataValueException('component_eventhandler_invalid', get_class($this), $name, $handler);
1195 1
						}
1196
					} else {
1197
						$response = call_user_func($handler, $sender, $param, $name);
1198 182
					}
1199 181
				} elseif (is_callable($handler, true)) {
1200
					[$object, $method] = $handler;
1201
					if (is_string($object)) {
1202 182
						$response = call_user_func($handler, $sender, $param, $name);
1203
					} else {
1204 182
						if (($pos = strrpos($method, '.')) !== false) {
1205
							$object = $object->getSubProperty(substr($method, 0, $pos));
1206
							$method = substr($method, $pos + 1);
1207
						}
1208
						if (method_exists($object, $method) || strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1209
							if ($method == '__dycall') {
1210
								$response = $object->__dycall($name, [$sender, $param, $name]);
1211
							} else {
1212
								$response = $object->$method($sender, $param, $name);
1213
							}
1214
						} else {
1215
							throw new TInvalidDataValueException('component_eventhandler_invalid', get_class($this), $name, $handler[1]);
1216
						}
1217
					}
1218
				} else {
1219
					throw new TInvalidDataValueException('component_eventhandler_invalid', get_class($this), $name, gettype($handler));
1220
				}
1221
1222
				$this->dyIntraRaiseEventPostHandler($name, $sender, $param, $handler, $response);
1223
1224 2
				if ($postfunction) {
1225
					$response = call_user_func_array($postfunction, [$sender, $param, $this, $response]);
1226 2
				}
1227
1228 2
				if ($responsetype & TEventResults::EVENT_RESULT_ALL) {
1229
					$responses[] = ['sender' => $sender, 'param' => $param, 'response' => $response];
1230
				} else {
1231 2
					$responses[] = $response;
1232 1
				}
1233 1
1234
				if ($response !== null && ($responsetype & TEventResults::EVENT_RESULT_FEED_FORWARD)) {
1235
					$param = $response;
1236
				}
1237
			}
1238
		} elseif (strncasecmp($name, 'on', 2) === 0 && !$this->hasEvent($name)) {
1239
			throw new TInvalidOperationException('component_event_undefined', get_class($this), $name);
1240
		}
1241
1242
		if ($responsetype & TEventResults::EVENT_RESULT_FILTER) {
1243
			$responses = array_filter($responses);
1244
		}
1245
1246
		$responses = $this->dyPostRaiseEvent($responses, $name, $sender, $param, $responsetype, $postfunction);
0 ignored issues
show
Bug introduced by
It seems like $postfunction can also be of type callable; however, parameter $postfunction of Prado\TComponent::dyPostRaiseEvent() does only seem to accept Prado\function|null, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

1246
		$responses = $this->dyPostRaiseEvent($responses, $name, $sender, $param, /** @scrutinizer ignore-type */ $responsetype, $postfunction);
Loading history...
1247
1248
		return $responses;
1249
	}
1250
1251
	/**
1252
	 * Evaluates a PHP expression in the context of this control.
1253
	 *
1254 2
	 * Behaviors may implement the function:
1255
	 * <code>
1256 2
	 *	public function dyEvaluateExpressionFilter($expression, $chain) {
1257
	 * 		return $chain->dyEvaluateExpressionFilter(str_replace('foo', 'bar', $expression)); //example
1258 2
	 * }
1259 2
	 * </code>
1260
	 * to be executed when evaluateExpression is called.  All attached behaviors are notified through
1261
	 * dyEvaluateExpressionFilter.  The chaining is important in this function due to the filtering
1262 2
	 * pass-through effect.
1263 2
	 *
1264 2
	 * @param string $expression PHP expression
1265 1
	 * @throws TInvalidOperationException if the expression is invalid
1266 1
	 * @return mixed the expression result
1267
	 */
1268
	public function evaluateExpression($expression)
1269
	{
1270
		$expression = $this->dyEvaluateExpressionFilter($expression);
1271
		try {
1272
			return eval("return $expression;");
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1273
		} 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...
1274
			throw new TInvalidOperationException('component_expression_invalid', get_class($this), $expression, $e->getMessage());
1275
		}
1276
	}
1277
1278
	/**
1279
	 * Evaluates a list of PHP statements.
1280
	 *
1281
	 * Behaviors may implement the function:
1282
	 * <code>
1283
	 *	public function dyEvaluateStatementsFilter($statements, $chain) {
1284
	 * 		return $chain->dyEvaluateStatementsFilter(str_replace('foo', 'bar', $statements)); //example
1285
	 * }
1286
	 * </code>
1287
	 * to be executed when evaluateStatements is called.  All attached behaviors are notified through
1288
	 * dyEvaluateStatementsFilter.  The chaining is important in this function due to the filtering
1289 1
	 * pass-through effect.
1290
	 *
1291 1
	 * @param string $statements PHP statements
1292 1
	 * @throws TInvalidOperationException if the statements are invalid
1293 1
	 * @return string content echoed or printed by the PHP statements
1294
	 */
1295
	public function evaluateStatements($statements)
1296
	{
1297
		$statements = $this->dyEvaluateStatementsFilter($statements);
1298
		try {
1299
			ob_start();
1300
			if (eval($statements) === false) {
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1301
				throw new \Exception('');
1302
			}
1303
			$content = ob_get_contents();
1304
			ob_end_clean();
1305
			return $content;
1306
		} catch (\Exception $e) {
1307
			throw new TInvalidOperationException('component_statements_invalid', get_class($this), $statements, $e->getMessage());
1308
		}
1309
	}
1310
1311
	/**
1312 1
	 * This method is invoked after the component is instantiated by a template.
1313
	 * When this method is invoked, the component's properties have been initialized.
1314 1
	 * The default implementation of this method will invoke
1315 1
	 * the potential parent component's {@link addParsedObject}.
1316
	 * This method can be overridden.
1317
	 *
1318
	 * Behaviors may implement the function:
1319
	 * <code>
1320
	 *	public function dyCreatedOnTemplate($parent, $chain) {
1321
	 * 		return $chain->dyCreatedOnTemplate($parent); //example
1322
	 *  }
1323
	 * </code>
1324
	 * to be executed when createdOnTemplate is called.  All attached behaviors are notified through
1325
	 * dyCreatedOnTemplate.
1326 6
	 *
1327
	 * @param \Prado\TComponent $parent potential parent of this control
1328 6
	 * @see addParsedObject
1329 6
	 */
1330
	public function createdOnTemplate($parent)
1331 1
	{
1332
		$parent = $this->dyCreatedOnTemplate($parent);
1333
		$parent->addParsedObject($this);
1334
	}
1335
1336
	/**
1337
	 * Processes an object that is created during parsing template.
1338
	 * The object can be either a component or a static text string.
1339
	 * This method can be overridden to customize the handling of newly created objects in template.
1340
	 * Only framework developers and control developers should use this method.
1341
	 *
1342 6
	 * Behaviors may implement the function:
1343
	 * <code>
1344 6
	 *	public function dyAddParsedObject($object[, $chain]) {
1345 6
	 *  }
1346
	 * </code>
1347 1
	 * to be executed when addParsedObject is called.  All attached behaviors are notified through
1348
	 * dyAddParsedObject.
1349
	 *
1350
	 * @param \Prado\TComponent|string $object text string or component parsed and instantiated in template
1351
	 * @see createdOnTemplate
1352
	 */
1353
	public function addParsedObject($object)
1354
	{
1355
		$this->dyAddParsedObject($object);
1356
	}
1357
1358
1359
	/**
1360
	 *This is the method registered for all instanced objects should a class behavior be added after
1361
	 * the class is instanced.  Only when the class to which the behavior is being added is in this
1362
	 * object's class hierarchy, via {@link getClassHierarchy}, is the behavior added to this instance.
1363
	 * @param mixed $sender the application
1364
	 * @param TClassBehaviorEventParameter $param
1365
	 * @since 3.2.3
1366
	 */
1367
	public function fxAttachClassBehavior($sender, $param)
1368 6
	{
1369
		if (in_array($param->getClass(), $this->getClassHierarchy(true))) {
1370 6
			return $this->attachBehavior($param->getName(), $param->getBehavior(), $param->getPriority());
1371 5
		}
1372
	}
1373 6
1374
1375
	/**
1376
	 *	This is the method registered for all instanced objects should a class behavior be removed after
1377 6
	 * the class is instanced.  Only when the class to which the behavior is being added is in this
1378
	 * object's class hierarchy, via {@link getClassHierarchy}, is the behavior removed from this instance.
1379
	 * @param mixed $sender the application
1380 6
	 * @param TClassBehaviorEventParameter $param
1381 6
	 * @since 3.2.3
1382 1
	 */
1383
	public function fxDetachClassBehavior($sender, $param)
1384 6
	{
1385 6
		if (in_array($param->getClass(), $this->getClassHierarchy(true))) {
1386
			return $this->detachBehavior($param->getName(), $param->getPriority());
1387 6
		}
1388 1
	}
1389
	
1390 6
	/**
1391 6
	 * instanceBehavior is an internal method that takes a Behavior Object, a class name, or array of
1392 6
	 * ['class' => 'MyBehavior', 'property1' => 'Value1'...] and creates a Behavior in return. eg.
1393 6
	 * <code>
1394
	 *		$b = $this->instanceBehavior('MyBehavior');
1395
	 * 		$b = $this->instanceBehavior(['class' => 'MyBehavior', 'property1' => 'Value1']);
1396
	 * 		$b = $this->instanceBehavior(new MyBehavior);
1397
	 * </code>
1398
	 * @param array|class|string $behavior string, Behavior, or array of ['class' => 'MyBehavior', 'property1' => 'Value1' ...].
0 ignored issues
show
Bug introduced by
The type Prado\class 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...
1399
	 * @throws TInvalidDataTypeException if the behavior is not an {@link IBaseBehavior}
1400
	 * @return {@link IBaseBehavior} an instance of $behavior or $behavior itself
0 ignored issues
show
Documentation Bug introduced by
The doc comment {@link at position 0 could not be parsed: Unknown type name '{' at position 0 in {@link.
Loading history...
1401
	 * @since 4.2.0
1402
	 */
1403
	protected static function instanceBehavior($behavior)
1404
	{
1405
		if (is_string($behavior)) {
1406
			$behavior = Prado::createComponent($behavior);
1407
		} elseif (is_array($behavior) && isset($behavior['class'])) {
1408
			$b = Prado::createComponent($behavior['class']);
1409 6
			unset($behavior['class']);
1410
			foreach ($behavior as $property => $value) {
1411 6
				$b->setSubProperty($property, $value);
1412 5
			}
1413
			$behavior = $b;
1414 6
		}
1415
		if (!($behavior instanceof IBaseBehavior)) {
1416
			throw new TInvalidDataTypeException('component_not_a_behavior', get_class($behavior));
1417
		}
1418 6
		return $behavior;
1419 6
	}
1420
1421
1422 6
	/**
1423
	 *	This will add a class behavior to all classes instanced (that are listening) and future newly instanced objects.
1424
	 * This registers the behavior for future instances and pushes the changes to all the instances that are listening as well.
1425 6
	 * The universal class behaviors are stored in an inverted stack with the latest class behavior being at the first position in the array.
1426 6
	 * This is done so class behaviors are added last first.
1427 6
	 * @param string $name name the key of the class behavior
1428 6
	 * @param object|string $behavior class behavior or name of the object behavior per instance
1429 6
	 * @param null|class|string $class string of class or class on which to attach this behavior.  Defaults to null which will error
1430
	 *	but more important, if this is on PHP 5.3 it will use Late Static Binding to derive the class
1431
	 * it should extend.
1432
	 * <code>
1433
	 * TPanel::attachClassBehavior('javascripts', (new TJsPanelBehavior())->init($this));
1434
	 * </code>
1435
	 * @param null|numeric $priority priority of behavior, default: null the default priority of the {@link TWeakCallableCollection}  Optional.
1436
	 * @throws TInvalidOperationException if the class behavior is being added to a {@link TComponent}; due to recursion.
1437
	 * @throws TInvalidOperationException if the class behavior is already defined
1438
	 * @since 3.2.3
1439 8
	 */
1440
	public static function attachClassBehavior($name, $behavior, $class = null, $priority = null)
1441 8
	{
1442
		if (!$class && function_exists('get_called_class')) {
1443
			$class = get_called_class();
1444
		}
1445
		if (!$class) {
1446
			throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
1447
		}
1448
1449
		if (!is_string($name)) {
0 ignored issues
show
introduced by
The condition is_string($name) is always true.
Loading history...
1450
			$name = get_class($name);
1451
		}
1452
		$class = strtolower($class);
1453
		if ($class === 'tcomponent') {
1454
			throw new TInvalidOperationException('component_no_tcomponent_class_behaviors');
1455
		}
1456
		if (empty(self::$_um[$class])) {
1457
			self::$_um[$class] = [];
1458
		}
1459
		if (isset(self::$_um[$class][$name])) {
1460
			throw new TInvalidOperationException('component_class_behavior_defined', $class, $name);
1461
		}
1462
		$behaviorObject = self::instanceBehavior($behavior);
1463 6
		$param = new TClassBehaviorEventParameter($class, $name, $behavior, $priority);
0 ignored issues
show
Bug introduced by
It seems like $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

1463
		$param = new TClassBehaviorEventParameter($class, $name, /** @scrutinizer ignore-type */ $behavior, $priority);
Loading history...
1464
		self::$_um[$class] = [$name => $param] + self::$_um[$class];
1465 6
		return $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

1465
		return $behaviorObject->/** @scrutinizer ignore-call */ raiseEvent('fxAttachClassBehavior', null, $param);
Loading history...
1466 6
	}
1467
1468 6
1469 6
	/**
1470 6
	 *	This will remove a behavior from a class.  It unregisters it from future instances and
1471 2
	 * pulls the changes from all the instances that are listening as well.
1472
	 * PHP 5.3 uses Late Static Binding to derive the static class upon which this method is called.
1473
	 * @param string $name the key of the class behavior
1474 6
	 * @param string $class class on which to attach this behavior.  Defaults to null.
1475 6
	 * @param null|false|numeric $priority priority: false is any priority, null is default
1476 1
	 *		{@link TWeakCallableCollection} priority, and numeric is a specific priority.
1477
	 * @throws TInvalidOperationException if the the class cannot be derived from Late Static Binding and is not
1478 6
	 * not supplied as a parameter.
1479 6
	 * @since 3.2.3
1480
	 */
1481
	public static function detachClassBehavior($name, $class = null, $priority = false)
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
		$class = strtolower($class);
1491
		if (!is_string($name)) {
0 ignored issues
show
introduced by
The condition is_string($name) is always true.
Loading history...
1492
			$name = get_class($name);
1493
		}
1494 37
		if (empty(self::$_um[$class]) || !isset(self::$_um[$class][$name])) {
1495
			return false;
1496 37
		}
1497 6
		$param = self::$_um[$class][$name];
1498 4
		$behavior = $param->getBehavior();
1499
		$behaviorObject = self::instanceBehavior($behavior);
1500 6
		unset(self::$_um[$class][$name]);
1501
		return $behaviorObject->raiseEvent('fxDetachClassBehavior', null, $param);
1502
	}
1503 37
1504
	/**
1505
	 * Returns the named behavior object.
1506
	 * The name 'asa' stands for 'as a'.
1507
	 * @param string $behaviorname the behavior name
1508
	 * @return IBehavior the behavior object, or null if the behavior does not exist
1509
	 * @since 3.2.3
1510
	 */
1511
	public function asa($behaviorname)
1512
	{
1513 1
		return $this->_m[$behaviorname] ?? null;
1514
	}
1515 1
1516 1
	/**
1517 1
	 * Returns whether or not the object or any of the behaviors are of a particular class.
1518
	 * The name 'isa' stands for 'is a'.  This first checks if $this is an instanceof the class.
1519
	 * It then checks each Behavior.  If a behavior implements {@link IInstanceCheck},
1520 1
	 * then the behavior can determine what it is an instanceof.  If this behavior function returns true,
1521
	 * then this method returns true.  If the behavior instance checking function returns false,
1522
	 * then no further checking is performed as it is assumed to be correct.
1523
	 *
1524 1
	 * If the behavior instance check function returns nothing or null or the behavior
1525
	 * doesn't implement the {@link IInstanceCheck} interface, then the default instanceof occurs.
1526
	 * The default isa behavior is to check if the behavior is an instanceof the class.
1527
	 *
1528
	 * The behavior {@link IInstanceCheck} is to allow a behavior to have the host object
1529
	 * act as a completely different object.
1530 1
	 *
1531
	 * @param mixed|string $class class or string
1532 1
	 * @return bool whether or not the object or a behavior is an instance of a particular class
1533 1
	 * @since 3.2.3
1534 1
	 */
1535
	public function isa($class)
1536 1
	{
1537
		if ($this instanceof $class) {
1538 1
			return true;
1539
		}
1540
		if ($this->_m !== null && $this->_behaviorsenabled) {
1541
			foreach ($this->_m->toArray() as $behavior) {
1542
				if (($behavior instanceof IBehavior) && !$behavior->getEnabled()) {
1543
					continue;
1544
				}
1545
1546
				$check = null;
1547
				if (($behavior->isa('\Prado\Util\IInstanceCheck')) && $check = $behavior->isinstanceof($class, $this)) {
1548
					return true;
1549
				}
1550
				if ($check === null && ($behavior->isa($class))) {
1551
					return true;
1552
				}
1553
			}
1554
		}
1555
		return false;
1556
	}
1557
1558
	/**
1559
	 * Attaches a list of behaviors to the component.
1560
	 * Each behavior is indexed by its name and should be an instance of
1561 27
	 * {@link IBehavior}, a string specifying the behavior class, or a
1562
	 * {@link TClassBehaviorEventParameter}.
1563 27
	 * @param array $behaviors list of behaviors to be attached to the component
1564 3
	 * @since 3.2.3
1565
	 */
1566 27
	public function attachBehaviors($behaviors)
1567 1
	{
1568
		foreach ($behaviors as $name => $behavior) {
1569 27
			if ($behavior instanceof TClassBehaviorEventParameter) {
1570 26
				$this->attachBehavior($behavior->getName(), $behavior->getBehavior(), $behavior->getPriority());
1571
			} else {
1572 27
				$this->attachBehavior($name, $behavior);
1573 27
			}
1574
		}
1575 27
	}
1576 27
1577 27
	/**
1578 27
	 * Detaches select behaviors from the component.
1579
	 * Each behavior is indexed by its name and should be an instance of
1580
	 * {@link IBehavior}, a string specifying the behavior class, or a
1581
	 * {@link TClassBehaviorEventParameter}.
1582
	 * @param array $behaviors list of behaviors to be detached from the component
1583
	 * @since 3.2.3
1584
	 */
1585
	public function detachBehaviors($behaviors)
1586
	{
1587
		if ($this->_m !== null) {
1588
			foreach ($behaviors as $name => $behavior) {
1589
				if ($behavior instanceof TClassBehaviorEventParameter) {
1590
					$this->detachBehavior($behavior->getName(), $behavior->getPriority());
1591
				} else {
1592
					$this->detachBehavior(is_string($behavior) ? $behavior : $name);
1593
				}
1594
			}
1595
		}
1596
	}
1597
1598 18
	/**
1599
	 * Detaches all behaviors from the component.
1600 18
	 * @since 3.2.3
1601 18
	 */
1602 18
	public function clearBehaviors()
1603 18
	{
1604 18
		if ($this->_m !== null) {
1605 18
			foreach ($this->_m->toArray() as $name => $behavior) {
1606
				$this->detachBehavior($name);
1607
			}
1608
			$this->_m = null;
1609
		}
1610
	}
1611
1612
	/**
1613
	 * Attaches a behavior to this component.
1614
	 * This method will create the behavior object based on the given
1615
	 * configuration. After that, the behavior object will be initialized
1616
	 * by calling its {@link IBehavior::attach} method.
1617
	 *
1618
	 * Already attached behaviors may implement the function:
1619
	 * <code>
1620
	 *	public function dyAttachBehavior($name,$behavior[, $chain]) {
1621 8
	 *  }
1622
	 * </code>
1623 8
	 * to be executed when attachBehavior is called.  All attached behaviors are notified through
1624 8
	 * dyAttachBehavior.
1625 8
	 *
1626
	 * @param string $name the behavior's name. It should uniquely identify this behavior.
1627 8
	 * @param array|class|string $behavior the behavior configuration. This is the name of the Behavior Class
1628
	 * instanced by {@link PradoBase::createComponent}, or is a Behavior, or is an array of
1629
	 * ['class'=>'TBehavior' property1='value 1' property2='value2'...] with the class and properties
1630
	 * with values.
1631
	 * @param null|numeric $priority
1632
	 * @return IBehavior the behavior object
1633
	 * @since 3.2.3
1634
	 */
1635
	public function attachBehavior($name, $behavior, $priority = null)
1636
	{
1637
		$behavior = self::instanceBehavior($behavior);
1638
		if ($behavior instanceof IBehavior) {
1639
			$behavior->setEnabled(true);
1640
		}
1641 8
		if ($this->_m === null) {
1642
			$this->_m = new TPriorityMap;
1643 8
		}
1644 8
		$behavior->attach($this);
1645 8
		$this->dyAttachBehavior($name, $behavior);
1646
		$this->_m->add($name, $behavior, $priority);
1647 8
		return $behavior;
1648
	}
1649
1650
	/**
1651
	 * Detaches a behavior from the component.
1652
	 * The behavior's {@link IBehavior::detach} method will be invoked.
1653
	 *
1654
	 * Behaviors may implement the function:
1655
	 * <code>
1656
	 *	public function dyDetachBehavior($name,$behavior[, $chain]) {
1657
	 *  }
1658
	 * </code>
1659
	 * to be executed when detachBehavior is called.  All attached behaviors are notified through
1660
	 * dyDetachBehavior.
1661
	 *
1662
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
1663
	 * @param false|numeric $priority the behavior's priority. This defaults to false, aka any priority.
1664
	 * @return null|IBehavior the detached behavior. Null if the behavior does not exist.
1665
	 * @since 3.2.3
1666
	 */
1667
	public function detachBehavior($name, $priority = false)
1668
	{
1669
		if ($this->_m != null && isset($this->_m[$name])) {
1670
			$this->_m[$name]->detach($this);
0 ignored issues
show
Bug introduced by
The method detach() does not exist on null. ( Ignorable by Annotation )

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

1670
			$this->_m[$name]->/** @scrutinizer ignore-call */ 
1671
                     detach($this);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1671
			$behavior = $this->_m->itemAt($name);
1672
			$this->_m->remove($name, $priority);
1673
			$this->dyDetachBehavior($name, $behavior);
1674
			return $behavior;
1675
		}
1676 16
		return null;
1677
	}
1678 16
1679 16
	/**
1680 16
	 * Enables all behaviors attached to this component independent of the behaviors
1681 16
	 *
1682 16
	 * Behaviors may implement the function:
1683
	 * <code>
1684 1
	 *	public function dyEnableBehaviors($name,$behavior[, $chain]) {
1685
	 *  }
1686 2
	 * </code>
1687
	 * to be executed when enableBehaviors is called.  All attached behaviors are notified through
1688
	 * dyEnableBehaviors.
1689
	 * @since 3.2.3
1690
	 */
1691
	public function enableBehaviors()
1692
	{
1693
		if (!$this->_behaviorsenabled) {
1694
			$this->_behaviorsenabled = true;
1695
			$this->dyEnableBehaviors();
1696
		}
1697
	}
1698
1699
	/**
1700
	 * Disables all behaviors attached to this component independent of the behaviors
1701
	 *
1702
	 * Behaviors may implement the function:
1703
	 * <code>
1704 16
	 *	public function dyDisableBehaviors($name,$behavior[, $chain]) {
1705
	 *  }
1706 16
	 * </code>
1707 16
	 * to be executed when disableBehaviors is called.  All attached behaviors are notified through
1708 16
	 * dyDisableBehaviors.
1709 16
	 * @since 3.2.3
1710 16
	 */
1711
	public function disableBehaviors()
1712 1
	{
1713
		if ($this->_behaviorsenabled) {
1714 2
			$this->dyDisableBehaviors();
1715
			$this->_behaviorsenabled = false;
1716
		}
1717
	}
1718
1719
1720
	/**
1721
	 * Returns if all the behaviors are turned on or off for the object.
1722 5
	 * @return bool whether or not all behaviors are enabled (true) or not (false)
1723
	 * @since 3.2.3
1724 5
	 */
1725 5
	public function getBehaviorsEnabled()
1726 5
	{
1727 5
		return $this->_behaviorsenabled;
1728 5
	}
1729
1730
	/**
1731
	 * Enables an attached object behavior.  This cannot enable or disable whole class behaviors.
1732
	 * A behavior is only effective when it is enabled.
1733
	 * A behavior is enabled when first attached.
1734
	 *
1735
	 * Behaviors may implement the function:
1736
	 * <code>
1737
	 *	public function dyEnableBehavior($name,$behavior[, $chain]) {
1738 5
	 *  }
1739
	 * </code>
1740 5
	 * to be executed when enableBehavior is called.  All attached behaviors are notified through
1741 5
	 * dyEnableBehavior.
1742
	 *
1743 5
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
1744 5
	 * @since 3.2.3
1745
	 */
1746 5
	public function enableBehavior($name)
1747 5
	{
1748
		if ($this->_m != null && isset($this->_m[$name])) {
1749 5
			if ($this->_m[$name] instanceof IBehavior) {
1750 5
				$this->_m[$name]->setEnabled(true);
1751
				$this->dyEnableBehavior($name, $this->_m[$name]);
1752 5
				return true;
1753
			}
1754
			return false;
1755
		}
1756
		return null;
1757
	}
1758
1759
	/**
1760
	 * Disables an attached behavior.  This cannot enable or disable whole class behaviors.
1761
	 * A behavior is only effective when it is enabled.
1762
	 *
1763
	 * Behaviors may implement the function:
1764
	 * <code>
1765
	 *	public function dyDisableBehavior($name,$behavior[, $chain]) {
1766
	 *  }
1767
	 * </code>
1768
	 * to be executed when disableBehavior is called.  All attached behaviors are notified through
1769
	 * dyDisableBehavior.
1770
	 *
1771
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
1772
	 * @since 3.2.3
1773
	 */
1774
	public function disableBehavior($name)
1775
	{
1776
		if ($this->_m != null && isset($this->_m[$name])) {
1777
			if ($this->_m[$name] instanceof IBehavior) {
1778
				$this->_m[$name]->setEnabled(false);
1779
				$this->dyDisableBehavior($name, $this->_m[$name]);
1780
				return true;
1781
			}
1782
			return false;
1783
		}
1784
		return null;
1785
	}
1786
1787
	/**
1788
	 * Returns an array with the names of all variables of that object that should be serialized.
1789
	 * Do not call this method. This is a PHP magic method that will be called automatically
1790
	 * prior to any serialization.
1791
	 */
1792
	public function __sleep()
1793
	{
1794
		$a = (array) $this;
1795
		$a = array_keys($a);
1796
		$exprops = [];
1797
		$this->_getZappableSleepProps($exprops);
1798
		return array_diff($a, $exprops);
1799
	}
1800
1801
	/**
1802
	 * Returns an array with the names of all variables of this object that should NOT be serialized
1803
	 * because their value is the default one or useless to be cached for the next page loads.
1804
	 * Reimplement in derived classes to add new variables, but remember to  also to call the parent
1805
	 * implementation first.
1806
	 * @param array $exprops by reference
1807
	 */
1808
	protected function _getZappableSleepProps(&$exprops)
1809
	{
1810
		if ($this->_listeningenabled === false) {
1811
			$exprops[] = "\0*\0_listeningenabled";
1812
		}
1813
		if ($this->_behaviorsenabled === true) {
1814
			$exprops[] = "\0*\0_behaviorsenabled";
1815
		}
1816
		if ($this->_e === []) {
1817
			$exprops[] = "\0*\0_e";
1818
		}
1819
		if ($this->_m === null) {
1820
			$exprops[] = "\0*\0_m";
1821
		}
1822
	}
1823
}
1824