Passed
Push — master ( bb2c5a...c1a196 )
by Fabio
06:48
created

TComponent::instanceBehavior()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6.027

Importance

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

600
				return $this->/** @scrutinizer ignore-call */ __dycall($method, $args);
Loading history...
601
			}
602
			return $args[0] ?? null;
603
		}
604
605
		// don't thrown an exception for __magicMethods() or any other weird methods natively implemented by php
606
		if (!method_exists($this, $method)) {
607
			throw new TApplicationException('component_method_undefined', get_class($this), $method);
608
		}
609
	}
610
611
612
	/**
613
	 * Returns a property value or an event handler list by property or event name.
614
	 * Do not call this method. This is a PHP magic method that we override
615
	 * to allow using the following syntax to read a property:
616
	 * <code>
617 95
	 * $value=$component->PropertyName;
618
	 * $value=$component->jsPropertyName; // return JavaScript literal
619 95
	 * </code>
620
	 * and to obtain the event handler list for an event,
621 84
	 * <code>
622 25
	 * $eventHandlerList=$component->EventName;
623
	 * </code>
624 2
	 * This will also return the global event handler list when specifing an 'fx'
625 23
	 * event,
626
	 * <code>
627 13
	 * $globalEventHandlerList=$component->fxEventName;
628 13
	 * </code>
629 12
	 * When behaviors are enabled, this will return the behavior of a specific
630
	 * name, a property of a behavior, or an object 'on' event defined by the behavior.
631 13
	 * @param string $name the property name or the event name
632 14
	 * @throws TInvalidOperationException if the property/event is not defined.
633
	 * @return mixed the property value or the event handler list as {@link TWeakCallableCollection}
634 3
	 */
635 3
	public function __get($name)
636 1
	{
637
		if (method_exists($this, $getter = 'get' . $name)) {
638 3
			// getting a property
639 13
			return $this->$getter();
640
		} elseif (method_exists($this, $jsgetter = 'getjs' . $name)) {
641 13
			// getting a javascript property
642 9
			return (string) $this->$jsgetter();
643 7
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
644 4
			// getting an event (handler list)
645 4
			$name = strtolower($name);
646 4
			if (!isset($this->_e[$name])) {
647 4
				$this->_e[$name] = new TWeakCallableCollection;
648
			}
649
			return $this->_e[$name];
650
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
651
			// getting a global event (handler list)
652 4
			$name = strtolower($name);
653
			if (!isset(self::$_ue[$name])) {
654
				self::$_ue[$name] = new TWeakCallableCollection;
655
			}
656
			return self::$_ue[$name];
657
		} elseif ($this->_behaviorsenabled) {
658
			// getting a behavior property/event (handler list)
659
			if (isset($this->_m[$name])) {
660
				return $this->_m[$name];
661
			} elseif ($this->_m !== null) {
662
				foreach ($this->_m->toArray() as $behavior) {
663
					if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) &&
664
						(property_exists($behavior, $name) || $behavior->canGetProperty($name) || $behavior->hasEvent($name))) {
665
						return $behavior->$name;
666
					}
667
				}
668
			}
669
		}
670 77
		throw new TInvalidOperationException('component_property_undefined', get_class($this), $name);
671
	}
672 77
673 76
	/**
674 2
	 * Sets value of a component property.
675
	 * Do not call this method. This is a PHP magic method that we override
676 76
	 * to allow using the following syntax to set a property or attach an event handler.
677 5
	 * <code>
678 1
	 * $this->PropertyName=$value;
679 1
	 * $this->jsPropertyName=$value; // $value will be treated as a JavaScript literal
680
	 * $this->EventName=$handler;
681 1
	 * $this->fxEventName=$handler; //global event listener
682 4
	 * </code>
683 3
	 * When behaviors are enabled, this will also set a behaviors properties and events.
684 4
	 * @param string $name the property name or event name
685 4
	 * @param mixed $value the property value or event handler
686 4
	 * @throws TInvalidOperationException If the property is not defined or read-only.
687 4
	 */
688 4
	public function __set($name, $value)
