Passed
Push — master ( 91641e...97f11f )
by Fabio
07:24
created

TComponent::__callStatic()   C

Complexity

Conditions 17
Paths 15

Size

Total Lines 38
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 17

Importance

Changes 0
Metric Value
cc 17
eloc 23
nc 15
nop 2
dl 0
loc 38
ccs 14
cts 14
cp 1
crap 17
rs 5.2166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * TComponent, TPropertyValue classes
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 *
7
 * Global Events, intra-object events, Class behaviors, expanded behaviors
8
 * @author Brad Anderson <[email protected]>
9
 *
10
 * @author Qiang Xue <[email protected]>
11
 * @link https://github.com/pradosoft/prado
12
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
13
 */
14
15
namespace Prado;
16
17
use Prado\Collections\TPriorityMap;
18
use Prado\Collections\TWeakCallableCollection;
19
use Prado\Exceptions\TApplicationException;
20
use Prado\Exceptions\TInvalidDataTypeException;
21
use Prado\Exceptions\TInvalidDataValueException;
22
use Prado\Exceptions\TInvalidOperationException;
23
use Prado\Exceptions\TUnknownMethodException;
24
use Prado\Util\IBaseBehavior;
25
use Prado\Util\IBehavior;
26
use Prado\Util\TCallChain;
27
use Prado\Util\IClassBehavior;
28
use Prado\Util\IDynamicMethods;
29
use Prado\Util\TClassBehaviorEventParameter;
30
use Prado\Web\Javascripts\TJavaScriptLiteral;
31
use Prado\Web\Javascripts\TJavaScriptString;
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
 * events, dynamic events, and global events.
39
 *
40
 * Properties
41
 *
42
 * A property is defined by a getter method, and/or a setter method.
43
 * Properties can be accessed in the way like accessing normal object members.
44
 * Reading or writing a property will cause the invocation of the corresponding
45
 * getter or setter method, e.g.,
46
 * ```php
47
 * $a=$this->Text;     // equivalent to $a=$this->getText();
48
 * $this->Text='abc';  // equivalent to $this->setText('abc');
49
 * ```
50
 * The signatures of getter and setter methods are as follows,
51
 * ```php
52
 * // getter, defines a readable property 'Text'
53
 * function getText() { ... }
54
 * // setter, defines a writable property 'Text', with $value being the value to be set to the property
55
 * function setText($value) { ... }
56
 * ```
57
 * Property names are case-insensitive. It is recommended that they are written
58
 * in the format of concatenated words, with the first letter of each word
59
 * capitalized (e.g. DisplayMode, ItemStyle).
60
 *
61
 *
62
 * Javascript Get and Set Properties
63
 *
64
 * Since Prado 3.2 a new class of javascript-friendly properties have been introduced
65
 * to better deal with potential security problems like cross-site scripting issues.
66
 * All the data that gets sent clientside inside a javascript block is now encoded by default.
67
 * Sometimes there's the need to bypass this encoding and be able to send raw javascript code.
68
 * This new class of javascript-friendly properties are identified by their name
69
 * starting with 'js' (case insensitive):
70
 * ```php
71
 * // getter, defines a readable property 'Text'
72
 * function getJsText() { ... }
73
 * // setter, defines a writable property 'Text', with $value being the value to be set to the property
74
 * function setJsText(TJavaScriptLiteral $value) { ... }
75
 * ```
76
 * Js-friendly properties can be accessed using both their Js-less name and their Js-enabled name:
77
 * ```php
78
 * // set some simple text as property value
79
 * $component->Text = 'text';
80
 * // set some javascript code as property value
81
 * $component->JsText = 'raw javascript';
82
 * ```
83
 * In the first case, the property value will automatically gets encoded when sent
84
 * clientside inside a javascript block.
85
 * In the second case, the property will be 'marked' as being a safe javascript
86
 * statement and will not be encoded when rendered inside a javascript block.
87
 * This special handling makes use of the {@see \Prado\Web\Javascripts\TJavaScriptLiteral} class.
88
 *
89
 *
90
 * Object Events
91
 *
92
 * An event is defined by the presence of a method whose name starts with 'on'.
93
 * The event name is the method name and is thus case-insensitive.
94
 * An event can be attached with one or several methods (called event handlers).
95
 * An event can be raised by calling {@see raiseEvent} method, upon which
96
 * the attached event handlers will be invoked automatically in the order they
97
 * are attached to the event. Event handlers must have the following signature,
98
 * ```php
99
 * function eventHandlerFuncName($sender, $param) { ... }
100
 * ```
101
 * where $sender refers to the object who is responsible for the raising of the event,
102
 * and $param refers to a structure that may contain event-specific information.
103
 * To raise an event (assuming named as 'Click') of a component, use
104
 * ```php
105
 * $component->raiseEvent('OnClick');
106
 * $component->raiseEvent('OnClick', $this, $param);
107
 * ```
108
 * To attach an event handler to an event, use one of the following ways,
109
 * ```php
110
 * $component->OnClick = $callback;
111
 * $component->OnClick->add($callback);
112
 * $component->attachEventHandler('OnClick', $callback);
113
 * ```
114
 * The first two ways make use of the fact that $component->OnClick refers to
115
 * the event handler list {@see \Prado\Collections\TWeakCallableCollection} for the 'OnClick' event.
116
 * The variable $callback contains the definition of the event handler that can
117
 * be either:
118
 *
119
 * a string referring to a global function name
120
 * ```php
121
 * $component->OnClick = 'buttonClicked';
122
 * // will cause the following function to be called
123
 * buttonClicked($sender, $param);
124
 * ```
125
 *
126
 * All types of PHP Callables are supported, such as:
127
 *  - Simple Callback function string, eg. 'my_callback_function'
128
 *  - Static class method call, eg. ['MyClass', 'myCallbackMethod'] and 'MyClass::myCallbackMethod'
129
 *  - Object method call, eg. [$object, 'myCallbackMethod']
130
 *  - Objects implementing __invoke
131
 *  - Closure / anonymous functions
132
 *
133
 * PRADO can accept method names in PRADO namespace as well.
134
 * ```php
135
 * $component->OnClick = [$object, 'buttonClicked'];
136
 * // will cause the following function to be called
137
 * $object->buttonClicked($sender, param);
138
 *
139
 * // the method can also be expressed using the PRADO namespace format
140
 * $component->OnClick = [$object, 'MainContent.SubmitButton.buttonClicked'];
141
 * // will cause the following function to be called
142
 * $object->MainContent->SubmitButton->buttonClicked($sender, $param);
143
 *
144
 * // Closure as an event handler
145
 * $component->OnClick = function ($sender, $param) { ... };
146
 * ```
147
 *
148
 *
149
 * Global and Dynamic Events
150
 *
151
 * With the addition of behaviors, a more expansive event model is needed.  There
152
 * are two new event types (global and dynamic events) as well as a more comprehensive
153
 * behavior model that includes class wide behaviors.
154
 *
155
 * A global event is defined by all events whose name starts with 'fx'.
156
 * The event name is potentially a method name and is thus case-insensitive. All 'fx' events
157
 * are valid as the whole 'fx' event/method space is global in nature. Any object may patch into
158
 * any global event by defining that event as a method. Global events have priorities
159
 * just like 'on' events; so as to be able to order the event execution. Due to the
160
 * nature of all events which start with 'fx' being valid, in effect, every object
161
 * has every 'fx' global event. It is simply an issue of tapping into the desired
162
 * global event.
163
 *
164
 * A global event that starts with 'fx' can be called even if the object does not
165
 * implement the method of the global event.  A call to a non-existing 'fx' method
166
 * will, at minimal, function and return null.  If a method argument list has a first
167
 * parameter, it will be returned instead of null.  This allows filtering and chaining.
168
 * 'fx' methods do not automatically install and uninstall. To install and uninstall an
169
 * object's global event listeners, call the object's {@see listen} and
170
 * {@see unlisten} methods, respectively.  An object may auto-install its global event
171
 * during {@see __construct} by overriding {@see getAutoGlobalListen} and returning true.
172
 *
173
 * As of PHP version 5.3, nulled objects without code references will still continue to persist
174
 * in the global event queue because {@see __destruct} is not automatically called.  In the common
175
 * __destruct method, if an object is listening to global events, then {@see unlisten} is called.
176
 * {@see unlisten} is required to be manually called before an object is
177
 * left without references if it is currently listening to any global events. This includes
178
 * class wide behaviors.  This is corrected in PHP 7.4.0 with WeakReferences and {@see
179
 * TWeakCallableCollection}
180
 *
181
 * An object that contains a method that starts with 'fx' will have those functions
182
 * automatically receive those events of the same name after {@see listen} is called on the object.
183
 *
184
 * An object may listen to a global event without defining an 'fx' method of the same name by
185
 * adding an object method to the global event list.  For example
186
 * ```php
187
 * $component->fxGlobalCheck=$callback;
188
 * $component->fxGlobalCheck->add($callback);
189
 * $component->attachEventHandler('fxGlobalCheck', [$object, 'someMethod']);
190
 * ```
191
 *
192
 *
193
 * Events between Objects and their behaviors, Dynamic Events
194
 *
195
 * An intra-object/behavior event is defined by methods that start with 'dy'.  Just as with
196
 * 'fx' global events, every object has every dynamic event.  Any call to a method that
197
 * starts with 'dy' will be handled, regardless of whether it is implemented.  These
198
 * events are for communicating with attached behaviors.
199
 *
200
 * Dynamic events can be used in a variety of ways.  They can be used to tell behaviors
201
 * when a non-behavior method is called.  Dynamic events could be used as data filters.
202
 * They could also be used to specify when a piece of code is to be run, eg. should the
203
 * loop process be performed on a particular piece of data.  In this way, some control
204
 * is handed to the behaviors over the process and/or data.
205
 *
206
 * If there are no handlers for an 'fx' or 'dy' event, it will return the first
207
 * parameter of the argument list.  If there are no arguments, these events
208
 * will return null.  If there are handlers an 'fx' method will be called directly
209
 * within the object.  Global 'fx' events are triggered by calling {@see raiseEvent}.
210
 * For dynamic events where there are behaviors that respond to the dynamic events, a
211
 * {@see \Prado\Util\TCallChain} is developed.  A call chain allows the behavior dynamic event
212
 * implementations to call further implementing behaviors within a chain.
213
 *
214
 * If an object implements {@see \Prado\Util\IDynamicMethods}, all global and object dynamic
215
 * events will be sent to {@see __dycall}.  In the case of global events, all
216
 * global events will trigger this method.  In the case of behaviors, all undefined
217
 * dynamic events  which are called will be passed through to this method.
218
 *
219
 *
220
 * Behaviors
221
 *
222
 * PRADO TComponent Behaviors is a method to extend a single component or a class
223
 * of components with new properties, methods, features, and fine control over the
224
 * owner object.  Behaviors can be attached to single objects or whole classes
225
 * (or interfaces, parents, and first level traits).
226
 *
227
 * There are two types of behaviors.  There are individual {@see \Prado\Util\IBehavior} and
228
 * there are class wide {IClassBehavior}.  IBehavior has one owner and IClassBehavior
229
 * can attach to multiple owners at the same time.  IClassBehavior is designed to be
230
 * stateless, like for specific filtering or addition of data.
231
 *
232
 * When a new class implements {@see \Prado\Util\IClassBehavior} or {@see \Prado\Util\IBehavior}, or extends
233
 * the PRADO implementations {@see \Prado\Util\TClassBehavior} and {@see \Prado\Util\TBehavior}, it may be
234
 * attached to a TComponent by calling the object's {@see attachBehavior}. The
235
 * behaviors associated name can then be used to {@see enableBehavior} or {@see
236
 * disableBehavior} the specific behavior.
237
 *
238
 * All behaviors may be turned on and off via {@see enableBehaviors} and
239
 * {@see disableBehaviors}, respectively.  To check if behaviors are on or off
240
 * a call to {@see getBehaviorsEnabled} will provide the variable.  By default,
241
 * a behavior's event handlers will be removed from events when disabled.
242
 *
243
 * Attaching and detaching whole sets of behaviors is done using
244
 * {@see attachBehaviors} and {@see detachBehaviors}.  {@see clearBehaviors}
245
 * removes all of an object's behaviors.
246
 *
247
 * {@see asa} returns a behavior of a specific name.  {@see isa} is the
248
 * behavior inclusive function that acts as the PHP operator {@see instanceof}.
249
 * A behavior could provide the functionality of a specific class thus causing
250
 * the host object to act similarly to a completely different class.  A behavior
251
 * would then implement {@see \Prado\Util\IInstanceCheck} to provide the identity of the
252
 * different class.
253
 *
254
 * IClassBehavior are similar to IBehavior except that the class behavior
255
 * attaches to multiple owners, like all the instances of a class.  A class behavior
256
 * will have the object upon which is being called be prepended to the parameter