689 4
	{
690 4
		if (method_exists($this, $setter = 'set' . $name)) {
691
			if (strncasecmp($name, 'js', 2) === 0 && $value && !($value instanceof TJavaScriptLiteral)) {
692
				$value = new TJavaScriptLiteral($value);
693 4
			}
694 4
			return $this->$setter($value);
695
		} elseif (method_exists($this, $jssetter = 'setjs' . $name)) {
696
			if ($value && !($value instanceof TJavaScriptString)) {
697
				$value = new TJavaScriptString($value);
698 1
			}
699 1
			return $this->$jssetter($value);
700
		} elseif ((strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) || strncasecmp($name, 'fx', 2) === 0) {
701 1
			return $this->attachEventHandler($name, $value);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->attachEventHandler($name, $value) targeting Prado\TComponent::attachEventHandler() seems to always return null.

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

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

}

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

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

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

Loading history...
702
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->_behaviorsenabled) {
703
			$sets = 0;
704
			foreach ($this->_m->toArray() as $behavior) {
705
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) &&
706
					(property_exists($behavior, $name) || $behavior->canSetProperty($name) || $behavior->hasEvent($name))) {
707
					$behavior->$name = $value;
708
					$sets++;
709
				}
710
			}
711
			if ($sets) {
712
				return $value;
713
			}
714
		}
715
716
		if (method_exists($this, 'get' . $name) || method_exists($this, 'getjs' . $name)) {
717 5
			throw new TInvalidOperationException('component_property_readonly', get_class($this), $name);
718
		} else {
719 5
			throw new TInvalidOperationException('component_property_undefined', get_class($this), $name);
720 5
		}
721 3
	}
722 2
723 1
	/**
724 1
	 * Checks if a property value is null, there are no events in the object
725 1
	 * event list or global event list registered under the name, and, if
726 1
	 * behaviors are enabled,
727 1
	 * Do not call this method. This is a PHP magic method that we override
728 1
	 * to allow using isset() to detect if a component property is set or not.
729 1
	 * This also works for global events.  When behaviors are enabled, it
730 1
	 * will check for a behavior of the specified name, and also check
731 1
	 * the behavior for events and properties.
732
	 * @param string $name the property name or the event name
733 1
	 * @since 3.2.3
734 1
	 */
735 1
	public function __isset($name)
736
	{
737
		if (method_exists($this, $getter = 'get' . $name)) {
738
			return $this->$getter() !== null;
739 1
		} elseif (method_exists($this, $jsgetter = 'getjs' . $name)) {
740
			return $this->$jsgetter() !== null;
741
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
742
			$name = strtolower($name);
743
			return isset($this->_e[$name]) && $this->_e[$name]->getCount();
744
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
745
			$name = strtolower($name);
746
			return isset(self::$_ue[$name]) && self::$_ue[$name]->getCount();
747
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->_behaviorsenabled) {
748
			if (isset($this->_m[$name])) {
749
				return true;
750
			}
751
			foreach ($this->_m->toArray() as $behavior) {
752
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled())) {
753 3
					return isset($behavior->$name);
754
				}
755 3
			}
756 2
		}
757 3
		return false;
758 1
	}
759 2
760 2
	/**
761 2
	 * Sets a component property to be null.  Clears the object or global
762 1
	 * events. When enabled, loops through all behaviors and unsets the
763 2
	 * property or event.
764 2
	 * Do not call this method. This is a PHP magic method that we override
765 1
	 * to allow using unset() to set a component property to be null.
766
	 * @param string $name the property name or the event name
767 2
	 * @throws TInvalidOperationException if the property is read only.
768 2
	 * @since 3.2.3
769 2
	 */
770 2
	public function __unset($name)
771 2
	{
772
		if (method_exists($this, $setter = 'set' . $name)) {
773
			$this->$setter(null);
774 2
		} elseif (method_exists($this, $jssetter = 'setjs' . $name)) {
775 2
			$this->$jssetter(null);
776
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
777
			$this->_e[strtolower($name)]->clear();
778 1
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
779 1
			$this->getEventHandlers($name)->remove([$this, $name]);
780
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->_behaviorsenabled) {
781 3
			if (isset($this->_m[$name])) {
782
				$this->detachBehavior($name);
783
			} else {
784
				$unset = 0;
785
				foreach ($this->_m->toArray() as $behavior) {
786
					if ((!($behavior instanceof IBehavior) || $behavior->getEnabled())) {
787
						unset($behavior->$name);
788
						$unset++;
789
					}
790 1
				}
791
				if (!$unset && method_exists($this, 'get' . $name)) {
792 1
					throw new TInvalidOperationException('component_property_readonly', get_class($this), $name);
793
				}
794
			}
795
		} elseif (method_exists($this, 'get' . $name)) {
796
			throw new TInvalidOperationException('component_property_readonly', get_class($this), $name);
797
		}
798
	}