257
 * list.  This way the object is known across the class behavior implementation.
258
 *
259
 * Class behaviors are attached using {@see attachClassBehavior} and detached
260
 * using {@see detachClassBehavior}.  Class behaviors are important in that
261
 * they will be applied to all new instances of a particular class and all listening
262
 * components as well.  Classes, Class Parents, Interfaces, and first level Traits
263
 * can be attached by class.
264
 * Class behaviors are default behaviors to new instances of a class in and are
265
 * received in {@see __construct}.  Detaching a class behavior will remove the
266
 * behavior from the default set of behaviors created for an object when the object
267
 * is instanced.
268
 *
269
 * Class behaviors are also added to all existing instances via the global 'fx'
270
 * event mechanism.  When a new class behavior is added, the event
271
 * {@see fxAttachClassBehavior} is raised and all existing instances that are
272
 * listening to this global event (primarily after {@see listen} is called)
273
 * will have this new behavior attached.  A similar process is used when
274
 * detaching class behaviors.  Any objects listening to the global 'fx' event
275
 * {@see fxDetachClassBehavior} will have a class behavior removed.
276
 *
277
 * Anonymous Behaviors are supported where the behavior does not have a name or
278
 * the behavior has a numeric for a name.  These cannot be accessed by name because
279
 * their names may be different in each request, for different owners, and possibly,
280
 * though extremely rarely, even the same object between serialization-sleep and
281
 * unserialization-wakeup.
282
 *
283
 * When serializing a component with behaviors, behaviors are saved and restored.
284
 * Named IClassBehavior class behaviors are updated with the current instance
285
 * of the named class behavior rather than replicate it from the wake up. {@see
286
 * __wakeup} will add any new named class behaviors to the unserializing component.
287
 *
288
 * IClassBehaviors can only use one given name for all behaviors except when applied
289
 * anonymously (with no name or a numeric name).
290
 *
291
 *
292
 * Dynamic Intra-Object Behavior Events
293
 *
294
 * Dynamic events start with 'dy'.  This mechanism is used to allow objects
295
 * to communicate with their behaviors directly.  The entire 'dy' event space
296
 * is valid.  All attached, enabled behaviors that implement a dynamic event
297
 * are called when the host object calls the dynamic event.  If there is no
298
 * implementation or behaviors, this returns null when no parameters are
299
 * supplied and will return the first parameter when there is at least one
300
 * parameter in the dynamic event.
301
 * ```php
302
 *	 null == $this->dyBehaviorEvent();
303
 *	 5 == $this->dyBehaviorEvent(5); //when no behaviors implement this dynamic event
304
 * ```
305
 *
306
 * Dynamic events can be chained together within behaviors to allow for data
307
 * filtering. Dynamic events are implemented within behaviors by defining the
308
 * event as a method.
309
 * ```php
310
 * class TObjectBehavior extends TBehavior {
311
 *     public function dyBehaviorEvent($param1, $callchain) {
312
 *			//Do something, eg:  $param1 += $this->getOwner()->getNumber();
313
 *			return $callchain->dyBehaviorEvent($param1);
314
 *     }
315
 * }
316
 * ```
317
 * This implementation of a behavior and dynamic event will flow through to the
318
 * next behavior implementing the dynamic event.  The first parameter is always
319
 * return when it is supplied.  Otherwise a dynamic event returns null.
320
 *
321
 * In the case of a class behavior, the object is also prepended to the dynamic
322
 * event.
323
 * ```php
324
 * class TObjectClassBehavior extends TClassBehavior {
325
 *     public function dyBehaviorEvent($hostobject, $param1, $callchain) {
326
 *			//Do something, eg:  $param1 += $hostobject->getNumber();
327
 *			return $callchain->dyBehaviorEvent($param1);
328
 *     }
329
 * }
330
 * ```
331
 * When calling a dynamic event, only the parameters are passed.  The host object
332
 * and the call chain are built into the framework.
333
 *
334
 *
335
 * Global Event and Dynamic Event Catching
336
 *
337
 * Given that all global 'fx' events and dynamic 'dy' events are valid and
338
 * operational, there is a mechanism for catching events called that are not
339
 * implemented (similar to the built-in PHP method {@see __call}).  When
340
 * a dynamic or global event is called but a behavior does not implement it,
341
 * yet desires to know when an undefined dynamic event is run, the behavior
342
 * implements the interface {@see \Prado\Util\IDynamicMethods} and method {@see __dycall}.
343
 *
344
 * In the case of dynamic events, {@see __dycall} is supplied with the method
345
 * name and its parameters.  When a global event is raised, via {@see raiseEvent},
346 194
 * the method is the event name and the parameters are supplied.
347
 *
348 194
 * When implemented, this catch-all mechanism is called for event global event event
349 38
 * when implemented outside of a behavior.  Within a behavior, it will also be called
350
 * when the object to which the behavior is attached calls any unimplemented dynamic
351
 * event.  This is the fall-back mechanism for informing a class and/or behavior
352 194
 * of when an global and/or undefined dynamic event is executed.
353 194
 *
354 194
 * @author Qiang Xue <[email protected]>
355 194
 * @author Brad Anderson <[email protected]>
356
 * @since 3.0
357
 * @method void dyClone()
358 194
 * @method void dyWakeUp()
359
 * @method void dyListen(array $globalEvents)
360
 * @method void dyUnlisten(array $globalEvents)
361
 * @method string dyPreRaiseEvent(string $name, mixed $sender, \Prado\TEventParameter $param, null|numeric $responsetype, null|function $postfunction)
362
 * @method dyIntraRaiseEventTestHandler(callable $handler, mixed $sender, \Prado\TEventParameter $param, string $name)
363
 * @method bool dyIntraRaiseEventPostHandler(string $name, mixed $sender, \Prado\TEventParameter $param, callable $handler, $response)
364
 * @method array dyPostRaiseEvent(array $responses, string $name, mixed $sender, \Prado\TEventParameter $param, null|numeric $responsetype, null|function $postfunction)
365
 * @method string dyEvaluateExpressionFilter(string $statements)
366
 * @method string dyEvaluateStatementsFilter(string $statements)
367
 * @method dyCreatedOnTemplate(\Prado\TComponent $parent)
368
 * @method void dyAddParsedObject(\Prado\TComponent|string $object)
369
 * @method void dyAttachBehavior(string $name, IBaseBehavior $behavior)
370
 * @method void dyDetachBehavior(string $name, IBaseBehavior $behavior)
371
 * @method void dyEnableBehavior(string $name, IBaseBehavior $behavior)
372
 * @method void dyDisableBehavior(string $name, IBaseBehavior $behavior)
373 183
 * @method void dyEnableBehaviors()
374
 * @method void dyDisableBehaviors()
375 183
 */
376
class TComponent
377
{
378
	/**
379
	 * @var array event handler lists
380
	 */
381
	protected $_e = [];
382
383
	/**
384
	 * @var bool if listening is enabled.  Automatically turned on or off in
385
	 * constructor according to {@see getAutoGlobalListen}.  Default false, off
386 620
	 */
387
	protected $_listeningenabled = false;
388 620
389
	/**
390
	 * @var array static registered global event handler lists
391 620
	 */
392
	private static $_ue = [];
393
394
	/**
395
	 * @var bool if object behaviors are on or off.  default true, on
396
	 */
397
	protected $_behaviorsenabled = true;
398
399 38
	/**
400
	 * @var TPriorityMap list of object behaviors
401 38
	 */
402
	protected $_m;
403
404
	/**
405
	 * @var array static global class behaviors, these behaviors are added upon instantiation of a class
406
	 */
407
	private static $_um = [];
408
409
410
	/**
411 194
	 * @const string the name of the global {@see raiseEvent} listener
412
	 */
413 194
	public const GLOBAL_RAISE_EVENT_LISTENER = 'fxGlobalListener';
414 194
415 194
416 192
	/**
417
	 * The common __construct.
418 194
	 * If desired by the new object, this will auto install and listen to global event functions
419 194
	 * as defined by the object via 'fx' methods. This also attaches any predefined behaviors.
420
	 * This function installs all class behaviors in a class hierarchy from the deepest subclass
421 1
	 * through each parent to the top most class, TComponent.
422
	 */
423
	public function __construct()
424
	{
425
		if ($this->getAutoGlobalListen()) {
426
			$this->listen();
427
		}
428
429
		$classes = $this->getClassHierarchy(true);
430
		array_pop($classes);
431
		foreach ($classes as $class) {
432
			if (isset(self::$_um[$class])) {
433
				$this->attachBehaviors(self::$_um[$class], true);
434
			}
435
		}
436
	}
437
438
	/**
439 38
	 * The common __clone magic method from PHP's "clone".
440
	 * This reattaches the behaviors to the cloned object.
441 38
	 * IBehavior objects are cloned, IClassBehaviors are not.
442
	 * Clone object events are scrubbed of the old object behaviors' events.
443
	 * To finalize the behaviors, dyClone is raised.
444
	 * @since 4.2.3
445 38
	 */
446
	public function __clone()
447 38
	{
448 38
		foreach ($this->_e as $event => $handlers) {
449
			$this->_e[$event] = clone $handlers;
450
		}
451 38
		$behaviorArray = array_values(($this->_m !== null) ? $this->_m->toArray() : []);
452 3
		if (count($behaviorArray) && count($this->_e)) {
453 3
			$behaviorArray = array_combine(array_map('spl_object_id', $behaviorArray), $behaviorArray);
454
			foreach ($this->_e as $event => $handlers) {
455
				foreach ($handlers->toArray() as $handler) {
456 38
					$a = is_array($handler);
457
					if ($a && array_key_exists(spl_object_id($handler[0]), $behaviorArray) || !$a && array_key_exists(spl_object_id($handler), $behaviorArray)) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($a && array_key_exists(...ndler), $behaviorArray), Probably Intended Meaning: $a && (array_key_exists(...dler), $behaviorArray))
Loading history...
458 38
						$handlers->remove($handler);
459
					}
460 38
				}
461
			}
462
		}
463
		if ($this->_m !== null) {
464
			$behaviors = $this->_m;
465
			$this->_m = new TPriorityMap();
466
			foreach ($behaviors->getPriorities() as $priority) {
467
				foreach ($behaviors->itemsAtPriority($priority) as $name => $behavior) {
468
					if ($behavior instanceof IBehavior) {
469
						$behavior = clone $behavior;
470
					}
471
					$this->attachBehavior($name, $behavior, $priority);
472
				}
473
			}
474
		}
475
		$this->callBehaviorsMethod('dyClone', $return);
476 38
	}
477
478 38
	/**
479 3
	 * The common __wakeup magic method from PHP's "unserialize".
480
	 * This reattaches the behaviors to the reconstructed object.
481
	 * Any global class behaviors are used rather than their unserialized copy.
482 38
	 * Any global behaviors not found in the object will be added.
483
	 * To finalize the behaviors, dyWakeUp is raised.
484 38
	 * If a TModule needs to add events to an object during unserialization,
485 38
	 * the module can use a small IClassBehavior [implementing dyWakeUp]
486
	 * (adding the event[s]) attached to the class with {@see
487
	 * attachClassBehavior} prior to unserialization.
488 38
	 * @since 4.2.3
489 3
	 */
490 3
	public function __wakeup()
491
	{
492
		$classes = $this->getClassHierarchy(true);
493 38
		array_pop($classes);
494
		$classBehaviors = [];
495 38
		if ($this->_m !== null) {
496
			$behaviors = $this->_m;
497 38
			$this->_m = new TPriorityMap();
498
			foreach ($behaviors->getPriorities() as $priority) {
499
				foreach ($behaviors->itemsAtPriority($priority) as $name => $behavior) {
500
					if ($behavior instanceof IClassBehavior && !is_numeric($name)) {
501
						//Replace class behaviors with their current instances, if they exist.
502
						foreach ($classes as $class) {
503
							if (isset(self::$_um[$class]) && array_key_exists($name, self::$_um[$class])) {
504 4
								$behavior = self::$_um[$class][$name]->getBehavior();
505
								break;
506 4
							}
507
						}
508
					}
509
					$classBehaviors[$name] = $name;
510
					$this->attachBehavior($name, $behavior, $priority);
511
				}
512
			}
513
		}
514
		foreach ($classes as $class) {
515
			if (isset(self::$_um[$class])) {
516
				foreach (self::$_um[$class] as $name => $behavior) {
517
					if(is_numeric($name)) {
518
						continue;
519
					}
520
					if (!array_key_exists($name, $classBehaviors)) {
521
						$this->attachBehaviors([$name => $behavior], true);
522
					}
523
				}
524
			}
525
		}
526
		$this->callBehaviorsMethod('dyWakeUp', $return);
527
	}
528
529
530
	/**
531
	 * Tells TComponent whether or not to automatically listen to global events.
532
	 * Defaults to false because PHP variable cleanup is affected if this is true.
533 211
	 * When unsetting a variable that is listening to global events, {@see unlisten}
534
	 * must explicitly be called when cleaning variables allocation or else the global
535 211
	 * event registry will contain references to the old object. This is true for PHP 5.4
536 211
	 *
537 4
	 * Override this method by a subclass to change the setting.  When set to true, this
538 4
	 * will enable {@see __construct} to call {@see listen}.
539 4
	 *
540
	 * @return bool whether or not to auto listen to global events during {@see __construct}, default false
541
	 */
542
	public function getAutoGlobalListen()
543
	{
544
		return false;
545
	}
546
547
548 4
	/**
549
	 * The common __destruct
550
	 * When listening, this unlistens from the global event routines.  It also detaches
551
	 * all the behaviors so they can clean up, eg remove handlers.
552
	 *
553 211
	 * Prior to PHP 7.4, when listening, unlisten must be manually called for objects
554 27
	 * to destruct because circular references will prevent the __destruct process.
555 27
	 */
556 27
	public function __destruct()
557 24
	{
558 3
		$this->clearBehaviors();
559 3
		if ($this->_listeningenabled) {
560 2
			$this->unlisten();
561
		}
562 24
	}
563
564
565 27
	/**
566 27
	 * This utility function is a private array filter method.  The array values
567
	 * that start with 'fx' are filtered in.
568
	 * @param mixed $name
569 8
	 */
570 8
	private function filter_prado_fx($name)
571 8
	{
572 3
		return strncasecmp($name, 'fx', 2) === 0;
573
	}
574 8
575
576
	/**
577
	 * This returns an array of the class name and the names of all its parents.  The base object last,
578
	 * {@see \Prado\TComponent}, and the deepest subclass is first.
579
	 * @param bool $lowercase optional should the names be all lowercase true/false
580 211
	 * @return string[] array of strings being the class hierarchy of $this.
581 211
	 */
582 6
	public function getClassHierarchy($lowercase = false)
583
	{
584 211
		static $_classhierarchy = [];
585
		$class = $this::class;
586
		if (isset($_classhierarchy[$class]) && isset($_classhierarchy[$class][$lowercase ? 1 : 0])) {
587
			return $_classhierarchy[$class][$lowercase ? 1 : 0];
588 4
		}
589 4
		$classes = [array_values(class_implements($class))];
590
		do {
591
			$classes[] = array_values(class_uses($class));
592
			$classes[] = [$class];
593
		} while ($class = get_parent_class($class));
594
		$classes = array_merge(...$classes);
595
		if ($lowercase) {
596
			$classes = array_map('strtolower', $classes);
597
		}
598
		$_classhierarchy[$class] ??= [];
599
		$_classhierarchy[$class][$lowercase ? 1 : 0] = $classes;
600
601
		return $classes;
602
	}
603
604
	/**
605
	 * This caches the 'fx' events for classes.
606
	 * @param object $class
607
	 * @return string[] fx events from a specific class
608
	 */
609
	protected function getClassFxEvents($class)
610
	{
611
		static $_classfx = [];
612
		$className = $class::class;
613
		if (isset($_classfx[$className])) {
614
			return $_classfx[$className];
615
		}
616
		$fx = array_filter(get_class_methods($class), [$this, 'filter_prado_fx']);
617 95
		$_classfx[$className] = $fx;
618
		return $fx;
619 95
	}
620
621 84
	/**
622 25
	 * This adds an object's fx event handlers into the global broadcaster to listen into any
623
	 * broadcast global events called through {@see raiseEvent}
624 2
	 *
625 23
	 * Behaviors may implement the function:
626
	 * ```php
627 13
	 *	public function dyListen($globalEvents[, ?TCallChain $chain = null]) {
628 13
	 * 		$this->listen($globalEvents); //eg
629 12
	 *      if ($chain)
630
	 *          $chain->dyUnlisten($globalEvents);
631 13
	 * }
632 14
	 * ```
633
	 * to be executed when listen is called.  All attached behaviors are notified through dyListen.
634 3
	 *
635 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...
636 1
	 */
637
	public function listen()
638 3
	{
639 13
		if ($this->_listeningenabled) {
640
			return;
641 13
		}
642 9
643 7
		$fx = $this->getClassFxEvents($this);
644 4
645 4
		foreach ($fx as $func) {
646 4
			$this->getEventHandlers($func)->add([$this, $func]);
647 4
		}
648
649
		if (is_a($this, IDynamicMethods::class)) {
650
			$this->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, [$this, '__dycall']);
651
			array_push($fx, TComponent::GLOBAL_RAISE_EVENT_LISTENER);
652 4
		}
653
654
		$this->_listeningenabled = true;
655
656
		$this->callBehaviorsMethod('dyListen', $return, $fx);
657
658
		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...
659
	}
660
661
	/**
662
	 * this removes an object's fx events from the global broadcaster
663
	 *
664
	 * Behaviors may implement the function:
665
	 * ```php
666
	 *	public function dyUnlisten($globalEvents[, ?TCallChain $chain = null]) {
667
	 * 		$this->behaviorUnlisten(); //eg
668
	 *      if ($chain)
669
	 *          $chain->dyUnlisten($globalEvents);
670 77
	 * }
671
	 * ```
672 77
	 * to be executed when listen is called.  All attached behaviors are notified through dyUnlisten.
673 76
	 *
674 2
	 * @return numeric the number of global events that were unregistered from the global event registry
675
	 */
676 76
	public function unlisten()
677 5
	{
678 1
		if (!$this->_listeningenabled) {
679 1
			return;
680
		}
681 1
682 4
		$fx = $this->getClassFxEvents($this);
683 3
684 4
		foreach ($fx as $func) {
685 4
			$this->detachEventHandler($func, [$this, $func]);
686 4
		}
687 4
688 4
		if (is_a($this, IDynamicMethods::class)) {
689 4
			$this->detachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, [$this, '__dycall']);
690 4
			array_push($fx, TComponent::GLOBAL_RAISE_EVENT_LISTENER);
691
		}
692
693 4
		$this->_listeningenabled = false;
694 4
695
		$this->callBehaviorsMethod('dyUnlisten', $return, $fx);
696
697
		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...
698 1
	}
699 1
700
	/**
701 1
	 * Gets the state of listening to global events
702
	 * @return bool is Listening to global broadcast enabled
703
	 */
704
	public function getListeningToGlobalEvents()
705
	{
706
		return $this->_listeningenabled;
707
	}
708
709
710
	/**
711
	 * Calls a method.
712
	 * Do not call this method directly. This is a PHP magic method that we override
713
	 * to allow behaviors, dynamic events (intra-object/behavior events),
714
	 * undefined dynamic and global events, and
715
	 * to allow using the following syntax to call a property setter or getter.
716
	 * ```php
717 5
	 * $this->getPropertyName($value); // if there's a $this->getjsPropertyName() method
718
	 * $this->setPropertyName($value); // if there's a $this->setjsPropertyName() method
719 5
	 * ```
720 5
	 *
721 3
	 * Additional object behaviors override class behaviors.
722 2
	 * dynamic and global events do not fail even if they aren't implemented.
723 1
	 * Any intra-object/behavior dynamic events that are not implemented by the behavior
724 1
	 * return the first function paramater or null when no parameters are specified.
725 1
	 *
726 1
	 * @param string $method method name that doesn't exist and is being called on the object
727 1
	 * @param mixed $args method parameters
728 1
	 * @throws TInvalidOperationException If the property is not defined or read-only or
729 1
	 * 		method is undefined
730 1
	 * @return mixed result of the method call, or false if 'fx' or 'dy' function but
731 1
	 *		is not found in the class, otherwise it runs
732
	 */
733 1
	public function __call($method, $args)
734 1
	{
735 1
		$getset = substr($method, 0, 3);
736
		if (($getset == 'get') || ($getset == 'set')) {
737
			$propname = substr($method, 3);
738
			$jsmethod = $getset . 'js' . $propname;
739 1
			if (Prado::method_visible($this, $jsmethod)) {
740
				if (count($args) > 0) {
741
					if ($args[0] && !($args[0] instanceof TJavaScriptString)) {
742
						$args[0] = new TJavaScriptString($args[0]);
743
					}
744
				}
745
				return $this->$jsmethod(...$args);
746
			}
747
748
			if (($getset == 'set') && Prado::method_visible($this, 'getjs' . $propname)) {
749
				throw new TInvalidOperationException('component_property_readonly', $this::class, $method);
750
			}
751
		}
752
		if ($this->callBehaviorsMethod($method, $return, ...$args)) {
753 3
			return $return;
754
		}
755 3
756 2
		// don't throw an exception for __magicMethods() or any other weird methods natively implemented by php
757 3
		if (!method_exists($this, $method)) {
758 1
			throw new TUnknownMethodException('component_method_undefined', $this::class, $method);
759 2
		}
760 2
	}
761 2
762 1
	/**
763 2
	 * This is the magic method that is called when a static function is not found.
764 2
	 * It checks the class if it has an ISingleton instance, in which case its behaviors
765 1
	 * are checked for the static method.  This further checks the Class-wide behaviors
766
	 * (added with {@see \Prado\TComponent::attachClassBehavior}) for the static method.
767 2
	 * When the static method is found (in either the ISingleton behaviors or the class-wide
768 2
	 * behaviors), the behavior's static method is called and the results returned.
769 2
	 * @param string $method The method name of the static call.
770 2
	 * @param array $args The array of arguments passed to the static call.
771 2
	 * @return mixed the result of the static call.
772
	 * @since 4.2.3
773
	 */
774 2
	public static function __callStatic(string $method, array $args)
775 2
	{
776
		$checkedClasses = [];
777
		if (is_a(static::class, ISingleton::class, true) && ($singleton = (static::class)::singleton(false)) && $singleton->getBehaviorsEnabled()) {
778 1
			foreach ($singleton->getBehaviors() as $behavior) {
779 1
				$class = $behavior::class;
780
				$lclass = strtolower($class);
781 3
				if (!isset($checkedClasses[$lclass])) {
782
					if ($behavior->getEnabled() && method_exists($class, $method)) {
783
						return forward_static_call_array([$class, $method], $args);
784
					}
785
					$checkedClasses[$lclass] = true;
786
				}
787
			}
788
		}
789
790 1
		if (isset(self::$_um[$lclass = strtolower(static::class)])) {
791
			foreach(self::$_um[$lclass] as $pbehavior) {
792 1
				$class = $behavior = $pbehavior->getBehavior();
793
				if (is_array($behavior)) {
794
					$class = $behavior['class'];
795
				} elseif (is_object($behavior)) {
796
					$class = $behavior::class;
797
				}
798
799
				$lclass = strtolower($class);
800
				if (!isset($checkedClasses[$lclass])) {
801
					if ((!($behavior instanceof IBaseBehavior) || $behavior->getEnabled()) && method_exists($class, $method)) {
802
						return forward_static_call_array([$class, $method], $args);
803
					}
804 9
					$checkedClasses[$lclass] = true;
805
				}
806 9
			}
807 6
		}
808 8
809 2
		// don't throw an exception for __magicMethods() or any other weird methods natively implemented by php
810 2
		if (!method_exists(static::class, $method)) {
811 2
			throw new TUnknownMethodException('component_method_undefined', static::class, $method);
812
		}
813
	}
814
815 8
	/**
816
	 * Returns a property value or an event handler list by property or event name.
817
	 * Do not call this method. This is a PHP magic method that we override
818
	 * to allow using the following syntax to read a property:
819
	 * ```php
820
	 * $value = $component->PropertyName;
821
	 * $value = $component->jsPropertyName; // return JavaScript literal
822
	 * ```
823
	 * and to obtain the event handler list for an event,
824
	 * ```php
825
	 * $eventHandlerList = $component->EventName;
826
	 * ```
827 11
	 * This will also return the global event handler list when specifing an 'fx'
828
	 * event,
829 11
	 * ```php
830 8
	 * $globalEventHandlerList = $component->fxEventName;
831 7
	 * ```
832 2
	 * When behaviors are enabled, this will return the behavior of a specific
833 2
	 * name, a property of a behavior, or an object 'on' event defined by the behavior.
834 2
	 * @param string $name the property name or the event name
835
	 * @throws TInvalidOperationException if the property/event is not defined.
836
	 * @return mixed the property value or the event handler list as {@see \Prado\Collections\TWeakCallableCollection}
837
	 */
838 7
	public function __get($name)