799
800
	/**
801
	 * Determines whether a property is defined.
802
	 * A property is defined if there is a getter or setter method
803
	 * defined in the class. Note, property names are case-insensitive.
804 9
	 * @param string $name the property name
805
	 * @return bool whether the property is defined
806 9
	 */
807 6
	public function hasProperty($name)
808 8
	{
809 2
		return $this->canGetProperty($name) || $this->canSetProperty($name);
810 2
	}
811 2
812
	/**
813
	 * Determines whether a property can be read.
814
	 * A property can be read if the class has a getter method
815 8
	 * for the property name. Note, property name is case-insensitive.
816
	 * This also checks for getjs.  When enabled, it loops through all
817
	 * active behaviors for the get property when undefined by the object.
818
	 * @param string $name the property name
819
	 * @return bool whether the property can be read
820
	 */
821
	public function canGetProperty($name)
822
	{
823
		if (method_exists($this, 'get' . $name) || method_exists($this, 'getjs' . $name)) {
824
			return true;
825
		} elseif ($this->_m !== null && $this->_behaviorsenabled) {
826
			foreach ($this->_m->toArray() as $behavior) {
827 11
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->canGetProperty($name)) {
828
					return true;
829 11
				}
830 8
			}
831 7
		}
832 2
		return false;
833 2
	}
834 2
835
	/**
836
	 * Determines whether a property can be set.
837
	 * A property can be written if the class has a setter method
838 7
	 * for the property name. Note, property name is case-insensitive.
839
	 * This also checks for setjs.  When enabled, it loops through all
840
	 * active behaviors for the set property when undefined by the object.
841
	 * @param string $name the property name
842
	 * @return bool whether the property can be written
843
	 */
844
	public function canSetProperty($name)
845
	{
846
		if (method_exists($this, 'set' . $name) || method_exists($this, 'setjs' . $name)) {
847
			return true;
848
		} elseif ($this->_m !== null && $this->_behaviorsenabled) {
849
			foreach ($this->_m->toArray() as $behavior) {
850
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->canSetProperty($name)) {
851 4
					return true;
852
				}
853 4
			}
854 4
		}
855 4
		return false;
856
	}
857 4
858
	/**
859
	 * Evaluates a property path.
860
	 * A property path is a sequence of property names concatenated by '.' character.
861
	 * For example, 'Parent.Page' refers to the 'Page' property of the component's
862
	 * 'Parent' property value (which should be a component also).
863
	 * When a property is not defined by an object, this also loops through all
864
	 * active behaviors of the object.
865
	 * @param string $path property path
866
	 * @return mixed the property path value
867
	 */
868
	public function getSubProperty($path)
869
	{
870 2
		$object = $this;
871
		foreach (explode('.', $path) as $property) {
872 2
			$object = $object->$property;
873 2
		}
874 1
		return $object;
875
	}
876 2
877 2
	/**
878
	 * Sets a value to a property path.
879 2
	 * A property path is a sequence of property names concatenated by '.' character.
880 2
	 * For example, 'Parent.Page' refers to the 'Page' property of the component's
881
	 * 'Parent' property value (which should be a component also).
882
	 * When a property is not defined by an object, this also loops through all
883
	 * active behaviors of the object.
884
	 * @param string $path property path
885
	 * @param mixed $value the property path value
886
	 */
887
	public function setSubProperty($path, $value)
888
	{
889
		$object = $this;
890
		if (($pos = strrpos($path, '.')) === false) {
891
			$property = $path;
892
		} else {
893
			$object = $this->getSubProperty(substr($path, 0, $pos));
894 176
			$property = substr($path, $pos + 1);
895
		}
896 176
		$object->$property = $value;
897 176
	}
898 3
899 3
	/**
900 3
	 * Determines whether an event is defined.
901 3
	 * An event is defined if the class has a method whose name is the event name
902
	 * prefixed with 'on', 'fx', or 'dy'.
903
	 * Every object responds to every 'fx' and 'dy' event as they are in a universally
904
	 * accepted event space.  'on' event must be declared by the object.
905 3
	 * When enabled, this will loop through all active behaviors for 'on' events
906
	 * defined by the behavior.
907
	 * Note, event name is case-insensitive.
908
	 * @param string $name the event name
909
	 * @return bool
910
	 */