839
	{
840
		if (Prado::method_visible($this, $getter = 'get' . $name)) {
841
			// getting a property
842
			return $this->$getter();
843
		} elseif (Prado::method_visible($this, $jsgetter = 'getjs' . $name)) {
844
			// getting a javascript property
845
			return (string) $this->$jsgetter();
846
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
847
			// getting an event (handler list)
848
			$name = strtolower($name);
849
			if (!isset($this->_e[$name])) {
850
				$this->_e[$name] = new TWeakCallableCollection();
851 4
			}
852
			return $this->_e[$name];
853 4
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
854 4
			// getting a global event (handler list)
855 4
			$name = strtolower($name);
856
			if (!isset(self::$_ue[$name])) {
857 4
				self::$_ue[$name] = new TWeakCallableCollection();
858
			}
859
			return self::$_ue[$name];
860
		} elseif ($this->getBehaviorsEnabled()) {
861
			// getting a behavior property/event (handler list)
862
			$name = strtolower($name);
863
			if (isset($this->_m[$name])) {
864
				return $this->_m[$name];
865
			} elseif ($this->_m !== null) {
866
				foreach ($this->_m->toArray() as $behavior) {
867
					if ($behavior->getEnabled() && (property_exists($behavior, $name) || $behavior->canGetProperty($name) || $behavior->hasEvent($name))) {
868
						return $behavior->$name;
869
					}
870 2
				}
871
			}
872 2
		}
873 2
		throw new TInvalidOperationException('component_property_undefined', $this::class, $name);
874 1
	}
875
876 2
	/**
877 2
	 * Sets value of a component property.
878
	 * Do not call this method. This is a PHP magic method that we override
879 2
	 * to allow using the following syntax to set a property or attach an event handler.
880 2
	 * ```php
881
	 *    $this->PropertyName = $value;
882
	 *    $this->jsPropertyName = $value; // $value will be treated as a JavaScript literal
883
	 *    $this->EventName = $handler;
884
	 *    $this->fxEventName = $handler; //global event listener
885
	 *    $this->EventName = function($sender, $param) {...};
886
	 * ```
887
	 * When behaviors are enabled, this will also set a behaviors properties and events.
888
	 * @param string $name the property name or event name
889
	 * @param mixed $value the property value or event handler
890
	 * @throws TInvalidOperationException If the property is not defined or read-only.
891
	 */
892
	public function __set($name, $value)
893
	{
894 176
		if (Prado::method_visible($this, $setter = 'set' . $name)) {
895
			if (strncasecmp($name, 'js', 2) === 0 && $value && !($value instanceof TJavaScriptLiteral)) {
896 176
				$value = new TJavaScriptLiteral($value);
897 176
			}
898 3
			return $this->$setter($value);
899 3
		} elseif (Prado::method_visible($this, $jssetter = 'setjs' . $name)) {
900 3
			if ($value && !($value instanceof TJavaScriptString)) {
901 3
				$value = new TJavaScriptString($value);
902
			}
903
			return $this->$jssetter($value);
904
		} elseif ((strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) || strncasecmp($name, 'fx', 2) === 0) {
905 3
			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...
906
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->getBehaviorsEnabled()) {
907
			$sets = 0;
908
			foreach ($this->_m->toArray() as $behavior) {
909
				if ($behavior->getEnabled() && (property_exists($behavior, $name) || $behavior->canSetProperty($name) || $behavior->hasEvent($name))) {
910
					$behavior->$name = $value;
911
					$sets++;
912
				}
913
			}
914
			if ($sets) {
915 211
				return $value;
916
			}
917 211
		}
918 211
919 211
		if (Prado::method_visible($this, 'get' . $name) || Prado::method_visible($this, 'getjs' . $name)) {
920
			throw new TInvalidOperationException('component_property_readonly', $this::class, $name);
921 178
		} else {
922 54
			throw new TInvalidOperationException('component_property_undefined', $this::class, $name);
923 177
		}
924 3
	}
925 3
926 3
	/**
927
	 * Checks if a property value is null, there are no events in the object
928
	 * event list or global event list registered under the name, and, if
929
	 * behaviors are enabled,
930
	 * Do not call this method. This is a PHP magic method that we override
931 176
	 * to allow using isset() to detect if a component property is set or not.
932
	 * This also works for global events.  When behaviors are enabled, it
933
	 * will check for a behavior of the specified name, and also check
934
	 * the behavior for events and properties.
935
	 * @param string $name the property name or the event name
936
	 * @since 3.2.3
937
	 */
938
	public function __isset($name)
939
	{
940
		if (Prado::method_visible($this, $getter = 'get' . $name)) {
941 92
			return $this->$getter() !== null;
942
		} elseif (Prado::method_visible($this, $jsgetter = 'getjs' . $name)) {
943 92
			return $this->$jsgetter() !== null;
944 63
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
945 63
			$name = strtolower($name);
946 14
			return isset($this->_e[$name]) && $this->_e[$name]->getCount();
947
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
948 63
			$name = strtolower($name);
949 38
			return isset(self::$_ue[$name]) && self::$_ue[$name]->getCount();
950 38
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->getBehaviorsEnabled()) {
951 38
			$name = strtolower($name);
952 5
			if (isset($this->_m[$name])) {
953
				return true;
954 38
			}
955 6
			foreach ($this->_m->toArray() as $behavior) {
956 6
				if ($behavior->getEnabled() && (property_exists($behavior, $name) || $behavior->canGetProperty($name) || $behavior->hasEvent($name))) {
957 6
					return isset($behavior->$name);
958 6
				}
959
			}
960
		}
961
		return false;
962 4
	}
963
964
	/**
965
	 * Sets a component property to be null.  Clears the object or global
966
	 * events. When enabled, loops through all behaviors and unsets the
967
	 * property or event.
968
	 * Do not call this method. This is a PHP magic method that we override
969
	 * to allow using unset() to set a component property to be null.
970
	 * @param string $name the property name or the event name
971
	 * @throws TInvalidOperationException if the property is read only.
972
	 * @since 3.2.3
973
	 */
974
	public function __unset($name)
975
	{
976
		if (Prado::method_visible($this, $setter = 'set' . $name)) {
977
			$this->$setter(null);
978
		} elseif (Prado::method_visible($this, $jssetter = 'setjs' . $name)) {
979
			$this->$jssetter(null);
980
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
981
			$this->_e[strtolower($name)]->clear();
982
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
983
			$this->getEventHandlers($name)->remove([$this, $name]);
984
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->getBehaviorsEnabled()) {
985
			$name = strtolower($name);
986
			if (isset($this->_m[$name])) {
987
				$this->detachBehavior($name);
988
			} else {
989
				$unset = 0;
990
				foreach ($this->_m->toArray() as $behavior) {
991
					if ($behavior->getEnabled()) {
992
						unset($behavior->$name);
993
						$unset++;
994
					}
995
				}
996
				if (!$unset && Prado::method_visible($this, 'get' . $name)) {
997
					throw new TInvalidOperationException('component_property_readonly', $this::class, $name);
998
				}
999
			}
1000
		} elseif (Prado::method_visible($this, 'get' . $name)) {
1001
			throw new TInvalidOperationException('component_property_readonly', $this::class, $name);
1002
		}
1003
	}
1004
1005
	/**
1006
	 * Determines whether a property is defined.
1007
	 * A property is defined if there is a getter or setter method
1008
	 * defined in the class. Note, property names are case-insensitive.
1009
	 * @param string $name the property name
1010 44
	 * @return bool whether the property is defined
1011
	 */
1012 44
	public function hasProperty($name)
1013 44
	{
1014
		return $this->canGetProperty($name) || $this->canSetProperty($name);
1015
	}
1016
1017
	/**
1018
	 * Determines whether a property can be read.
1019
	 * A property can be read if the class has a getter method
1020
	 * for the property name. Note, property name is case-insensitive.
1021
	 * This also checks for getjs.  When enabled, it loops through all
1022
	 * active behaviors for the get property when undefined by the object.
1023
	 * @param string $name the property name
1024
	 * @return bool whether the property can be read
1025 38
	 */
1026
	public function canGetProperty($name)
1027 38
	{
1028
		if (Prado::method_visible($this, 'get' . $name) || Prado::method_visible($this, 'getjs' . $name)) {
1029 38
			return true;
1030 38
		} elseif ($this->_m !== null && $this->getBehaviorsEnabled()) {
1031 1
			foreach ($this->_m->toArray() as $behavior) {
1032
				if ($behavior->getEnabled() && $behavior->canGetProperty($name)) {
1033
					return true;
1034 2
				}
1035
			}
1036
		}
1037
		return false;
1038
	}
1039
1040
	/**
1041
	 * Determines whether a property can be set.
1042
	 * A property can be written if the class has a setter method
1043
	 * for the property name. Note, property name is case-insensitive.
1044
	 * This also checks for setjs.  When enabled, it loops through all
1045
	 * active behaviors for the set property when undefined by the object.
1046
	 * @param string $name the property name
1047
	 * @return bool whether the property can be written
1048
	 */
1049
	public function canSetProperty($name)
1050
	{
1051
		if (Prado::method_visible($this, 'set' . $name) || Prado::method_visible($this, 'setjs' . $name)) {
1052
			return true;
1053
		} elseif ($this->_m !== null && $this->getBehaviorsEnabled()) {
1054
			foreach ($this->_m->toArray() as $behavior) {
1055
				if ($behavior->getEnabled() && $behavior->canSetProperty($name)) {
1056
					return true;
1057
				}
1058
			}
1059
		}
1060
		return false;
1061
	}
1062
1063
	/**
1064
	 * Evaluates a property path.
1065
	 * A property path is a sequence of property names concatenated by '.' character.
1066
	 * For example, 'Parent.Page' refers to the 'Page' property of the component's
1067
	 * 'Parent' property value (which should be a component also).
1068
	 * When a property is not defined by an object, this also loops through all
1069
	 * active behaviors of the object.
1070
	 * @param string $path property path
1071
	 * @return mixed the property path value
1072
	 */
1073
	public function getSubProperty($path)
1074
	{
1075
		$object = $this;
1076
		foreach (explode('.', $path) as $property) {
1077
			$object = $object->$property;
1078
		}
1079
		return $object;
1080
	}
1081
1082
	/**
1083
	 * Sets a value to a property path.
1084
	 * A property path is a sequence of property names concatenated by '.' character.
1085
	 * For example, 'Parent.Page' refers to the 'Page' property of the component's
1086
	 * 'Parent' property value (which should be a component also).
1087
	 * When a property is not defined by an object, this also loops through all
1088
	 * active behaviors of the object.
1089
	 * @param string $path property path
1090
	 * @param mixed $value the property path value
1091
	 */
1092
	public function setSubProperty($path, $value)
1093
	{
1094
		$object = $this;
1095
		if (($pos = strrpos($path, '.')) === false) {
1096
			$property = $path;
1097
		} else {
1098
			$object = $this->getSubProperty(substr($path, 0, $pos));
1099
			$property = substr($path, $pos + 1);
1100
		}
1101
		$object->$property = $value;
1102
	}
1103
1104
	/**
1105
	 * Calls a method on a component's behaviors.  When the method is a
1106
	 * dynamic event, it is raised with all the behaviors.  When a class implements
1107
	 * a dynamic event (eg. for patching), the class can customize raising the
1108
	 * dynamic event with the classes behaviors using this method.
1109 182
	 * Dynamic [dy] and global [fx] events call {@see __dycall} when $this
1110
	 * implements IDynamicMethods.  Finally, this catches all unexecuted
1111 182
	 * Dynamic [dy] and global [fx] events and returns the first $args parameter;
1112 182
	 * acting as a passthrough (filter) of the first $args parameter. In dy/fx methods,
1113 1
	 * there can be no $args parameters, the first parameter used as a pass through
1114 1
	 * filter, or act as a return variable with the first $args parameter being
1115
	 * the default return value.
1116
	 * @param string $method The method being called or dynamic/global event being raised.
1117 182
	 * @param mixed &$return The return value.
1118 181
	 * @param array $args The arguments to the method being called.
1119
	 * @return bool Was the method handled.
1120
	 * @since 4.2.3
1121 182
	 */
1122 182
	public function callBehaviorsMethod($method, &$return, ...$args): bool
1123
	{
1124 182
		if ($this->_m !== null && $this->getBehaviorsEnabled()) {
1125
			if (strncasecmp($method, 'dy', 2) === 0) {
1126 182
				if ($callchain = $this->getCallChain($method, ...$args)) {
1127 60
					$return = $callchain->call(...$args);
1128 60
					return true;
1129 60
				}
1130 1
			} else {
1131 1
				foreach ($this->_m->toArray() as $behavior) {
1132
					if ($behavior->getEnabled() && Prado::method_visible($behavior, $method)) {
1133 60
						if ($behavior instanceof IClassBehavior) {
1134 60
							array_unshift($args, $this);
1135 59
						}
1136
						$return = $behavior->$method(...$args);
1137
						return true;
1138
					}
1139 59
				}
1140
			}
1141
		}
1142
		if (strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1143
			if ($this instanceof IDynamicMethods) {
1144
				$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

1144
				/** @scrutinizer ignore-call */ 
1145
    $return = $this->__dycall($method, $args);
Loading history...
1145
				return true;
1146
			}
1147
			$return = $args[0] ?? null;
1148
			return true;
1149
		}
1150
		return false;
1151
	}
1152
1153
	/**
1154
	 * This gets the chain of methods implemented by attached and enabled behaviors.
1155 59
	 * This method disregards the {behaviorsEnabled
1156 59
	 * @param string $method The name of the behaviors method being chained.
1157 59
	 * @param array $args The arguments to the behaviors method being chained.
1158
	 * @return ?TCallChain The chain of methods implemented by behaviors or null when
1159
	 *   there are no methods to call.
1160 59
	 * @since 4.2.3
1161 1
	 */
1162 1
	protected function getCallChain($method, ...$args): ?TCallChain
1163
	{
1164 59
		$classArgs = $callchain = null;
1165 59
		foreach ($this->_m->toArray() as $behavior) {
1166 1
			if ($behavior->getEnabled() && (Prado::method_visible($behavior, $method) || ($behavior instanceof IDynamicMethods))) {
1167
				if ($classArgs === null) {
1168 59
					$classArgs = $args;
1169
					array_unshift($classArgs, $this);
1170
				}
1171 59
				if (!$callchain) {
1172
					$callchain = new TCallChain($method);
1173
				}
1174
				$callchain->addCall([$behavior, $method], ($behavior instanceof IClassBehavior) ? $classArgs : $args);
1175
			}
1176
		}
1177
		return $callchain;
1178 59
	}
1179
1180 59
	/**
1181 1
	 * Determines whether a method is defined. When behaviors are enabled, this
1182
	 * will loop through all enabled behaviors checking for the method as well.
1183
	 * Nested behaviors within behaviors are not supported but the nested behavior can
1184 59
	 * affect the primary behavior like any behavior affects their owner.
1185 2
	 * Note, method name are case-insensitive.
1186
	 * @param string $name the method name
1187 58
	 * @return bool
1188
	 * @since 4.2.2
1189
	 */
1190 59
	public function hasMethod($name)
1191 60
	{
1192
		if (Prado::method_visible($this, $name) || strncasecmp($name, 'dy', 2) === 0) {
1193
			return true;
1194 173
		} elseif ($this->_m !== null && $this->getBehaviorsEnabled()) {
1195 1
			foreach ($this->_m->toArray() as $behavior) {
1196
				//Prado::method_visible($behavior, $name) rather than $behavior->hasMethod($name) b/c only one layer is supported, @4.2.2
1197
				if ($behavior->getEnabled() && Prado::method_visible($behavior, $name)) {
1198 182
					return true;
1199 181
				}
1200
			}
1201
		}
1202 182
		return false;
1203
	}
1204 182
1205
	/**
1206
	 * Determines whether an event is defined.
1207
	 * An event is defined if the class has a method whose name is the event name
1208
	 * prefixed with 'on', 'fx', or 'dy'.
1209
	 * Every object responds to every 'fx' and 'dy' event as they are in a universally
1210
	 * accepted event space.  'on' event must be declared by the object.
1211
	 * When enabled, this will loop through all active behaviors for 'on' events
1212
	 * defined by the behavior.
1213
	 * Note, event name is case-insensitive.
1214
	 * @param string $name the event name
1215
	 * @return bool
1216
	 */
1217
	public function hasEvent($name)
1218
	{
1219
		if ((strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) || strncasecmp($name, 'fx', 2) === 0 || strncasecmp($name, 'dy', 2) === 0) {
1220
			return true;
1221
		} elseif ($this->_m !== null && $this->getBehaviorsEnabled()) {
1222
			foreach ($this->_m->toArray() as $behavior) {
1223
				if ($behavior->getEnabled() && $behavior->hasEvent($name)) {
1224 2
					return true;
1225
				}
1226 2
			}
1227
		}
1228 2
		return false;
1229
	}
1230
1231 2
	/**
1232 1
	 * Checks if an event has any handlers.  This function also checks through all
1233 1
	 * the behaviors for 'on' events when behaviors are enabled.
1234
	 * 'dy' dynamic events are not handled by this function.
1235
	 * @param string $name the event name
1236
	 * @return bool whether an event has been attached one or several handlers
1237
	 */
1238
	public function hasEventHandler($name)
1239
	{
1240
		$name = strtolower($name);
1241
		if (strncasecmp($name, 'fx', 2) === 0) {
1242
			return isset(self::$_ue[$name]) && self::$_ue[$name]->getCount() > 0;
1243
		} else {
1244
			if (isset($this->_e[$name]) && $this->_e[$name]->getCount() > 0) {
1245
				return true;
1246
			} elseif ($this->_m !== null && $this->getBehaviorsEnabled()) {
1247
				foreach ($this->_m->toArray() as $behavior) {
1248
					if ($behavior->getEnabled() && $behavior->hasEventHandler($name)) {
1249
						return true;
1250
					}
1251
				}
1252
			}
1253
		}
1254 2
		return false;
1255
	}
1256 2
1257
	/**
1258 2
	 * Returns the list of attached event handlers for an 'on' or 'fx' event.   This function also
1259 2
	 * checks through all the behaviors for 'on' event lists when behaviors are enabled.
1260
	 * @param mixed $name
1261
	 * @throws TInvalidOperationException if the event is not defined
1262 2
	 * @return TWeakCallableCollection list of attached event handlers for an event
1263 2
	 */
1264 2
	public function getEventHandlers($name)
1265 1
	{
1266 1
		if (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
1267
			$name = strtolower($name);
1268
			if (!isset($this->_e[$name])) {
1269
				$this->_e[$name] = new TWeakCallableCollection();
1270
			}
1271
			return $this->_e[$name];
1272
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
1273
			$name = strtolower($name);
1274
			if (!isset(self::$_ue[$name])) {
1275
				self::$_ue[$name] = new TWeakCallableCollection();
1276
			}
1277
			return self::$_ue[$name];
1278
		} elseif ($this->_m !== null && $this->getBehaviorsEnabled()) {
1279
			foreach ($this->_m->toArray() as $behavior) {
1280
				if ($behavior->getEnabled() && $behavior->hasEvent($name)) {
1281
					return $behavior->getEventHandlers($name);
1282
				}
1283
			}
1284
		}
1285
		throw new TInvalidOperationException('component_event_undefined', $this::class, $name);
1286
	}
1287
1288
	/**
1289 1
	 * Attaches an event handler to an event.
1290
	 *
1291 1
	 * The handler must be a valid PHP callback, i.e., a string referring to
1292 1
	 * a global function name, or an array containing two elements with
1293 1
	 * the first element being an object and the second element a method name
1294
	 * of the object. In Prado, you can also use method path to refer to
1295
	 * an event handler. For example, array($object,'Parent.buttonClicked')
1296
	 * uses a method path that refers to the method $object->Parent->buttonClicked(...).
1297
	 *
1298
	 * The event handler must be of the following signature,
1299
	 * ```php
1300
	 * function handlerName($sender, $param) {}
1301
	 * function handlerName($sender, $param, $name) {}
1302
	 * ```
1303
	 * where $sender represents the object that raises the event,
1304
	 * and $param is the event parameter. $name refers to the event name
1305
	 * being handled.
1306
	 *
1307
	 * This is a convenient method to add an event handler.
1308
	 * It is equivalent to {@see getEventHandlers}($name)->add($handler).
1309
	 * For complete management of event handlers, use {@see getEventHandlers}
1310
	 * to get the event handler list first, and then do various
1311
	 * {@see \Prado\Collections\TWeakCallableCollection} operations to append, insert or remove
1312 1
	 * event handlers. You may also do these operations like
1313
	 * getting and setting properties, e.g.,
1314 1
	 * ```php
1315 1
	 *    $component->OnClick[] = array($object,'buttonClicked');
1316
	 *    $component->OnClick->insertAt(0,array($object,'buttonClicked'));
1317
	 *    $component->OnClick[] = function ($sender, $param) { ... };
1318
	 * ```
1319
	 * which are equivalent to the following
1320
	 * ```php
1321
	 *    $component->getEventHandlers('OnClick')->add(array($object,'buttonClicked'));
1322
	 *    $component->getEventHandlers('OnClick')->insertAt(0,array($object,'buttonClicked'));
1323
	 * ```
1324
	 *
1325
	 * Due to the nature of {@see getEventHandlers}, any active behaviors defining
1326 6
	 * new 'on' events, this method will pass through to the behavior transparently.
1327
	 *
1328 6
	 * @param string $name the event name
1329 6
	 * @param callable $handler the event handler
1330
	 * @param null|numeric $priority the priority of the handler, defaults to null which translates into the
1331 1
	 * default priority of 10.0 within {@see \Prado\Collections\TWeakCallableCollection}
1332
	 * @throws TInvalidOperationException if the event does not exist
1333
	 */
1334
	public function attachEventHandler($name, $handler, $priority = null)
1335
	{
1336
		$this->getEventHandlers($name)->add($handler, $priority);
1337
	}
1338
1339
	/**
1340
	 * Detaches an existing event handler.
1341
	 * This method is the opposite of {@see attachEventHandler}.  It will detach
1342 6
	 * any 'on' events defined by an objects active behaviors as well.
1343
	 * @param string $name event name
1344 6
	 * @param callable $handler the event handler to be removed
1345 6
	 * @param null|false|numeric $priority the priority of the handler, defaults to false which translates
1346
	 * to an item of any priority within {@see \Prado\Collections\TWeakCallableCollection}; null means the default priority
1347 1
	 * @return bool if the removal is successful
1348
	 */
1349
	public function detachEventHandler($name, $handler, $priority = false)
1350
	{
1351
		if ($this->hasEventHandler($name)) {
1352
			try {
1353
				$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\TWeakC...bleCollection::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

1353
				$this->getEventHandlers($name)->remove($handler, /** @scrutinizer ignore-type */ $priority);
Loading history...
1354
				return true;
1355
			} catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1356
			}
1357
		}
1358
		return false;
1359
	}
1360
1361
	/**
1362
	 * Raises an event.  This raises both inter-object 'on' events and global 'fx' events.
1363
	 * This method represents the happening of an event and will
1364
	 * invoke all attached event handlers for the event in {@see \Prado\Collections\TWeakCallableCollection} order.
1365
	 * This method does not handle intra-object/behavior dynamic 'dy' events.
1366
	 *
1367
	 * There are ways to handle event responses.  By default {@see EVENT_RESULT_FILTER},
1368 6
	 * all event responses are stored in an array, filtered for null responses, and returned.
1369
	 * If {@see EVENT_RESULT_ALL} is specified, all returned results will be stored along
1370 6
	 * with the sender and param in an array
1371 5
	 * ```php
1372
	 * 		$result[] = array('sender'=>$sender,'param'=>$param,'response'=>$response);
1373 6
	 * ```
1374
	 *
1375
	 * If {@see EVENT_RESULT_FEED_FORWARD} is specified, then each handler result is then
1376
	 * fed forward as the parameters for the next event.  This allows for events to filter data
1377 6
	 * directly by affecting the event parameters
1378
	 *
1379
	 * If a callable function is set in the response type or the post function filter is specified then the
1380 6
	 * result of each called event handler is post processed by the callable function.  Used in
1381 6
	 * combination with {@see EVENT_RESULT_FEED_FORWARD}, any event (and its result) can be chained.
1382 1
	 *
1383
	 * When raising a global 'fx' event, registered handlers in the global event list for
1384 6
	 * {@see GLOBAL_RAISE_EVENT_LISTENER} are always added into the set of event handlers.  In this way,
1385 6
	 * these global events are always raised for every global 'fx' event.  The registered handlers for global
1386
	 * raiseEvent events have priorities.  Any registered global raiseEvent event handlers with a priority less than zero
1387 6
	 * are added before the main event handlers being raised and any registered global raiseEvent event handlers
1388 1
	 * with a priority equal or greater than zero are added after the main event handlers being raised.  In this way
1389
	 * all {@see GLOBAL_RAISE_EVENT_LISTENER} handlers are always called for every raised 'fx' event.
1390 6
	 *
1391 6
	 * Behaviors may implement the following functions with TBehaviors:
1392 6
	 * ```php
1393 6
	 *	public function dyPreRaiseEvent($name, $sender, $param, $responsetype, $postfunction[, TCallChain $chain) {
1394
	 *      ....  //  Your logic
1395
	 *  	return $chain->dyPreRaiseEvent($name, $sender, $param, $responsetype, $postfunction); //eg, the event name may be filtered/changed
1396
	 *  }
1397
	 *	public function dyIntraRaiseEventTestHandler($handler, $sender, $param, $name, TCallChain $chain) {
1398
	 *      ....  //  Your logic
1399
	 *  	return $chain->dyIntraRaiseEventTestHandler($handler, $sender, $param, $name); //should this particular handler be executed?  true/false
1400
	 *  }
1401
	 *  public function dyIntraRaiseEventPostHandler($name, $sender, $param, $handler, $response, TCallChain $chain) {
1402
	 *      ....  //  Your logic
1403
	 *		return $chain->dyIntraRaiseEventPostHandler($name, $sender, $param, $handler, $response); //contains the per handler response
1404
	 *  }
1405
	 *  public function dyPostRaiseEvent($responses, $name, $sender, $param,$ responsetype, $postfunction, TCallChain $chain) {
1406
	 *      ....  //  Your logic
1407
	 *		return $chain->dyPostRaiseEvent($responses, $name, $sender, $param,$ responsetype, $postfunction);
1408
	 *  }
1409 6
	 * ```
1410
	 * to be executed when raiseEvent is called.  The 'intra' dynamic events are called per handler in
1411 6
	 * the handler loop.  TClassBehaviors prepend the object being raised.
1412 5
	 *
1413
	 * dyPreRaiseEvent has the effect of being able to change the event being raised.  This intra
1414 6
	 * object/behavior event returns the name of the desired event to be raised.  It will pass through
1415
	 * if no dynamic event is specified, or if the original event name is returned.
1416
	 * dyIntraRaiseEventTestHandler returns true or false as to whether a specific handler should be
1417
	 * called for a specific raised event (and associated event arguments)
1418 6
	 * dyIntraRaiseEventPostHandler does not return anything.  This allows behaviors to access the results
1419 6
	 * of an event handler in the per handler loop.
1420
	 * dyPostRaiseEvent returns the responses.  This allows for any post processing of the event
1421
	 * results from the sum of all event handlers
1422 6
	 *
1423
	 * When handling a catch-all {@see __dycall}, the method name is the name of the event
1424
	 * and the parameters are the sender, the param, and then the name of the event.
1425 6
	 *
1426 6
	 * In the rare circumstance that the event handlers need to be raised in reverse order, then
1427 6
	 * specifying {@see \Prado\TEventResults::EVENT_REVERSE} can be used to reverse the order of the
1428 6
	 * handlers.
1429 6
	 *
1430
	 * @param string $name the event name
1431
	 * @param mixed $sender the event sender object
1432
	 * @param \Prado\TEventParameter $param the event parameter
1433
	 * @param null|numeric $responsetype how the results of the event are tabulated.  default: {@see EVENT_RESULT_FILTER}  The default filters out
1434
	 *		null responses. optional
1435
	 * @param null|callable $postfunction any per handler filtering of the response result needed is passed through
1436
	 *		this if not null. default: null.  optional
1437
	 * @throws TInvalidOperationException if the event is undefined
1438
	 * @throws TInvalidDataValueException If an event handler is invalid
1439 8
	 * @return mixed the results of the event
1440
	 */