911
	public function hasEvent($name)
912
	{
913
		if ((strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) || strncasecmp($name, 'fx', 2) === 0 || strncasecmp($name, 'dy', 2) === 0) {
914
			return true;
915 211
		} elseif ($this->_m !== null && $this->_behaviorsenabled) {
916
			foreach ($this->_m->toArray() as $behavior) {
917 211
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->hasEvent($name)) {
918 211
					return true;
919 211
				}
920
			}
921 178
		}
922 54
		return false;
923 177
	}
924 3
925 3
	/**
926 3
	 * Checks if an event has any handlers.  This function also checks through all
927
	 * the behaviors for 'on' events when behaviors are enabled.
928
	 * 'dy' dynamic events are not handled by this function.
929
	 * @param string $name the event name
930
	 * @return bool whether an event has been attached one or several handlers
931 176
	 */
932
	public function hasEventHandler($name)
933
	{
934
		$name = strtolower($name);
935
		if (strncasecmp($name, 'fx', 2) === 0) {
936
			return isset(self::$_ue[$name]) && self::$_ue[$name]->getCount() > 0;
937
		} else {
938
			if (isset($this->_e[$name]) && $this->_e[$name]->getCount() > 0) {
939
				return true;
940
			} elseif ($this->_m !== null && $this->_behaviorsenabled) {
941 92
				foreach ($this->_m->toArray() as $behavior) {
942
					if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->hasEventHandler($name)) {
943 92
						return true;
944 63
					}
945 63
				}
946 14
			}
947
		}
948 63
		return false;
949 38
	}
950 38
951 38
	/**
952 5
	 * Returns the list of attached event handlers for an 'on' or 'fx' event.   This function also
953
	 * checks through all the behaviors for 'on' event lists when behaviors are enabled.
954 38
	 * @param mixed $name
955 6
	 * @throws TInvalidOperationException if the event is not defined
956 6
	 * @return TWeakCallableCollection list of attached event handlers for an event
957 6
	 */
958 6
	public function getEventHandlers($name)
959
	{
960
		if (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
961
			$name = strtolower($name);
962 4
			if (!isset($this->_e[$name])) {
963
				$this->_e[$name] = new TWeakCallableCollection;
964
			}
965
			return $this->_e[$name];
966
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
967
			$name = strtolower($name);
968
			if (!isset(self::$_ue[$name])) {
969
				self::$_ue[$name] = new TWeakCallableCollection;
970
			}
971
			return self::$_ue[$name];
972
		} elseif ($this->_m !== null && $this->_behaviorsenabled) {
973
			foreach ($this->_m->toArray() as $behavior) {
974
				if ((!($behavior instanceof IBehavior) || $behavior->getEnabled()) && $behavior->hasEvent($name)) {
975
					return $behavior->getEventHandlers($name);
976
				}
977
			}
978
		}
979
		throw new TInvalidOperationException('component_event_undefined', get_class($this), $name);
980
	}
981
982
	/**
983
	 * Attaches an event handler to an event.
984
	 *
985
	 * The handler must be a valid PHP callback, i.e., a string referring to
986
	 * a global function name, or an array containing two elements with
987
	 * the first element being an object and the second element a method name
988
	 * of the object. In Prado, you can also use method path to refer to
989
	 * an event handler. For example, array($object,'Parent.buttonClicked')
990
	 * uses a method path that refers to the method $object->Parent->buttonClicked(...).
991
	 *
992
	 * The event handler must be of the following signature,
993
	 * <code>
994
	 * function handlerName($sender, $param) {}
995
	 * function handlerName($sender, $param, $name) {}
996
	 * </code>
997
	 * where $sender represents the object that raises the event,
998
	 * and $param is the event parameter. $name refers to the event name
999
	 * being handled.
1000
	 *
1001
	 * This is a convenient method to add an event handler.
1002
	 * It is equivalent to {@link getEventHandlers}($name)->add($handler).
1003
	 * For complete management of event handlers, use {@link getEventHandlers}
1004
	 * to get the event handler list first, and then do various
1005
	 * {@link TWeakCallableCollection} operations to append, insert or remove
1006
	 * event handlers. You may also do these operations like
1007
	 * getting and setting properties, e.g.,
1008
	 * <code>
1009
	 * $component->OnClick[]=array($object,'buttonClicked');
1010 44
	 * $component->OnClick->insertAt(0,array($object,'buttonClicked'));
1011
	 * </code>
1012 44
	 * which are equivalent to the following
1013 44
	 * <code>
1014
	 * $component->getEventHandlers('OnClick')->add(array($object,'buttonClicked'));
1015
	 * $component->getEventHandlers('OnClick')->insertAt(0,array($object,'buttonClicked'));
1016
	 * </code>
1017
	 *
1018
	 * Due to the nature of {@link getEventHandlers}, any active behaviors defining
1019
	 * new 'on' events, this method will pass through to the behavior transparently.
1020
	 *
1021
	 * @param string $name the event name
1022
	 * @param callable $handler the event handler
1023
	 * @param null|numeric $priority the priority of the handler, defaults to null which translates into the
1024
	 * default priority of 10.0 within {@link TWeakCallableCollection}
1025 38
	 * @throws TInvalidOperationException if the event does not exist
1026
	 */
1027 38
	public function attachEventHandler($name, $handler, $priority = null)
1028
	{
1029 38
		$this->getEventHandlers($name)->add($handler, $priority);
1030 38
	}
1031 1
1032
	/**
1033
	 * Detaches an existing event handler.
1034 2
	 * This method is the opposite of {@link attachEventHandler}.  It will detach
1035
	 * any 'on' events defined by an objects active behaviors as well.
1036
	 * @param string $name event name
1037
	 * @param callable $handler the event handler to be removed
1038
	 * @param null|false|numeric $priority the priority of the handler, defaults to false which translates
1039
	 * to an item of any priority within {@link TWeakCallableCollection}; null means the default priority
1040
	 * @return bool if the removal is successful
1041
	 */
1042
	public function detachEventHandler($name, $handler, $priority = false)
1043
	{
1044
		if ($this->hasEventHandler($name)) {
1045
			try {
1046
				$this->getEventHandlers($name)->remove($handler, $priority);
0 ignored issues
show
Bug introduced by
It seems like $priority can also be of type Prado\numeric; however, parameter $priority of Prado\Collections\TPriorityList::remove() does only seem to accept boolean|double|null, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

1141
		$name = $this->dyPreRaiseEvent($name, $sender, $param, /** @scrutinizer ignore-type */ $responsetype, $postfunction);
Loading history...
1142
1143
		if ($this->hasEventHandler($name) || $this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER)) {
1144
			$handlers = $this->getEventHandlers($name);
1145
			$handlerArray = $handlers->toArray();
1146
			if (strncasecmp($name, 'fx', 2) === 0 && $this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER)) {
1147
				$globalhandlers = $this->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER);
1148
				$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

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

1148
				$handlerArray = array_merge($globalhandlers->toArrayBelowPriority(0), $handlerArray, $globalhandlers->toArrayAbovePriority(/** @scrutinizer ignore-type */ 0));
Loading history...
1149
			}
1150
			$response = null;
1151
			foreach ($handlerArray as $handler) {
1152
				if ($this->dyIntraRaiseEventTestHandler($handler, $sender, $param, $name) === false) {
1153
					continue;
1154
				}
1155 59
1156 59
				if (is_string($handler)) {
1157 59
					if (($pos = strrpos($handler, '.')) !== false) {
1158
						$object = $this->getSubProperty(substr($handler, 0, $pos));
1159
						$method = substr($handler, $pos + 1);
1160 59
						if (method_exists($object, $method) || strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1161 1
							if ($method == '__dycall') {
1162 1
								$response = $object->__dycall($name, [$sender, $param, $name]);
1163
							} else {
1164 59
								$response = $object->$method($sender, $param, $name);
1165 59
							}
1166 1
						} else {
1167
							throw new TInvalidDataValueException('component_eventhandler_invalid', get_class($this), $name, $handler);
1168 59
						}
1169
					} else {
1170
						$response = call_user_func($handler, $sender, $param, $name);
1171 59
					}
1172
				} elseif (is_callable($handler, true)) {
1173
					[$object, $method] = $handler;
1174
					if (is_string($object)) {
1175
						$response = call_user_func($handler, $sender, $param, $name);
1176
					} else {
1177
						if (($pos = strrpos($method, '.')) !== false) {
1178 59
							$object = $object->getSubProperty(substr($method, 0, $pos));
1179
							$method = substr($method, $pos + 1);
1180 59
						}
1181 1
						if (method_exists($object, $method) || strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1182
							if ($method == '__dycall') {
1183
								$response = $object->__dycall($name, [$sender, $param, $name]);
1184 59
							} else {
1185 2
								$response = $object->$method($sender, $param, $name);
1186
							}
1187 58
						} else {
1188
							throw new TInvalidDataValueException('component_eventhandler_invalid', get_class($this), $name, $handler[1]);
1189
						}
1190 59
					}
1191 60
				} else {
1192
					throw new TInvalidDataValueException('component_eventhandler_invalid', get_class($this), $name, gettype($handler));
1193
				}
1194 173
1195 1
				$this->dyIntraRaiseEventPostHandler($name, $sender, $param, $handler, $response);
1196
1197
				if ($postfunction) {
1198 182
					$response = call_user_func_array($postfunction, [$sender, $param, $this, $response]);
1199 181
				}
1200
1201
				if ($responsetype & TEventResults::EVENT_RESULT_ALL) {
1202 182
					$responses[] = ['sender' => $sender, 'param' => $param, 'response' => $response];
1203
				} else {
1204 182
					$responses[] = $response;
1205
				}
1206
1207
				if ($response !== null && ($responsetype & TEventResults::EVENT_RESULT_FEED_FORWARD)) {
1208
					$param = $response;
1209
				}
1210
			}
1211
		} elseif (strncasecmp($name, 'on', 2) === 0 && !$this->hasEvent($name)) {
1212
			throw new TInvalidOperationException('component_event_undefined', get_class($this), $name);
1213
		}