1441 8
	public function raiseEvent($name, $sender, $param, $responsetype = null, $postfunction = null)
1442
	{
1443
		$p = $param;
0 ignored issues
show
Unused Code introduced by
The assignment to $p is dead and can be removed.
Loading history...
1444
		if (is_callable($responsetype)) {
1445
			$postfunction = $responsetype;
1446
			$responsetype = null;
1447
		}
1448
1449
		if ($responsetype === null) {
1450
			$responsetype = TEventResults::EVENT_RESULT_FILTER;
1451
		}
1452
1453
		$name = strtolower($name);
1454
		$responses = [];
1455
1456
		if($param instanceof IEventParameter) {
0 ignored issues
show
introduced by
$param is always a sub-type of Prado\IEventParameter.
Loading history...
1457
			$param->setEventName($name);
1458
		}
1459
1460
		$this->callBehaviorsMethod('dyPreRaiseEvent', $name, $name, $sender, $param, $responsetype, $postfunction);
1461
1462
		if ($this->hasEventHandler($name) || $this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER)) {
1463 6
			$handlers = $this->getEventHandlers($name);
1464
			$handlerArray = $handlers->toArray();
1465 6
			if (strncasecmp($name, 'fx', 2) === 0 && $this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER)) {
1466 6
				$globalhandlers = $this->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER);
1467
				$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

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

1467
				$handlerArray = array_merge($globalhandlers->toArrayBelowPriority(0), $handlerArray, $globalhandlers->toArrayAbovePriority(/** @scrutinizer ignore-type */ 0));
Loading history...
1468 6
			}
1469 6
			$response = null;
1470 6
			if ($responsetype & TEventResults::EVENT_REVERSE) {
1471 2
				$handlerArray = array_reverse($handlerArray);
1472
			}
1473
			foreach ($handlerArray as $handler) {
1474 6
				$this->callBehaviorsMethod('dyIntraRaiseEventTestHandler', $return, $handler, $sender, $param, $name);
1475 6
				if ($return === false) {
1476 1
					continue;
1477
				}
1478 6
1479 6
				if (is_string($handler)) {
1480
					if (($pos = strrpos($handler, '.')) !== false) {
1481
						$object = $this->getSubProperty(substr($handler, 0, $pos));
1482
						$method = substr($handler, $pos + 1);
1483 6
						if (Prado::method_visible($object, $method) || strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1484
							if ($method == '__dycall') {
1485
								$response = $object->__dycall($name, [$sender, $param]);
1486
							} else {
1487
								$response = $object->$method($sender, $param);
1488
							}
1489
						} else {
1490
							throw new TInvalidDataValueException('component_eventhandler_invalid', $this::class, $name, $handler);
1491
						}
1492
					} else {
1493
						$response = call_user_func($handler, $sender, $param);
1494 37
					}
1495
				} elseif (is_callable($handler, true)) {
1496 37
					if (is_object($handler) || is_string($handler[0])) {
1497 6
						$response = call_user_func($handler, $sender, $param);
1498 4
					} else {
1499
						[$object, $method] = $handler;
1500 6
						if (($pos = strrpos($method, '.')) !== false) {
1501
							$object = $object->getSubProperty(substr($method, 0, $pos));
1502
							$method = substr($method, $pos + 1);
1503 37
						}
1504
						if (Prado::method_visible($object, $method) || strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1505
							if ($method == '__dycall') {
1506
								$response = $object->__dycall($name, [$sender, $param]);
1507
							} else {
1508
								$response = $object->$method($sender, $param);
1509
							}
1510
						} else {
1511
							throw new TInvalidDataValueException('component_eventhandler_invalid', $this::class, $name, $handler[1]);
1512
						}
1513 1
					}
1514
				} else {
1515 1
					throw new TInvalidDataValueException('component_eventhandler_invalid', $this::class, $name, gettype($handler));
1516 1
				}
1517 1
1518
				$this->callBehaviorsMethod('dyIntraRaiseEventPostHandler', $return, $name, $sender, $param, $handler, $response);
1519
1520 1
				if ($postfunction) {
1521
					$response = call_user_func_array($postfunction, [$sender, $param, $this, $response]);
1522
				}
1523
1524 1
				if ($responsetype & TEventResults::EVENT_RESULT_ALL) {
1525
					$responses[] = ['sender' => $sender, 'param' => $param, 'response' => $response];
1526
				} else {
1527
					$responses[] = $response;
1528
				}
1529
1530 1
				if ($response !== null && ($responsetype & TEventResults::EVENT_RESULT_FEED_FORWARD)) {
1531
					$param = $response;
1532 1
				}
1533 1
			}
1534 1
		} elseif (strncasecmp($name, 'on', 2) === 0 && !$this->hasEvent($name)) {
1535
			throw new TInvalidOperationException('component_event_undefined', $this::class, $name);
1536 1
		}
1537
1538 1
		if ($responsetype & TEventResults::EVENT_RESULT_FILTER) {
1539
			$responses = array_filter($responses);
1540
		}
1541
1542
		$this->callBehaviorsMethod('dyPostRaiseEvent', $responses, $responses, $name, $sender, $param, $responsetype, $postfunction);
1543
1544
		return $responses;
1545
	}
1546
1547
	/**
1548
	 * Evaluates a PHP expression in the context of this control.
1549
	 *
1550
	 * Behaviors may implement the function:
1551
	 * ```php
1552
	 *	public function dyEvaluateExpressionFilter($expression, TCallChain $chain) {
1553
	 * 		return $chain->dyEvaluateExpressionFilter(str_replace('foo', 'bar', $expression)); //example
1554
	 * }
1555
	 * ```
1556
	 * to be executed when evaluateExpression is called.  All attached behaviors are notified through
1557
	 * dyEvaluateExpressionFilter.  The chaining is important in this function due to the filtering
1558
	 * pass-through effect.
1559
	 *
1560
	 * @param string $expression PHP expression
1561 27
	 * @throws TInvalidOperationException if the expression is invalid
1562
	 * @return mixed the expression result
1563 27
	 */
1564 3
	public function evaluateExpression($expression)
1565
	{
1566 27
		$this->callBehaviorsMethod('dyEvaluateExpressionFilter', $expression, $expression);
1567 1
		try {
1568
			return eval("return $expression;");
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1569 27
		} 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...
1570 26
			throw new TInvalidOperationException('component_expression_invalid', $this::class, $expression, $e->getMessage());
1571
		}
1572 27
	}
1573 27
1574
	/**
1575 27
	 * Evaluates a list of PHP statements.
1576 27
	 *
1577 27
	 * Behaviors may implement the function:
1578 27
	 * ```php
1579
	 *	public function dyEvaluateStatementsFilter($statements, TCallChain $chain) {
1580
	 * 		return $chain->dyEvaluateStatementsFilter(str_replace('foo', 'bar', $statements)); //example
1581
	 * }
1582
	 * ```
1583
	 * to be executed when evaluateStatements is called.  All attached behaviors are notified through
1584
	 * dyEvaluateStatementsFilter.  The chaining is important in this function due to the filtering
1585
	 * pass-through effect.
1586
	 *
1587
	 * @param string $statements PHP statements
1588
	 * @throws TInvalidOperationException if the statements are invalid
1589
	 * @return string content echoed or printed by the PHP statements
1590
	 */
1591
	public function evaluateStatements($statements)
1592
	{
1593
		$this->callBehaviorsMethod('dyEvaluateStatementsFilter', $statements, $statements);
1594
		try {
1595
			ob_start();
1596
			if (eval($statements) === false) {
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1597
				throw new \Exception('');
1598 18
			}
1599
			$content = ob_get_contents();
1600 18
			ob_end_clean();
1601 18
			return $content;
1602 18
		} catch (\Exception $e) {
1603 18
			throw new TInvalidOperationException('component_statements_invalid', $this::class, $statements, $e->getMessage());
1604 18
		}
1605 18
	}
1606
1607
	/**
1608
	 * This method is invoked after the component is instantiated by a template.
1609
	 * When this method is invoked, the component's properties have been initialized.
1610
	 * The default implementation of this method will invoke
1611
	 * the potential parent component's {@see addParsedObject}.
1612
	 * This method can be overridden.
1613
	 *
1614
	 * Behaviors may implement the function:
1615
	 * ```php
1616
	 *	public function dyCreatedOnTemplate($parent, TCallChain $chain) {
1617
	 * 		return $chain->dyCreatedOnTemplate($parent); //example
1618
	 *  }
1619
	 * ```
1620
	 * to be executed when createdOnTemplate is called.  All attached behaviors are notified through
1621 8
	 * dyCreatedOnTemplate.
1622
	 *
1623 8
	 * @param \Prado\TComponent $parent potential parent of this control
1624 8
	 * @see addParsedObject
1625 8
	 */
1626
	public function createdOnTemplate($parent)
1627 8
	{
1628
		$this->callBehaviorsMethod('dyCreatedOnTemplate', $parent, $parent);
1629
		$parent->addParsedObject($this);
1630
	}
1631
1632
	/**
1633
	 * Processes an object that is created during parsing template.
1634
	 * The object can be either a component or a static text string.
1635
	 * This method can be overridden to customize the handling of newly created objects in template.
1636
	 * Only framework developers and control developers should use this method.
1637
	 *
1638
	 * Behaviors may implement the function:
1639
	 * ```php
1640
	 *	public function dyAddParsedObject($object, TCallChain $chain) {
1641 8
	 *      return $chain-> dyAddParsedObject($object);
1642
	 *  }
1643 8
	 * ```
1644 8
	 * to be executed when addParsedObject is called.  All attached behaviors are notified through
1645 8
	 * dyAddParsedObject.
1646
	 *
1647 8
	 * @param \Prado\TComponent|string $object text string or component parsed and instantiated in template
1648
	 * @see createdOnTemplate
1649
	 */
1650
	public function addParsedObject($object)
1651
	{
1652
		$this->callBehaviorsMethod('dyAddParsedObject', $return, $object);
1653
	}
1654
1655
	/**
1656
	 *This is the method registered for all instanced objects should a class behavior be added after
1657
	 * the class is instanced.  Only when the class to which the behavior is being added is in this
1658
	 * object's class hierarchy, via {@see getClassHierarchy}, is the behavior added to this instance.
1659
	 * @param mixed $sender the application
1660
	 * @param TClassBehaviorEventParameter $param
1661
	 * @since 3.2.3
1662
	 */
1663
	public function fxAttachClassBehavior($sender, $param)