1214
1215
		if ($responsetype & TEventResults::EVENT_RESULT_FILTER) {
1216
			$responses = array_filter($responses);
1217
		}
1218
1219
		$responses = $this->dyPostRaiseEvent($responses, $name, $sender, $param, $responsetype, $postfunction);
0 ignored issues
show
Bug introduced by
It seems like $postfunction can also be of type callable; however, parameter $postfunction of Prado\TComponent::dyPostRaiseEvent() does only seem to accept Prado\function|null, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

1219
		$responses = $this->dyPostRaiseEvent($responses, $name, $sender, $param, /** @scrutinizer ignore-type */ $responsetype, $postfunction);
Loading history...
1220
1221
		return $responses;
1222
	}
1223
1224 2
	/**
1225
	 * Evaluates a PHP expression in the context of this control.
1226 2
	 *
1227
	 * Behaviors may implement the function:
1228 2
	 * <code>
1229
	 *	public function dyEvaluateExpressionFilter($expression, $chain) {
1230
	 * 		return $chain->dyEvaluateExpressionFilter(str_replace('foo', 'bar', $expression)); //example
1231 2
	 * }
1232 1
	 * </code>
1233 1
	 * to be executed when evaluateExpression is called.  All attached behaviors are notified through
1234
	 * dyEvaluateExpressionFilter.  The chaining is important in this function due to the filtering
1235
	 * pass-through effect.
1236
	 *
1237
	 * @param string $expression PHP expression
1238
	 * @throws TInvalidOperationException if the expression is invalid
1239
	 * @return mixed the expression result
1240
	 */
1241
	public function evaluateExpression($expression)
1242
	{
1243
		$expression = $this->dyEvaluateExpressionFilter($expression);
1244
		try {
1245
			return eval("return $expression;");
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1246
		} 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...
1247
			throw new TInvalidOperationException('component_expression_invalid', get_class($this), $expression, $e->getMessage());
1248
		}
1249
	}
1250
1251
	/**
1252
	 * Evaluates a list of PHP statements.
1253
	 *
1254 2
	 * Behaviors may implement the function:
1255
	 * <code>
1256 2
	 *	public function dyEvaluateStatementsFilter($statements, $chain) {
1257
	 * 		return $chain->dyEvaluateStatementsFilter(str_replace('foo', 'bar', $statements)); //example
1258 2
	 * }
1259 2
	 * </code>
1260
	 * to be executed when evaluateStatements is called.  All attached behaviors are notified through
1261
	 * dyEvaluateStatementsFilter.  The chaining is important in this function due to the filtering
1262 2
	 * pass-through effect.
1263 2
	 *
1264 2
	 * @param string $statements PHP statements
1265 1
	 * @throws TInvalidOperationException if the statements are invalid
1266 1
	 * @return string content echoed or printed by the PHP statements
1267
	 */