1664
	{
1665
		if ($this->isa($param->getClass())) {
1666
			if (($behavior = $param->getBehavior()) instanceof IBehavior) {
1667
				$behavior = clone $behavior;
1668
			}
1669
			return $this->attachBehavior($param->getName(), $behavior, $param->getPriority());
1670
		}
1671
	}
1672
1673
	/**
1674
	 *	This is the method registered for all instanced objects should a class behavior be removed after
1675
	 * the class is instanced.  Only when the class to which the behavior is being added is in this
1676 16
	 * object's class hierarchy, via {@see getClassHierarchy}, is the behavior removed from this instance.
1677
	 * @param mixed $sender the application
1678 16
	 * @param TClassBehaviorEventParameter $param
1679 16
	 * @since 3.2.3
1680 16
	 */
1681 16
	public function fxDetachClassBehavior($sender, $param)
1682 16
	{
1683
		if ($this->isa($param->getClass())) {
1684 1
			return $this->detachBehavior($param->getName(), $param->getPriority());
1685
		}
1686 2
	}
1687
1688
	/**
1689
	 * instanceBehavior is an internal method that takes a Behavior Object, a class name, or array of
1690
	 * ['class' => 'MyBehavior', 'property1' => 'Value1'...] and creates a Behavior in return. eg.
1691
	 * ```php
1692
	 *		$b = $this->instanceBehavior('MyBehavior');
1693
	 * 		$b = $this->instanceBehavior(['class' => 'MyBehavior', 'property1' => 'Value1']);
1694
	 * 		$b = $this->instanceBehavior(new MyBehavior);
1695
	 * ```
1696
	 * If the behavior is an array, the key IBaseBehavior::CONFIG_KEY is stripped and used to initialize
1697
	 * the behavior.
1698
	 *
1699
	 * @param array|IBaseBehavior|string $behavior string, Behavior, or array of ['class' => 'MyBehavior', 'property1' => 'Value1' ...].
1700
	 * @throws TInvalidDataTypeException if the behavior is not an {@see \Prado\Util\IBaseBehavior}
1701
	 * @return IBaseBehavior&TComponent an instance of $behavior or $behavior itself
1702
	 * @since 4.2.0
1703
	 */
1704 16
	protected static function instanceBehavior($behavior)