1268
	public function evaluateStatements($statements)
1269
	{
1270
		$statements = $this->dyEvaluateStatementsFilter($statements);
1271
		try {
1272
			ob_start();
1273
			if (eval($statements) === false) {
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1274
				throw new \Exception('');
1275
			}
1276
			$content = ob_get_contents();
1277
			ob_end_clean();
1278
			return $content;
1279
		} catch (\Exception $e) {
1280
			throw new TInvalidOperationException('component_statements_invalid', get_class($this), $statements, $e->getMessage());
1281
		}
1282
	}
1283
1284
	/**
1285
	 * This method is invoked after the component is instantiated by a template.
1286
	 * When this method is invoked, the component's properties have been initialized.
1287
	 * The default implementation of this method will invoke
1288
	 * the potential parent component's {@link addParsedObject}.
1289 1
	 * This method can be overridden.
1290
	 *
1291 1
	 * Behaviors may implement the function:
1292 1
	 * <code>
1293 1
	 *	public function dyCreatedOnTemplate($parent, $chain) {
1294
	 * 		return $chain->dyCreatedOnTemplate($parent); //example
1295
	 *  }
1296
	 * </code>
1297
	 * to be executed when createdOnTemplate is called.  All attached behaviors are notified through
1298
	 * dyCreatedOnTemplate.
1299
	 *
1300
	 * @param \Prado\TComponent $parent potential parent of this control
1301
	 * @see addParsedObject
1302
	 */
1303
	public function createdOnTemplate($parent)
1304
	{
1305
		$parent = $this->dyCreatedOnTemplate($parent);
1306
		$parent->addParsedObject($this);
1307
	}
1308
1309
	/**
1310
	 * Processes an object that is created during parsing template.
1311
	 * The object can be either a component or a static text string.
1312 1
	 * This method can be overridden to customize the handling of newly created objects in template.
1313
	 * Only framework developers and control developers should use this method.
1314 1
	 *
1315 1
	 * Behaviors may implement the function:
1316
	 * <code>
1317
	 *	public function dyAddParsedObject($object[, $chain]) {
1318
	 *  }
1319
	 * </code>
1320
	 * to be executed when addParsedObject is called.  All attached behaviors are notified through
1321
	 * dyAddParsedObject.
1322
	 *
1323
	 * @param \Prado\TComponent|string $object text string or component parsed and instantiated in template
1324
	 * @see createdOnTemplate
1325
	 */
1326 6
	public function addParsedObject($object)
1327
	{
1328 6
		$this->dyAddParsedObject($object);
1329 6
	}
1330
1331 1
1332
	/**
1333
	 *This is the method registered for all instanced objects should a class behavior be added after
1334
	 * the class is instanced.  Only when the class to which the behavior is being added is in this
1335
	 * object's class hierarchy, via {@link getClassHierarchy}, is the behavior added to this instance.
1336
	 * @param mixed $sender the application
1337
	 * @param TClassBehaviorEventParameter $param
1338
	 * @since 3.2.3
1339
	 */
1340
	public function fxAttachClassBehavior($sender, $param)
1341
	{
1342 6
		if (in_array($param->getClass(), $this->getClassHierarchy(true))) {
1343
			return $this->attachBehavior($param->getName(), $param->getBehavior(), $param->getPriority());
1344 6
		}
1345 6
	}
1346
1347 1
1348
	/**
1349
	 *	This is the method registered for all instanced objects should a class behavior be removed after
1350
	 * the class is instanced.  Only when the class to which the behavior is being added is in this
1351
	 * object's class hierarchy, via {@link getClassHierarchy}, is the behavior removed from this instance.
1352
	 * @param mixed $sender the application
1353
	 * @param TClassBehaviorEventParameter $param
1354
	 * @since 3.2.3
1355
	 */
1356
	public function fxDetachClassBehavior($sender, $param)
1357
	{
1358
		if (in_array($param->getClass(), $this->getClassHierarchy(true))) {
1359
			return $this->detachBehavior($param->getName(), $param->getPriority());
1360
		}
1361
	}
1362
	
1363
	/**
1364
	 * instanceBehavior is an internal method that takes a Behavior Object, a class name, or array of
1365
	 * ['class' => 'MyBehavior', 'property1' => 'Value1'...] and creates a Behavior in return. eg.
1366
	 * <code>
1367
	 *		$b = $this->instanceBehavior('MyBehavior');
1368 6
	 * 		$b = $this->instanceBehavior(['class' => 'MyBehavior', 'property1' => 'Value1']);
1369
	 * 		$b = $this->instanceBehavior(new MyBehavior);
1370 6
	 * </code>
1371 5
	 * @param array|class|string $behavior string, Behavior, or array of ['class' => 'MyBehavior', 'property1' => 'Value1' ...].
0 ignored issues
show
Bug introduced by
The type Prado\class was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
1372
	 * @throws TInvalidDataTypeException if the behavior is not an {@link IBaseBehavior}
1373 6
	 * @return {@link IBaseBehavior} an instance of $behavior or $behavior itself
0 ignored issues
show
Documentation Bug introduced by
The doc comment {@link at position 0 could not be parsed: Unknown type name '{' at position 0 in {@link.
Loading history...
1374
	 * @since 4.2.0
1375
	 */
1376
	protected static function instanceBehavior($behavior)
1377 6
	{
1378
		if (is_string($behavior)) {
1379
			$behavior = Prado::createComponent($behavior);
1380 6
		} elseif (is_array($behavior) && isset($behavior['class'])) {
1381 6
			$b = Prado::createComponent($behavior['class']);
1382 1
			unset($behavior['class']);
1383
			foreach ($behavior as $property => $value) {
1384 6
				$b->setSubProperty($property, $value);
1385 6
			}
1386
			$behavior = $b;
1387 6
		}
1388 1
		if (!($behavior instanceof IBaseBehavior)) {
1389
			throw new TInvalidDataTypeException('component_not_a_behavior', get_class($behavior));
1390 6
		}
1391 6
		return $behavior;
1392 6
	}
1393 6
1394
1395
	/**
1396
	 *	This will add a class behavior to all classes instanced (that are listening) and future newly instanced objects.
1397
	 * This registers the behavior for future instances and pushes the changes to all the instances that are listening as well.
1398
	 * The universal class behaviors are stored in an inverted stack with the latest class behavior being at the first position in the array.
1399
	 * This is done so class behaviors are added last first.
1400
	 * @param string $name name the key of the class behavior
1401
	 * @param object|string $behavior class behavior or name of the object behavior per instance
1402
	 * @param null|class|string $class string of class or class on which to attach this behavior.  Defaults to null which will error
1403
	 *	but more important, if this is on PHP 5.3 it will use Late Static Binding to derive the class
1404
	 * it should extend.
1405
	 * <code>
1406
	 * TPanel::attachClassBehavior('javascripts', (new TJsPanelBehavior())->init($this));
1407
	 * </code>
1408
	 * @param null|numeric $priority priority of behavior, default: null the default priority of the {@link TWeakCallableCollection}  Optional.
1409 6
	 * @throws TInvalidOperationException if the class behavior is being added to a {@link TComponent}; due to recursion.
1410
	 * @throws TInvalidOperationException if the class behavior is already defined
1411 6
	 * @since 3.2.3
1412 5
	 */
1413
	public static function attachClassBehavior($name, $behavior, $class = null, $priority = null)
1414 6
	{
1415
		if (!$class && function_exists('get_called_class')) {
1416
			$class = get_called_class();
1417
		}
1418 6
		if (!$class) {
1419 6
			throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
1420
		}
1421
1422 6
		if (!is_string($name)) {
0 ignored issues
show
introduced by
The condition is_string($name) is always true.
Loading history...
1423
			$name = get_class($name);
1424
		}
1425 6
		$class = strtolower($class);
1426 6
		if ($class === 'tcomponent') {
1427 6
			throw new TInvalidOperationException('component_no_tcomponent_class_behaviors');
1428 6
		}
1429 6
		if (empty(self::$_um[$class])) {
1430
			self::$_um[$class] = [];
1431
		}
1432
		if (isset(self::$_um[$class][$name])) {
1433
			throw new TInvalidOperationException('component_class_behavior_defined', $class, $name);
1434
		}
1435
		$behaviorObject = self::instanceBehavior($behavior);
1436
		$param = new TClassBehaviorEventParameter($class, $name, $behavior, $priority);
0 ignored issues
show
Bug introduced by
It seems like $behavior can also be of type string; however, parameter $behavior of Prado\Util\TClassBehavio...arameter::__construct() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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