1705
	{
1706 16
		$config = null;
1707 16
		$isArray = false;
1708 16
		$init = false;
1709 16
		if (is_string($behavior) || (($isArray = is_array($behavior)) && isset($behavior['class']))) {
1710 16
			if ($isArray && array_key_exists(IBaseBehavior::CONFIG_KEY, $behavior)) {
0 ignored issues
show
Bug introduced by
It seems like $behavior can also be of type string; however, parameter $array of array_key_exists() does only seem to accept ArrayObject|array, maybe add an additional type check? ( Ignorable by Annotation )

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

1710
			if ($isArray && array_key_exists(IBaseBehavior::CONFIG_KEY, /** @scrutinizer ignore-type */ $behavior)) {
Loading history...
1711
				$config = $behavior[IBaseBehavior::CONFIG_KEY];
1712 1
				unset($behavior[IBaseBehavior::CONFIG_KEY]);
1713
			}
1714 2
			$behavior = Prado::createComponent($behavior);
1715
			$init = true;
1716
		}
1717
		if (!($behavior instanceof IBaseBehavior)) {
1718
			throw new TInvalidDataTypeException('component_not_a_behavior', $behavior::class);
1719
		}
1720
		if ($init) {
1721
			$behavior->init($config);
1722 5
		}
1723
		return $behavior;
1724 5
	}
1725 5
1726 5
1727 5
	/**
1728 5
	 *	This will add a class behavior to all classes instanced (that are listening) and future newly instanced objects.
1729
	 * This registers the behavior for future instances and pushes the changes to all the instances that are listening as well.
1730
	 * The universal class behaviors are stored in an inverted stack with the latest class behavior being at the first position in the array.
1731
	 * This is done so class behaviors are added last first.
1732
	 * @param string $name name the key of the class behavior
1733
	 * @param object|string $behavior class behavior or name of the object behavior per instance
1734
	 * @param null|array|IBaseBehavior|string $class string of class or class on which to attach this behavior.  Defaults to null which will error
1735
	 *	but more important, if this is on PHP 5.3 it will use Late Static Binding to derive the class
1736
	 * it should extend.
1737
	 * ```php
1738 5
	 *   TPanel::attachClassBehavior('javascripts', new TJsPanelClassBehavior());
1739
	 *   TApplication::attachClassBehavior('jpegize', \Prado\Util\Behaviors\TJPEGizeAssetBehavior::class, \Prado\Web\TFileAsset::class);
1740 5
	 * ```
1741 5
	 * An array is used to initialize values of the behavior. eg. ['class' => '\\MyBehavior', 'property' => 'value'].
1742
	 * @param null|numeric $priority priority of behavior, default: null the default
1743 5
	 *  priority of the {@see \Prado\Collections\TWeakCallableCollection}  Optional.
1744 5
	 * @throws TInvalidOperationException if the class behavior is being added to a
1745
	 *  {@see \Prado\TComponent}; due to recursion.
1746 5
	 * @throws TInvalidOperationException if the class behavior is already defined
1747 5
	 * @return array|object the behavior if its an IClassBehavior and an array of all
1748
	 * behaviors that have been attached from 'fxAttachClassBehavior' when the Class
1749 5
	 * Behavior being attached is a per instance IBaseBehavior.
1750 5
	 * @since 3.2.3
1751
	 */
1752 5
	public static function attachClassBehavior($name, $behavior, $class = null, $priority = null)
1753
	{
1754
		if (!$class) {
1755
			$class = get_called_class();
1756
		}
1757
		if (!$class) {
1758
			throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
1759
		}
1760
1761
		$class = strtolower($class);
1762
		if ($class === strtolower(TComponent::class)) {
1763
			throw new TInvalidOperationException('component_no_tcomponent_class_behaviors');
1764
		}
1765
		if (empty(self::$_um[$class])) {
1766
			self::$_um[$class] = [];
1767
		}
1768
		$name = strtolower($name !== null ? $name : '');
0 ignored issues
show
introduced by
The condition $name !== null is always true.
Loading history...
1769
		if (!empty($name) && !is_numeric($name) && isset(self::$_um[$class][$name])) {
1770
			throw new TInvalidOperationException('component_class_behavior_defined', $class, $name);
1771
		}
1772
		$behaviorObject = self::instanceBehavior($behavior);
1773
		$behaviorObject->setName($name);
1774
		$isClassBehavior = $behaviorObject instanceof \Prado\Util\IClassBehavior;
1775
		$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

1775
		$param = new TClassBehaviorEventParameter($class, $name, /** @scrutinizer ignore-type */ $isClassBehavior ? $behaviorObject : $behavior, $priority);
Loading history...
1776
		if (empty($name) || is_numeric($name)) {
1777
			self::$_um[$class][] = $param;
1778
		} else {
1779
			self::$_um[$class] = [$name => $param] + self::$_um[$class];
1780
		}
1781
		$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 said class. However, the method does not exist in Prado\Util\IBehavior or Prado\Util\IClassBehavior. Are you sure you never get one of those? ( Ignorable by Annotation )

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

1781
		/** @scrutinizer ignore-call */ 
1782
  $results = $behaviorObject->raiseEvent('fxAttachClassBehavior', null, $param);
Loading history...
1782
		return $isClassBehavior ? $behaviorObject : $results;
1783
	}
1784
1785
	/**
1786
	 *	This will remove a behavior from a class.  It unregisters it from future instances and
1787
	 * pulls the changes from all the instances that are listening as well.
1788
	 * PHP 5.3 uses Late Static Binding to derive the static class upon which this method is called.
1789
	 * @param string $name the key of the class behavior
1790
	 * @param string $class class on which to attach this behavior.  Defaults to null.
1791
	 * @param null|false|numeric $priority priority: false is any priority, null is default
1792
	 *		{@see \Prado\Collections\TWeakCallableCollection} priority, and numeric is a specific priority.
1793
	 * @throws TInvalidOperationException if the the class cannot be derived from Late Static Binding and is not
1794
	 * not supplied as a parameter.
1795
	 * @return null|array|object the behavior if its an IClassBehavior and an array of all behaviors
1796
	 * that have been detached from 'fxDetachClassBehavior' when the Class Behavior being
1797
	 * attached is a per instance IBehavior.  Null if no behavior of $name to detach.
1798
	 * @since 3.2.3
1799
	 */
1800
	public static function detachClassBehavior($name, $class = null, $priority = false)
1801
	{
1802
		if (!$class) {
1803
			$class = get_called_class();
1804
		}
1805
		if (!$class) {
1806
			throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
1807
		}
1808
1809
		$class = strtolower($class);
1810
		$name = strtolower($name);
1811
		if (empty(self::$_um[$class]) || !isset(self::$_um[$class][$name])) {
1812
			return null;
1813
		}
1814
		$param = self::$_um[$class][$name];
1815
		$behavior = $param->getBehavior();
1816
		$behaviorObject = self::instanceBehavior($behavior);
1817
		$behaviorObject->setName($name);
1818
		$isClassBehavior = $behaviorObject instanceof IClassBehavior;
1819
		unset(self::$_um[$class][$name]);
1820
		if(empty(self::$_um[$class])) {
1821
			unset(self::$_um[$class]);
1822
		}
1823
		$results = $behaviorObject->raiseEvent('fxDetachClassBehavior', null, $param);
1824
		return $isClassBehavior ? $behaviorObject : $results;
1825
	}
1826
1827
	/**
1828
	 * Returns the named behavior object.  If the $behaviorname is not found, but is
1829
	 * an existing class or interface, this will return the first instanceof.
1830
	 * The name 'asa' stands for 'as a'.
1831
	 * @param string $behaviorname the behavior name or the class name of the behavior.
1832
	 * @return object the behavior object of name or class, or null if the behavior does not exist
1833
	 * @since 3.2.3
1834
	 */
1835
	public function asa($behaviorname)
1836
	{
1837
		$behaviorname = strtolower($behaviorname);
1838
		if (isset($this->_m[$behaviorname])) {
1839
			return $this->_m[$behaviorname];
1840
		}
1841
		if ((class_exists($behaviorname, false) || interface_exists($behaviorname, false)) && $this->_m) {
1842
			foreach($this->_m->toArray() as $behavior) {
1843
				if ($behavior instanceof $behaviorname) {
1844
					return $behavior;
1845
				}
1846
			}
1847
		}
1848
		return null;
1849
	}
1850
1851
	/**
1852
	 * Returns whether or not the object or any of the behaviors are of a particular class.
1853
	 * The name 'isa' stands for 'is a'.  This first checks if $this is an instanceof the class.
1854
	 * Then it checks if the $class is in the hierarchy, which includes first level traits.
1855
	 * It then checks each Behavior.  If a behavior implements {@see \Prado\Util\IInstanceCheck},
1856
	 * then the behavior can determine what it is an instanceof.  If this behavior function returns true,
1857
	 * then this method returns true.  If the behavior instance checking function returns false,
1858
	 * then no further checking is performed as it is assumed to be correct.
1859
	 *
1860
	 * If the behavior instance check function returns nothing or null or the behavior
1861
	 * doesn't implement the {@see \Prado\Util\IInstanceCheck} interface, then the default instanceof occurs.
1862
	 * The default isa behavior is to check if the behavior is an instanceof the class.
1863
	 *
1864
	 * The behavior {@see \Prado\Util\IInstanceCheck} is to allow a behavior to have the host object
1865
	 * act as a completely different object.
1866
	 *
1867
	 * @param mixed|string $class class or string
1868
	 * @return bool whether or not the object or a behavior is an instance of a particular class
1869
	 * @since 3.2.3
1870
	 */
1871
	public function isa($class)
1872
	{
1873
		if ($this instanceof $class || in_array(strtolower(is_object($class) ? $class::class : $class), $this->getClassHierarchy(true))) {
1874
			return true;
1875
		}
1876
		if ($this->_m !== null && $this->getBehaviorsEnabled()) {
1877
			foreach ($this->_m->toArray() as $behavior) {
1878
				if (!$behavior->getEnabled()) {
1879
					continue;
1880
				}
1881
1882
				$check = null;
1883
				if (($behavior->isa(\Prado\Util\IInstanceCheck::class)) && $check = $behavior->isinstanceof($class, $this)) {
1884
					return true;
1885
				}
1886
				if ($check === null && ($behavior->isa($class))) {
1887
					return true;
1888
				}
1889
			}
1890
		}
1891
		return false;
1892
	}
1893
1894
	/**
1895
	 * Returns all the behaviors attached to the TComponent.  IBaseBehavior[s] may
1896
	 * be attached but not {@see \Prado\Util\IBaseBehavior::getEnabled Enabled}.
1897
	 * @param ?string $class Filters the result by class, default null for no filter.
1898
	 * @return array The behaviors [optionally filtered] attached to the TComponent.
1899
	 * @since 4.2.2
1900
	 */
1901
	public function getBehaviors(?string $class = null)
1902
	{
1903
		if ($class === null) {
1904
			return isset($this->_m) ? $this->_m->toArray() : [];
1905
		} elseif (class_exists($class, false) || interface_exists($class, false)) {
1906
			return array_filter($this->_m->toArray(), fn ($b) => $b instanceof $class);
1907
		}
1908
		return [];
1909
	}
1910
1911
	/**
1912
	 * Attaches a list of behaviors to the component.
1913
	 * Each behavior is indexed by its name and should be an instance of
1914
	 * {@see \Prado\Util\IBaseBehavior}, a string specifying the behavior class, or a
1915
	 * {@see \Prado\Util\TClassBehaviorEventParameter}.
1916
	 * @param array $behaviors list of behaviors to be attached to the component
1917
	 * @param bool $cloneIBehavior Should IBehavior be cloned before attaching.
1918
	 *   Default is false.
1919
	 * @since 3.2.3
1920
	 */
1921
	public function attachBehaviors($behaviors, bool $cloneIBehavior = false)
1922
	{
1923
		foreach ($behaviors as $name => $behavior) {
1924
			if ($behavior instanceof TClassBehaviorEventParameter) {
1925
				$paramBehavior = $behavior->getBehavior();
1926
				if ($cloneIBehavior && ($paramBehavior instanceof IBehavior)) {
1927
					$paramBehavior = clone $paramBehavior;
1928
				}
1929
				$this->attachBehavior($behavior->getName(), $paramBehavior, $behavior->getPriority());
1930
			} else {
1931
				if ($cloneIBehavior && ($behavior instanceof IBehavior)) {
1932
					$behavior = clone $behavior;
1933
				}
1934
				$this->attachBehavior($name, $behavior);
1935
			}
1936
		}
1937
	}
1938
1939
	/**
1940
	 * Detaches select behaviors from the component.
1941
	 * Each behavior is indexed by its name and should be an instance of
1942
	 * {@see \Prado\Util\IBaseBehavior}, a string specifying the behavior class, or a
1943
	 * {@see \Prado\Util\TClassBehaviorEventParameter}.
1944
	 * @param array $behaviors list of behaviors to be detached from the component
1945
	 * @since 3.2.3
1946
	 */
1947
	public function detachBehaviors($behaviors)
1948
	{
1949
		if ($this->_m !== null) {
1950
			foreach ($behaviors as $name => $behavior) {
1951
				if ($behavior instanceof TClassBehaviorEventParameter) {
1952
					$this->detachBehavior($behavior->getName(), $behavior->getPriority());
1953
				} else {
1954
					$this->detachBehavior(is_string($behavior) ? $behavior : $name);
1955
				}
1956
			}
1957
		}
1958
	}
1959
1960
	/**
1961
	 * Detaches all behaviors from the component.
1962
	 * @since 3.2.3
1963
	 */
1964
	public function clearBehaviors()
1965
	{
1966
		if ($this->_m !== null) {
1967
			foreach ($this->_m->getKeys() as $name) {
1968
				$this->detachBehavior($name);
1969
			}
1970
			$this->_m = null;
1971
		}
1972
	}
1973
1974
	/**
1975
	 * Attaches a behavior to this component.
1976
	 * This method will create the behavior object based on the given
1977
	 * configuration. After that, the behavior object will be initialized
1978
	 * by calling its {@see \Prado\Util\IBaseBehavior::attach} method.
1979
	 *
1980
	 * Already attached behaviors may implement the function:
1981
	 * ```php
1982
	 *	public function dyAttachBehavior($name,$behavior[, ?TCallChain $chain = null]) {
1983
	 *      if ($chain)
1984
	 *          return $chain->dyDetachBehavior($name, $behavior);
1985
	 *  }
1986
	 * ```
1987
	 * to be executed when attachBehavior is called.  All attached behaviors are notified through
1988
	 * dyAttachBehavior.
1989
	 *
1990
	 * @param null|numeric|string $name the behavior's name. It should uniquely identify this behavior.
1991
	 * @param array|IBaseBehavior|string $behavior the behavior configuration. This is the name of the Behavior Class
1992
	 * instanced by {@see \Prado\PradoBase::createComponent}, or is a Behavior, or is an array of
1993
	 * ['class'=>'TBehavior' property1='value 1' property2='value2'...] with the class and properties
1994
	 * with values.
1995
	 * @param null|numeric $priority
1996
	 * @return IBaseBehavior the behavior object
1997
	 * @since 3.2.3
1998
	 */
1999
	public function attachBehavior($name, $behavior, $priority = null)
2000
	{
2001
		$name = strtolower($name !== null ? $name : '');
2002
		if ($this->_m && isset($this->_m[$name])) {
2003
			$this->detachBehavior($name);
2004
		}
2005
		$behavior = self::instanceBehavior($behavior);
2006
		if ($this->_m === null) {
2007
			$this->_m = new TPriorityMap();
2008
		}
2009
		if (empty($name) || is_numeric($name)) {
2010
			$name = $this->_m->getNextIntegerKey();
2011
		}
2012
		$this->_m->add($name, $behavior, $priority);
2013
		$behavior->setName($name);
2014
		$behavior->attach($this);
2015
		$this->callBehaviorsMethod('dyAttachBehavior', $return, $name, $behavior);
2016
		return $behavior;
2017
	}
2018
2019
	/**
2020
	 * Detaches a behavior from the component.
2021
	 * The behavior's {@see \Prado\Util\IBaseBehavior::detach} method will be invoked.
2022
	 *
2023
	 * Behaviors may implement the function:
2024
	 * ```php
2025
	 *	public function dyDetachBehavior($name, $behavior[, ?TCallChain $chain = null]) {
2026
	 *      if ($chain)
2027
	 *          return $chain->dyDetachBehavior($name, $behavior);
2028
	 *  }
2029
	 * ```
2030
	 * to be executed when detachBehavior is called.  All attached behaviors are notified through
2031
	 * dyDetachBehavior.
2032
	 *
2033
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
2034
	 * @param false|numeric $priority the behavior's priority. This defaults to false, which is any priority.
2035
	 * @return null|IBaseBehavior the detached behavior. Null if the behavior does not exist.
2036
	 * @since 3.2.3
2037
	 */
2038
	public function detachBehavior($name, $priority = false)
2039
	{
2040
		$name = strtolower($name);
2041
		if ($this->_m != null && ($behavior = $this->_m->itemAt($name, $priority))) {
2042
			$this->callBehaviorsMethod('dyDetachBehavior', $return, $name, $behavior);
2043
			$behavior->detach($this);
2044
			$this->_m->remove($name, $priority);
2045
			return $behavior;
2046
		}
2047
		return null;
2048
	}
2049
2050
	/**
2051
	 * Enables all behaviors attached to this component independent of the behaviors
2052
	 *
2053
	 * Behaviors may implement the function:
2054
	 * ```php
2055
	 *	public function dyEnableBehaviors([?TCallChain $chain = null]) {
2056
	 *      if ($chain)
2057
	 *          return $chain->dyEnableBehaviors();
2058
	 *  }
2059
	 * ```
2060
	 * to be executed when enableBehaviors is called.  All attached behaviors are notified through
2061
	 * dyEnableBehaviors.
2062
	 * @since 3.2.3
2063
	 */
2064
	public function enableBehaviors()
2065
	{
2066
		if (!$this->_behaviorsenabled) {
2067
			$this->_behaviorsenabled = true;
2068
			$this->callBehaviorsMethod('dyEnableBehaviors', $return);
2069
		}
2070
	}
2071
2072
	/**
2073
	 * Disables all behaviors attached to this component independent of the behaviors
2074
	 *
2075
	 * Behaviors may implement the function:
2076
	 * ```php
2077
	 *	public function dyDisableBehaviors([?TCallChain $chain = null]) {
2078
	 *      if ($chain)
2079
	 *          return $chain->dyDisableBehaviors();
2080
	 *  }
2081
	 * ```
2082
	 * to be executed when disableBehaviors is called.  All attached behaviors are notified through
2083
	 * dyDisableBehaviors.
2084
	 * @since 3.2.3
2085
	 */
2086
	public function disableBehaviors()
2087
	{
2088
		if ($this->_behaviorsenabled) {
2089
			$callchain = $this->getCallChain('dyDisableBehaviors');
2090
			$this->_behaviorsenabled = false;
2091
			if ($callchain) { // normal dynamic events won't work because behaviors are disabled.
2092
				$callchain->call();
2093
			}
2094
		}
2095
	}
2096
2097
2098
	/**
2099
	 * Returns if all the behaviors are turned on or off for the object.
2100
	 * @return bool whether or not all behaviors are enabled (true) or not (false)
2101
	 * @since 3.2.3
2102
	 */
2103
	public function getBehaviorsEnabled()
2104
	{
2105
		return $this->_behaviorsenabled;
2106
	}
2107
2108
	/**
2109
	 * Enables an attached object behavior.  This cannot enable or disable whole class behaviors.
2110
	 * A behavior is only effective when it is enabled.
2111
	 * A behavior is enabled when first attached.
2112
	 *
2113
	 * Behaviors may implement the function:
2114
	 * ```php
2115
	 *	public function dyEnableBehavior($name, $behavior[, ?TCallChain $chain = null]) {
2116
	 *      if ($chain)
2117
	 *          return $chain->dyEnableBehavior($name, $behavior);
2118
	 *  }
2119
	 * ```
2120
	 * to be executed when enableBehavior is called.  All attached behaviors are notified through
2121
	 * dyEnableBehavior.
2122
	 *
2123
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
2124
	 * @return bool Was the behavior found and enabled.
2125
	 * @since 3.2.3
2126
	 */
2127
	public function enableBehavior($name): bool
2128
	{
2129
		$name = strtolower($name);
2130
		if ($this->_m != null && isset($this->_m[$name])) {
2131
			$behavior = $this->_m[$name];
2132
			if ($behavior->getEnabled() === false) {
2133
				$behavior->setEnabled(true);
2134
				$this->callBehaviorsMethod('dyEnableBehavior', $return, $name, $behavior);
2135
			}
2136
			return true;
2137
		}
2138
		return false;
2139
	}
2140
2141
	/**
2142
	 * Disables an attached behavior.  This cannot enable or disable whole class behaviors.
2143
	 * A behavior is only effective when it is enabled.
2144
	 *
2145
	 * Behaviors may implement the function:
2146
	 * ```php
2147
	 *	public function dyDisableBehavior($name, $behavior[, ?TCallChain $chain = null]) {
2148
	 *      if ($chain)
2149
	 *          return $chain->dyDisableBehavior($name, $behavior);
2150
	 *  }
2151
	 * ```
2152
	 * to be executed when disableBehavior is called.  All attached behaviors are notified through
2153
	 * dyDisableBehavior.
2154
	 *
2155
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
2156
	 * @return bool Was the behavior found and disabled.
2157
	 * @since 3.2.3
2158
	 */
2159
	public function disableBehavior($name): bool
2160
	{
2161
		$name = strtolower($name);
2162
		if ($this->_m != null && isset($this->_m[$name])) {
2163
			$behavior = $this->_m[$name];
2164
			if ($behavior->getEnabled() === true) {
2165
				$behavior->setEnabled(false);
2166
				$this->callBehaviorsMethod('dyDisableBehavior', $return, $name, $behavior);
2167
			}
2168
			return true;
2169
		}
2170
		return false;
2171
	}
2172
2173
	/**
2174
	 * Returns an array with the names of all variables of that object that should be serialized.
2175
	 * Do not call this method. This is a PHP magic method that will be called automatically
2176
	 * prior to any serialization.
2177
	 */
2178
	public function __sleep()
2179
	{
2180
		$a = (array) $this;
2181
		$a = array_keys($a);
2182
		$exprops = [];
2183
		$this->_getZappableSleepProps($exprops);
2184
		return array_diff($a, $exprops);
2185
	}
2186
2187
	/**
2188
	 * Returns an array with the names of all variables of this object that should NOT be serialized
2189
	 * because their value is the default one or useless to be cached for the next page loads.
2190
	 * Reimplement in derived classes to add new variables, but remember to  also to call the parent
2191
	 * implementation first.
2192
	 * @param array $exprops by reference
2193
	 */
2194
	protected function _getZappableSleepProps(&$exprops)
2195
	{
2196
		if ($this->_listeningenabled === false) {
2197
			$exprops[] = "\0*\0_listeningenabled";
2198
		}
2199
		if ($this->_behaviorsenabled === true) {
2200
			$exprops[] = "\0*\0_behaviorsenabled";
2201
		}
2202
		$exprops[] = "\0*\0_e";
2203
		if ($this->_m === null) {
2204
			$exprops[] = "\0*\0_m";
2205
		}
2206
	}
2207
}
2208