1 | <?php |
||||||
2 | |||||||
3 | /** |
||||||
4 | * TComponent, TPropertyValue classes |
||||||
5 | * |
||||||
6 | * @author Qiang Xue <[email protected]> |
||||||
7 | * |
||||||
8 | * Global Events, intra-object events, Class behaviors, expanded behaviors |
||||||
9 | * @author Brad Anderson <[email protected]> |
||||||
10 | * |
||||||
11 | * @author Qiang Xue <[email protected]> |
||||||
12 | * @link https://github.com/pradosoft/prado |
||||||
13 | * @license https://github.com/pradosoft/prado/blob/master/LICENSE |
||||||
14 | */ |
||||||
15 | |||||||
16 | namespace Prado; |
||||||
17 | |||||||
18 | use Prado\Collections\TPriorityMap; |
||||||
19 | use Prado\Collections\TWeakCallableCollection; |
||||||
20 | use Prado\Exceptions\TApplicationException; |
||||||
21 | use Prado\Exceptions\TInvalidDataTypeException; |
||||||
22 | use Prado\Exceptions\TInvalidDataValueException; |
||||||
23 | use Prado\Exceptions\TInvalidOperationException; |
||||||
24 | use Prado\Exceptions\TUnknownMethodException; |
||||||
25 | use Prado\Util\IBaseBehavior; |
||||||
26 | use Prado\Util\IBehavior; |
||||||
27 | use Prado\Util\TCallChain; |
||||||
28 | use Prado\Util\IClassBehavior; |
||||||
29 | use Prado\Util\IDynamicMethods; |
||||||
30 | use Prado\Util\TClassBehaviorEventParameter; |
||||||
31 | use Prado\Web\Javascripts\TJavaScriptLiteral; |
||||||
32 | use Prado\Web\Javascripts\TJavaScriptString; |
||||||
33 | |||||||
34 | /** |
||||||
35 | * TComponent class |
||||||
36 | * |
||||||
37 | * TComponent is the base class for all PRADO components. |
||||||
38 | * TComponent implements the protocol of defining, using properties, behaviors, |
||||||
39 | * events, dynamic events, and global events. |
||||||
40 | * |
||||||
41 | * Properties |
||||||
42 | * |
||||||
43 | * A property is defined by a getter method, and/or a setter method. |
||||||
44 | * Properties can be accessed in the way like accessing normal object members. |
||||||
45 | * Reading or writing a property will cause the invocation of the corresponding |
||||||
46 | * getter or setter method, e.g., |
||||||
47 | * ```php |
||||||
48 | * $a=$this->Text; // equivalent to $a=$this->getText(); |
||||||
49 | * $this->Text='abc'; // equivalent to $this->setText('abc'); |
||||||
50 | * ``` |
||||||
51 | * The signatures of getter and setter methods are as follows, |
||||||
52 | * ```php |
||||||
53 | * // getter, defines a readable property 'Text' |
||||||
54 | * function getText() { ... } |
||||||
55 | * // setter, defines a writable property 'Text', with $value being the value to be set to the property |
||||||
56 | * function setText($value) { ... } |
||||||
57 | * ``` |
||||||
58 | * Property names are case-insensitive. It is recommended that they are written |
||||||
59 | * in the format of concatenated words, with the first letter of each word |
||||||
60 | * capitalized (e.g. DisplayMode, ItemStyle). |
||||||
61 | * |
||||||
62 | * |
||||||
63 | * Javascript Get and Set Properties |
||||||
64 | * |
||||||
65 | * Since Prado 3.2 a new class of javascript-friendly properties have been introduced |
||||||
66 | * to better deal with potential security problems like cross-site scripting issues. |
||||||
67 | * All the data that gets sent clientside inside a javascript block is now encoded by default. |
||||||
68 | * Sometimes there's the need to bypass this encoding and be able to send raw javascript code. |
||||||
69 | * This new class of javascript-friendly properties are identified by their name |
||||||
70 | * starting with 'js' (case insensitive): |
||||||
71 | * ```php |
||||||
72 | * // getter, defines a readable property 'Text' |
||||||
73 | * function getJsText() { ... } |
||||||
74 | * // setter, defines a writable property 'Text', with $value being the value to be set to the property |
||||||
75 | * function setJsText(TJavaScriptLiteral $value) { ... } |
||||||
76 | * ``` |
||||||
77 | * Js-friendly properties can be accessed using both their Js-less name and their Js-enabled name: |
||||||
78 | * ```php |
||||||
79 | * // set some simple text as property value |
||||||
80 | * $component->Text = 'text'; |
||||||
81 | * // set some javascript code as property value |
||||||
82 | * $component->JsText = 'raw javascript'; |
||||||
83 | * ``` |
||||||
84 | * In the first case, the property value will automatically gets encoded when sent |
||||||
85 | * clientside inside a javascript block. |
||||||
86 | * In the second case, the property will be 'marked' as being a safe javascript |
||||||
87 | * statement and will not be encoded when rendered inside a javascript block. |
||||||
88 | * This special handling makes use of the {@see \Prado\Web\Javascripts\TJavaScriptLiteral} class. |
||||||
89 | * |
||||||
90 | * |
||||||
91 | * Object Events |
||||||
92 | * |
||||||
93 | * An event is defined by the presence of a method whose name starts with 'on'. |
||||||
94 | * The event name is the method name and is thus case-insensitive. |
||||||
95 | * An event can be attached with one or several methods (called event handlers). |
||||||
96 | * An event can be raised by calling {@see raiseEvent} method, upon which |
||||||
97 | * the attached event handlers will be invoked automatically in the order they |
||||||
98 | * are attached to the event. Event handlers must have the following signature, |
||||||
99 | * ```php |
||||||
100 | * function eventHandlerFuncName($sender, $param) { ... } |
||||||
101 | * ``` |
||||||
102 | * where $sender refers to the object who is responsible for the raising of the event, |
||||||
103 | * and $param refers to a structure that may contain event-specific information. |
||||||
104 | * To raise an event (assuming named as 'Click') of a component, use |
||||||
105 | * ```php |
||||||
106 | * $component->raiseEvent('OnClick'); |
||||||
107 | * $component->raiseEvent('OnClick', $this, $param); |
||||||
108 | * ``` |
||||||
109 | * To attach an event handler to an event, use one of the following ways, |
||||||
110 | * ```php |
||||||
111 | * $component->OnClick = $callback; |
||||||
112 | * $component->OnClick->add($callback); |
||||||
113 | * $component->attachEventHandler('OnClick', $callback); |
||||||
114 | * ``` |
||||||
115 | * The first two ways make use of the fact that $component->OnClick refers to |
||||||
116 | * the event handler list {@see \Prado\Collections\TWeakCallableCollection} for the 'OnClick' event. |
||||||
117 | * The variable $callback contains the definition of the event handler that can |
||||||
118 | * be either: |
||||||
119 | * |
||||||
120 | * a string referring to a global function name |
||||||
121 | * ```php |
||||||
122 | * $component->OnClick = 'buttonClicked'; |
||||||
123 | * // will cause the following function to be called |
||||||
124 | * buttonClicked($sender, $param); |
||||||
125 | * ``` |
||||||
126 | * |
||||||
127 | * All types of PHP Callables are supported, such as: |
||||||
128 | * - Simple Callback function string, eg. 'my_callback_function' |
||||||
129 | * - Static class method call, eg. ['MyClass', 'myCallbackMethod'] and 'MyClass::myCallbackMethod' |
||||||
130 | * - Object method call, eg. [$object, 'myCallbackMethod'] |
||||||
131 | * - Objects implementing __invoke |
||||||
132 | * - Closure / anonymous functions |
||||||
133 | * |
||||||
134 | * PRADO can accept method names in PRADO namespace as well. |
||||||
135 | * ```php |
||||||
136 | * $component->OnClick = [$object, 'buttonClicked']; |
||||||
137 | * // will cause the following function to be called |
||||||
138 | * $object->buttonClicked($sender, param); |
||||||
139 | * |
||||||
140 | * // the method can also be expressed using the PRADO namespace format |
||||||
141 | * $component->OnClick = [$object, 'MainContent.SubmitButton.buttonClicked']; |
||||||
142 | * // will cause the following function to be called |
||||||
143 | * $object->MainContent->SubmitButton->buttonClicked($sender, $param); |
||||||
144 | * |
||||||
145 | * // Closure as an event handler |
||||||
146 | * $component->OnClick = function ($sender, $param) { ... }; |
||||||
147 | * ``` |
||||||
148 | * |
||||||
149 | * |
||||||
150 | * Global and Dynamic Events |
||||||
151 | * |
||||||
152 | * With the addition of behaviors, a more expansive event model is needed. There |
||||||
153 | * are two new event types (global and dynamic events) as well as a more comprehensive |
||||||
154 | * behavior model that includes class wide behaviors. |
||||||
155 | * |
||||||
156 | * A global event is defined by all events whose name starts with 'fx'. |
||||||
157 | * The event name is potentially a method name and is thus case-insensitive. All 'fx' events |
||||||
158 | * are valid as the whole 'fx' event/method space is global in nature. Any object may patch into |
||||||
159 | * any global event by defining that event as a method. Global events have priorities |
||||||
160 | * just like 'on' events; so as to be able to order the event execution. Due to the |
||||||
161 | * nature of all events which start with 'fx' being valid, in effect, every object |
||||||
162 | * has every 'fx' global event. It is simply an issue of tapping into the desired |
||||||
163 | * global event. |
||||||
164 | * |
||||||
165 | * A global event that starts with 'fx' can be called even if the object does not |
||||||
166 | * implement the method of the global event. A call to a non-existing 'fx' method |
||||||
167 | * will, at minimal, function and return null. If a method argument list has a first |
||||||
168 | * parameter, it will be returned instead of null. This allows filtering and chaining. |
||||||
169 | * 'fx' methods do not automatically install and uninstall. To install and uninstall an |
||||||
170 | * object's global event listeners, call the object's {@see listen} and |
||||||
171 | * {@see unlisten} methods, respectively. An object may auto-install its global event |
||||||
172 | * during {@see __construct} by overriding {@see getAutoGlobalListen} and returning true. |
||||||
173 | * |
||||||
174 | * As of PHP version 5.3, nulled objects without code references will still continue to persist |
||||||
175 | * in the global event queue because {@see __destruct} is not automatically called. In the common |
||||||
176 | * __destruct method, if an object is listening to global events, then {@see unlisten} is called. |
||||||
177 | * {@see unlisten} is required to be manually called before an object is |
||||||
178 | * left without references if it is currently listening to any global events. This includes |
||||||
179 | * class wide behaviors. This is corrected in PHP 7.4.0 with WeakReferences and {@see |
||||||
180 | * TWeakCallableCollection} |
||||||
181 | * |
||||||
182 | * An object that contains a method that starts with 'fx' will have those functions |
||||||
183 | * automatically receive those events of the same name after {@see listen} is called on the object. |
||||||
184 | * |
||||||
185 | * An object may listen to a global event without defining an 'fx' method of the same name by |
||||||
186 | * adding an object method to the global event list. For example |
||||||
187 | * ```php |
||||||
188 | * $component->fxGlobalCheck=$callback; |
||||||
189 | * $component->fxGlobalCheck->add($callback); |
||||||
190 | * $component->attachEventHandler('fxGlobalCheck', [$object, 'someMethod']); |
||||||
191 | * ``` |
||||||
192 | * |
||||||
193 | * |
||||||
194 | * Events between Objects and their behaviors, Dynamic Events |
||||||
195 | * |
||||||
196 | * An intra-object/behavior event is defined by methods that start with 'dy'. Just as with |
||||||
197 | * 'fx' global events, every object has every dynamic event. Any call to a method that |
||||||
198 | * starts with 'dy' will be handled, regardless of whether it is implemented. These |
||||||
199 | * events are for communicating with attached behaviors. |
||||||
200 | * |
||||||
201 | * Dynamic events can be used in a variety of ways. They can be used to tell behaviors |
||||||
202 | * when a non-behavior method is called. Dynamic events could be used as data filters. |
||||||
203 | * They could also be used to specify when a piece of code is to be run, eg. should the |
||||||
204 | * loop process be performed on a particular piece of data. In this way, some control |
||||||
205 | * is handed to the behaviors over the process and/or data. |
||||||
206 | * |
||||||
207 | * If there are no handlers for an 'fx' or 'dy' event, it will return the first |
||||||
208 | * parameter of the argument list. If there are no arguments, these events |
||||||
209 | * will return null. If there are handlers an 'fx' method will be called directly |
||||||
210 | * within the object. Global 'fx' events are triggered by calling {@see raiseEvent}. |
||||||
211 | * For dynamic events where there are behaviors that respond to the dynamic events, a |
||||||
212 | * {@see \Prado\Util\TCallChain} is developed. A call chain allows the behavior dynamic event |
||||||
213 | * implementations to call further implementing behaviors within a chain. |
||||||
214 | * |
||||||
215 | * If an object implements {@see \Prado\Util\IDynamicMethods}, all global and object dynamic |
||||||
216 | * events will be sent to {@see __dycall}. In the case of global events, all |
||||||
217 | * global events will trigger this method. In the case of behaviors, all undefined |
||||||
218 | * dynamic events which are called will be passed through to this method. |
||||||
219 | * |
||||||
220 | * |
||||||
221 | * Behaviors |
||||||
222 | * |
||||||
223 | * PRADO TComponent Behaviors is a method to extend a single component or a class |
||||||
224 | * of components with new properties, methods, features, and fine control over the |
||||||
225 | * owner object. Behaviors can be attached to single objects or whole classes |
||||||
226 | * (or interfaces, parents, and first level traits). |
||||||
227 | * |
||||||
228 | * There are two types of behaviors. There are individual {@see \Prado\Util\IBehavior} and |
||||||
229 | * there are class wide {IClassBehavior}. IBehavior has one owner and IClassBehavior |
||||||
230 | * can attach to multiple owners at the same time. IClassBehavior is designed to be |
||||||
231 | * stateless, like for specific filtering or addition of data. |
||||||
232 | * |
||||||
233 | * When a new class implements {@see \Prado\Util\IClassBehavior} or {@see \Prado\Util\IBehavior}, or extends |
||||||
234 | * the PRADO implementations {@see \Prado\Util\TClassBehavior} and {@see \Prado\Util\TBehavior}, it may be |
||||||
235 | * attached to a TComponent by calling the object's {@see attachBehavior}. The |
||||||
236 | * behaviors associated name can then be used to {@see enableBehavior} or {@see |
||||||
237 | * disableBehavior} the specific behavior. |
||||||
238 | * |
||||||
239 | * All behaviors may be turned on and off via {@see enableBehaviors} and |
||||||
240 | * {@see disableBehaviors}, respectively. To check if behaviors are on or off |
||||||
241 | * a call to {@see getBehaviorsEnabled} will provide the variable. By default, |
||||||
242 | * a behavior's event handlers will be removed from events when disabled. |
||||||
243 | * |
||||||
244 | * Attaching and detaching whole sets of behaviors is done using |
||||||
245 | * {@see attachBehaviors} and {@see detachBehaviors}. {@see clearBehaviors} |
||||||
246 | * removes all of an object's behaviors. |
||||||
247 | * |
||||||
248 | * {@see asa} returns a behavior of a specific name. {@see isa} is the |
||||||
249 | * behavior inclusive function that acts as the PHP operator {@see instanceof}. |
||||||
250 | * A behavior could provide the functionality of a specific class thus causing |
||||||
251 | * the host object to act similarly to a completely different class. A behavior |
||||||
252 | * would then implement {@see \Prado\Util\IInstanceCheck} to provide the identity of the |
||||||
253 | * different class. |
||||||
254 | * |
||||||
255 | * IClassBehavior are similar to IBehavior except that the class behavior |
||||||
256 | * attaches to multiple owners, like all the instances of a class. A class behavior |
||||||
257 | * will have the object upon which is being called be prepended to the parameter |
||||||
258 | * list. This way the object is known across the class behavior implementation. |
||||||
259 | * |
||||||
260 | * Class behaviors are attached using {@see attachClassBehavior} and detached |
||||||
261 | * using {@see detachClassBehavior}. Class behaviors are important in that |
||||||
262 | * they will be applied to all new instances of a particular class and all listening |
||||||
263 | * components as well. Classes, Class Parents, Interfaces, and first level Traits |
||||||
264 | * can be attached by class. |
||||||
265 | * Class behaviors are default behaviors to new instances of a class in and are |
||||||
266 | * received in {@see __construct}. Detaching a class behavior will remove the |
||||||
267 | * behavior from the default set of behaviors created for an object when the object |
||||||
268 | * is instanced. |
||||||
269 | * |
||||||
270 | * Class behaviors are also added to all existing instances via the global 'fx' |
||||||
271 | * event mechanism. When a new class behavior is added, the event |
||||||
272 | * {@see fxAttachClassBehavior} is raised and all existing instances that are |
||||||
273 | * listening to this global event (primarily after {@see listen} is called) |
||||||
274 | * will have this new behavior attached. A similar process is used when |
||||||
275 | * detaching class behaviors. Any objects listening to the global 'fx' event |
||||||
276 | * {@see fxDetachClassBehavior} will have a class behavior removed. |
||||||
277 | * |
||||||
278 | * Anonymous Behaviors are supported where the behavior does not have a name or |
||||||
279 | * the behavior has a numeric for a name. These cannot be accessed by name because |
||||||
280 | * their names may be different in each request, for different owners, and possibly, |
||||||
281 | * though extremely rarely, even the same object between serialization-sleep and |
||||||
282 | * unserialization-wakeup. |
||||||
283 | * |
||||||
284 | * When serializing a component with behaviors, behaviors are saved and restored. |
||||||
285 | * Named IClassBehavior class behaviors are updated with the current instance |
||||||
286 | * of the named class behavior rather than replicate it from the wake up. {@see |
||||||
287 | * __wakeup} will add any new named class behaviors to the unserializing component. |
||||||
288 | * |
||||||
289 | * IClassBehaviors can only use one given name for all behaviors except when applied |
||||||
290 | * anonymously (with no name or a numeric name). |
||||||
291 | * |
||||||
292 | * |
||||||
293 | * Dynamic Intra-Object Behavior Events |
||||||
294 | * |
||||||
295 | * Dynamic events start with 'dy'. This mechanism is used to allow objects |
||||||
296 | * to communicate with their behaviors directly. The entire 'dy' event space |
||||||
297 | * is valid. All attached, enabled behaviors that implement a dynamic event |
||||||
298 | * are called when the host object calls the dynamic event. If there is no |
||||||
299 | * implementation or behaviors, this returns null when no parameters are |
||||||
300 | * supplied and will return the first parameter when there is at least one |
||||||
301 | * parameter in the dynamic event. |
||||||
302 | * ```php |
||||||
303 | * null == $this->dyBehaviorEvent(); |
||||||
304 | * 5 == $this->dyBehaviorEvent(5); //when no behaviors implement this dynamic event |
||||||
305 | * ``` |
||||||
306 | * |
||||||
307 | * Dynamic events can be chained together within behaviors to allow for data |
||||||
308 | * filtering. Dynamic events are implemented within behaviors by defining the |
||||||
309 | * event as a method. |
||||||
310 | * ```php |
||||||
311 | * class TObjectBehavior extends TBehavior { |
||||||
312 | * public function dyBehaviorEvent($param1, $callchain) { |
||||||
313 | * //Do something, eg: $param1 += $this->getOwner()->getNumber(); |
||||||
314 | * return $callchain->dyBehaviorEvent($param1); |
||||||
315 | * } |
||||||
316 | * } |
||||||
317 | * ``` |
||||||
318 | * This implementation of a behavior and dynamic event will flow through to the |
||||||
319 | * next behavior implementing the dynamic event. The first parameter is always |
||||||
320 | * return when it is supplied. Otherwise a dynamic event returns null. |
||||||
321 | * |
||||||
322 | * In the case of a class behavior, the object is also prepended to the dynamic |
||||||
323 | * event. |
||||||
324 | * ```php |
||||||
325 | * class TObjectClassBehavior extends TClassBehavior { |
||||||
326 | * public function dyBehaviorEvent($hostobject, $param1, $callchain) { |
||||||
327 | * //Do something, eg: $param1 += $hostobject->getNumber(); |
||||||
328 | * return $callchain->dyBehaviorEvent($param1); |
||||||
329 | * } |
||||||
330 | * } |
||||||
331 | * ``` |
||||||
332 | * When calling a dynamic event, only the parameters are passed. The host object |
||||||
333 | * and the call chain are built into the framework. |
||||||
334 | * |
||||||
335 | * |
||||||
336 | * Global Event and Dynamic Event Catching |
||||||
337 | * |
||||||
338 | * Given that all global 'fx' events and dynamic 'dy' events are valid and |
||||||
339 | * operational, there is a mechanism for catching events called that are not |
||||||
340 | * implemented (similar to the built-in PHP method {@see __call}). When |
||||||
341 | * a dynamic or global event is called but a behavior does not implement it, |
||||||
342 | * yet desires to know when an undefined dynamic event is run, the behavior |
||||||
343 | * implements the interface {@see \Prado\Util\IDynamicMethods} and method {@see __dycall}. |
||||||
344 | * |
||||||
345 | * In the case of dynamic events, {@see __dycall} is supplied with the method |
||||||
346 | 194 | * name and its parameters. When a global event is raised, via {@see raiseEvent}, |
|||||
347 | * the method is the event name and the parameters are supplied. |
||||||
348 | 194 | * |
|||||
349 | 38 | * When implemented, this catch-all mechanism is called for event global event event |
|||||
350 | * when implemented outside of a behavior. Within a behavior, it will also be called |
||||||
351 | * when the object to which the behavior is attached calls any unimplemented dynamic |
||||||
352 | 194 | * event. This is the fall-back mechanism for informing a class and/or behavior |
|||||
353 | 194 | * of when an global and/or undefined dynamic event is executed. |
|||||
354 | 194 | * |
|||||
355 | 194 | * @author Qiang Xue <[email protected]> |
|||||
356 | * @author Brad Anderson <[email protected]> |
||||||
357 | * @since 3.0 |
||||||
358 | 194 | * @method void dyClone() |
|||||
359 | * @method void dyWakeUp() |
||||||
360 | * @method void dyListen(array $globalEvents) |
||||||
361 | * @method void dyUnlisten(array $globalEvents) |
||||||
362 | * @method string dyPreRaiseEvent(string $name, mixed $sender, \Prado\TEventParameter $param, null|numeric $responsetype, null|callable $postfunction) |
||||||
363 | * @method dyIntraRaiseEventTestHandler(callable $handler, mixed $sender, \Prado\TEventParameter $param, string $name) |
||||||
364 | * @method bool dyIntraRaiseEventPostHandler(string $name, mixed $sender, \Prado\TEventParameter $param, callable $handler, $response) |
||||||
365 | * @method array dyPostRaiseEvent(array $responses, string $name, mixed $sender, \Prado\TEventParameter $param, null|numeric $responsetype, null|callable $postfunction) |
||||||
366 | * @method string dyEvaluateExpressionFilter(string $statements) |
||||||
367 | * @method string dyEvaluateStatementsFilter(string $statements) |
||||||
368 | * @method dyCreatedOnTemplate(\Prado\TComponent $parent) |
||||||
369 | * @method void dyAddParsedObject(\Prado\TComponent|string $object) |
||||||
370 | * @method void dyAttachBehavior(string $name, IBaseBehavior $behavior) |
||||||
371 | * @method void dyDetachBehavior(string $name, IBaseBehavior $behavior) |
||||||
372 | * @method void dyEnableBehavior(string $name, IBaseBehavior $behavior) |
||||||
373 | 183 | * @method void dyDisableBehavior(string $name, IBaseBehavior $behavior) |
|||||
374 | * @method void dyEnableBehaviors() |
||||||
375 | 183 | * @method void dyDisableBehaviors() |
|||||
376 | */ |
||||||
377 | class TComponent |
||||||
378 | { |
||||||
379 | /** |
||||||
380 | * @var array event handler lists |
||||||
381 | */ |
||||||
382 | protected $_e = []; |
||||||
383 | |||||||
384 | /** |
||||||
385 | * @var bool if listening is enabled. Automatically turned on or off in |
||||||
386 | 620 | * constructor according to {@see getAutoGlobalListen}. Default false, off |
|||||
387 | */ |
||||||
388 | 620 | protected $_listeningenabled = false; |
|||||
389 | |||||||
390 | /** |
||||||
391 | 620 | * @var array static registered global event handler lists |
|||||
392 | */ |
||||||
393 | private static $_ue = []; |
||||||
394 | |||||||
395 | /** |
||||||
396 | * @var bool if object behaviors are on or off. default true, on |
||||||
397 | */ |
||||||
398 | protected $_behaviorsenabled = true; |
||||||
399 | 38 | ||||||
400 | /** |
||||||
401 | 38 | * @var TPriorityMap list of object behaviors |
|||||
402 | */ |
||||||
403 | protected $_m; |
||||||
404 | |||||||
405 | /** |
||||||
406 | * @var array static global class behaviors, these behaviors are added upon instantiation of a class |
||||||
407 | */ |
||||||
408 | private static $_um = []; |
||||||
409 | |||||||
410 | |||||||
411 | 194 | /** |
|||||
412 | * @const string the name of the global {@see raiseEvent} listener |
||||||
413 | 194 | */ |
|||||
414 | 194 | public const GLOBAL_RAISE_EVENT_LISTENER = 'fxGlobalListener'; |
|||||
415 | 194 | ||||||
416 | 192 | ||||||
417 | /** |
||||||
418 | 194 | * The common __construct. |
|||||
419 | 194 | * If desired by the new object, this will auto install and listen to global event functions |
|||||
420 | * as defined by the object via 'fx' methods. This also attaches any predefined behaviors. |
||||||
421 | 1 | * This function installs all class behaviors in a class hierarchy from the deepest subclass |
|||||
422 | * through each parent to the top most class, TComponent. |
||||||
423 | */ |
||||||
424 | public function __construct() |
||||||
425 | { |
||||||
426 | if ($this->getAutoGlobalListen()) { |
||||||
427 | $this->listen(); |
||||||
428 | } |
||||||
429 | |||||||
430 | $classes = $this->getClassHierarchy(true); |
||||||
431 | array_pop($classes); |
||||||
432 | foreach ($classes as $class) { |
||||||
433 | if (isset(self::$_um[$class])) { |
||||||
434 | $this->attachBehaviors(self::$_um[$class], true); |
||||||
435 | } |
||||||
436 | } |
||||||
437 | } |
||||||
438 | |||||||
439 | 38 | /** |
|||||
440 | * The common __clone magic method from PHP's "clone". |
||||||
441 | 38 | * This reattaches the behaviors to the cloned object. |
|||||
442 | * IBehavior objects are cloned, IClassBehaviors are not. |
||||||
443 | * Clone object events are scrubbed of the old object behaviors' events. |
||||||
444 | * To finalize the behaviors, dyClone is raised. |
||||||
445 | 38 | * @since 4.3.0 |
|||||
446 | */ |
||||||
447 | 38 | public function __clone() |
|||||
448 | 38 | { |
|||||
449 | foreach ($this->_e as $event => $handlers) { |
||||||
450 | $this->_e[$event] = clone $handlers; |
||||||
451 | 38 | } |
|||||
452 | 3 | $behaviorArray = array_values(($this->_m !== null) ? $this->_m->toArray() : []); |
|||||
453 | 3 | if (count($behaviorArray) && count($this->_e)) { |
|||||
454 | $behaviorArray = array_combine(array_map('spl_object_id', $behaviorArray), $behaviorArray); |
||||||
455 | foreach ($this->_e as $event => $handlers) { |
||||||
456 | 38 | foreach ($handlers->toArray() as $handler) { |
|||||
457 | $a = is_array($handler); |
||||||
458 | 38 | 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
![]() |
|||||||
459 | $handlers->remove($handler); |
||||||
460 | 38 | } |
|||||
461 | } |
||||||
462 | } |
||||||
463 | } |
||||||
464 | if ($this->_m !== null) { |
||||||
465 | $behaviors = $this->_m; |
||||||
466 | $this->_m = new TPriorityMap(); |
||||||
467 | foreach ($behaviors->getPriorities() as $priority) { |
||||||
468 | foreach ($behaviors->itemsAtPriority($priority) as $name => $behavior) { |
||||||
469 | if ($behavior instanceof IBehavior) { |
||||||
470 | $behavior = clone $behavior; |
||||||
471 | } |
||||||
472 | $this->attachBehavior($name, $behavior, $priority); |
||||||
473 | } |
||||||
474 | } |
||||||
475 | } |
||||||
476 | 38 | $this->callBehaviorsMethod('dyClone', $return); |
|||||
477 | } |
||||||
478 | 38 | ||||||
479 | 3 | /** |
|||||
480 | * The common __wakeup magic method from PHP's "unserialize". |
||||||
481 | * This reattaches the behaviors to the reconstructed object. |
||||||
482 | 38 | * Any global class behaviors are used rather than their unserialized copy. |
|||||
483 | * Any global behaviors not found in the object will be added. |
||||||
484 | 38 | * To finalize the behaviors, dyWakeUp is raised. |
|||||
485 | 38 | * If a TModule needs to add events to an object during unserialization, |
|||||
486 | * the module can use a small IClassBehavior [implementing dyWakeUp] |
||||||
487 | * (adding the event[s]) attached to the class with {@see |
||||||
488 | 38 | * attachClassBehavior} prior to unserialization. |
|||||
489 | 3 | * @since 4.3.0 |
|||||
490 | 3 | */ |
|||||
491 | public function __wakeup() |
||||||
492 | { |
||||||
493 | 38 | $classes = $this->getClassHierarchy(true); |
|||||
494 | array_pop($classes); |
||||||
495 | 38 | $classBehaviors = []; |
|||||
496 | if ($this->_m !== null) { |
||||||
497 | 38 | $behaviors = $this->_m; |
|||||
498 | $this->_m = new TPriorityMap(); |
||||||
499 | foreach ($behaviors->getPriorities() as $priority) { |
||||||
500 | foreach ($behaviors->itemsAtPriority($priority) as $name => $behavior) { |
||||||
501 | if ($behavior instanceof IClassBehavior && !is_numeric($name)) { |
||||||
502 | //Replace class behaviors with their current instances, if they exist. |
||||||
503 | foreach ($classes as $class) { |
||||||
504 | 4 | if (isset(self::$_um[$class]) && array_key_exists($name, self::$_um[$class])) { |
|||||
505 | $behavior = self::$_um[$class][$name]->getBehavior(); |
||||||
506 | 4 | break; |
|||||
507 | } |
||||||
508 | } |
||||||
509 | } |
||||||
510 | $classBehaviors[$name] = $name; |
||||||
511 | $this->attachBehavior($name, $behavior, $priority); |
||||||
512 | } |
||||||
513 | } |
||||||
514 | } |
||||||
515 | foreach ($classes as $class) { |
||||||
516 | if (isset(self::$_um[$class])) { |
||||||
517 | foreach (self::$_um[$class] as $name => $behavior) { |
||||||
518 | if (is_numeric($name)) { |
||||||
519 | continue; |
||||||
520 | } |
||||||
521 | if (!array_key_exists($name, $classBehaviors)) { |
||||||
522 | $this->attachBehaviors([$name => $behavior], true); |
||||||
523 | } |
||||||
524 | } |
||||||
525 | } |
||||||
526 | } |
||||||
527 | $this->callBehaviorsMethod('dyWakeUp', $return); |
||||||
528 | } |
||||||
529 | |||||||
530 | |||||||
531 | /** |
||||||
532 | * Tells TComponent whether or not to automatically listen to global events. |
||||||
533 | 211 | * Defaults to false because PHP variable cleanup is affected if this is true. |
|||||
534 | * When unsetting a variable that is listening to global events, {@see unlisten} |
||||||
535 | 211 | * must explicitly be called when cleaning variables allocation or else the global |
|||||
536 | 211 | * event registry will contain references to the old object. This is true for PHP 5.4 |
|||||
537 | 4 | * |
|||||
538 | 4 | * Override this method by a subclass to change the setting. When set to true, this |
|||||
539 | 4 | * will enable {@see __construct} to call {@see listen}. |
|||||
540 | * |
||||||
541 | * @return bool whether or not to auto listen to global events during {@see __construct}, default false |
||||||
542 | */ |
||||||
543 | public function getAutoGlobalListen() |
||||||
544 | { |
||||||
545 | return false; |
||||||
546 | } |
||||||
547 | |||||||
548 | 4 | ||||||
549 | /** |
||||||
550 | * The common __destruct |
||||||
551 | * When listening, this unlistens from the global event routines. It also detaches |
||||||
552 | * all the behaviors so they can clean up, eg remove handlers. |
||||||
553 | 211 | * |
|||||
554 | 27 | * Prior to PHP 7.4, when listening, unlisten must be manually called for objects |
|||||
555 | 27 | * to destruct because circular references will prevent the __destruct process. |
|||||
556 | 27 | */ |
|||||
557 | 24 | public function __destruct() |
|||||
558 | 3 | { |
|||||
559 | 3 | $this->clearBehaviors(); |
|||||
560 | 2 | if ($this->_listeningenabled) { |
|||||
561 | $this->unlisten(); |
||||||
562 | 24 | } |
|||||
563 | } |
||||||
564 | |||||||
565 | 27 | ||||||
566 | 27 | /** |
|||||
567 | * This utility function is a private array filter method. The array values |
||||||
568 | * that start with 'fx' are filtered in. |
||||||
569 | 8 | * @param mixed $name |
|||||
570 | 8 | */ |
|||||
571 | 8 | private function filter_prado_fx($name) |
|||||
572 | 3 | { |
|||||
573 | return strncasecmp($name, 'fx', 2) === 0; |
||||||
574 | 8 | } |
|||||
575 | |||||||
576 | |||||||
577 | /** |
||||||
578 | * This returns an array of the class name and the names of all its parents. The base object last, |
||||||
579 | * {@see \Prado\TComponent}, and the deepest subclass is first. |
||||||
580 | 211 | * @param bool $lowercase optional should the names be all lowercase true/false |
|||||
581 | 211 | * @return string[] array of strings being the class hierarchy of $this. |
|||||
582 | 6 | */ |
|||||
583 | public function getClassHierarchy($lowercase = false) |
||||||
584 | 211 | { |
|||||
585 | static $_classhierarchy = []; |
||||||
586 | $class = $this::class; |
||||||
587 | if (isset($_classhierarchy[$class]) && isset($_classhierarchy[$class][$lowercase ? 1 : 0])) { |
||||||
588 | 4 | return $_classhierarchy[$class][$lowercase ? 1 : 0]; |
|||||
589 | 4 | } |
|||||
590 | $classes = [array_values(class_implements($class))]; |
||||||
591 | do { |
||||||
592 | $classes[] = array_values(class_uses($class)); |
||||||
593 | $classes[] = [$class]; |
||||||
594 | } while ($class = get_parent_class($class)); |
||||||
595 | $classes = array_merge(...$classes); |
||||||
596 | if ($lowercase) { |
||||||
597 | $classes = array_map('strtolower', $classes); |
||||||
598 | } |
||||||
599 | $_classhierarchy[$class] ??= []; |
||||||
600 | $_classhierarchy[$class][$lowercase ? 1 : 0] = $classes; |
||||||
601 | |||||||
602 | return $classes; |
||||||
603 | } |
||||||
604 | |||||||
605 | /** |
||||||
606 | * This caches the 'fx' events for classes. |
||||||
607 | * @param object $class |
||||||
608 | * @return string[] fx events from a specific class |
||||||
609 | */ |
||||||
610 | protected function getClassFxEvents($class) |
||||||
611 | { |
||||||
612 | static $_classfx = []; |
||||||
613 | $className = $class::class; |
||||||
614 | if (isset($_classfx[$className])) { |
||||||
615 | return $_classfx[$className]; |
||||||
616 | } |
||||||
617 | 95 | $fx = array_filter(get_class_methods($class), [$this, 'filter_prado_fx']); |
|||||
618 | $_classfx[$className] = $fx; |
||||||
619 | 95 | return $fx; |
|||||
620 | } |
||||||
621 | 84 | ||||||
622 | 25 | /** |
|||||
623 | * This adds an object's fx event handlers into the global broadcaster to listen into any |
||||||
624 | 2 | * broadcast global events called through {@see raiseEvent} |
|||||
625 | 23 | * |
|||||
626 | * Behaviors may implement the function: |
||||||
627 | 13 | * ```php |
|||||
628 | 13 | * public function dyListen($globalEvents[, ?TCallChain $chain = null]) { |
|||||
629 | 12 | * $this->listen($globalEvents); //eg |
|||||
630 | * if ($chain) |
||||||
631 | 13 | * $chain->dyUnlisten($globalEvents); |
|||||
632 | 14 | * } |
|||||
633 | * ``` |
||||||
634 | 3 | * to be executed when listen is called. All attached behaviors are notified through dyListen. |
|||||
635 | 3 | * |
|||||
636 | 1 | * @return numeric the number of global events that were registered to the global event registry |
|||||
0 ignored issues
–
show
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. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||||
637 | */ |
||||||
638 | 3 | public function listen() |
|||||
639 | 13 | { |
|||||
640 | if ($this->_listeningenabled) { |
||||||
641 | 13 | return; |
|||||
642 | 9 | } |
|||||
643 | 7 | ||||||
644 | 4 | $fx = $this->getClassFxEvents($this); |
|||||
645 | 4 | ||||||
646 | 4 | foreach ($fx as $func) { |
|||||
647 | 4 | $this->getEventHandlers($func)->add([$this, $func]); |
|||||
648 | } |
||||||
649 | |||||||
650 | if (is_a($this, IDynamicMethods::class)) { |
||||||
651 | $this->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, [$this, '__dycall']); |
||||||
652 | 4 | array_push($fx, TComponent::GLOBAL_RAISE_EVENT_LISTENER); |
|||||
653 | } |
||||||
654 | |||||||
655 | $this->_listeningenabled = true; |
||||||
656 | |||||||
657 | $this->callBehaviorsMethod('dyListen', $return, $fx); |
||||||
658 | |||||||
659 | return count($fx); |
||||||
0 ignored issues
–
show
|
|||||||
660 | } |
||||||
661 | |||||||
662 | /** |
||||||
663 | * this removes an object's fx events from the global broadcaster |
||||||
664 | * |
||||||
665 | * Behaviors may implement the function: |
||||||
666 | * ```php |
||||||
667 | * public function dyUnlisten($globalEvents[, ?TCallChain $chain = null]) { |
||||||
668 | * $this->behaviorUnlisten(); //eg |
||||||
669 | * if ($chain) |
||||||
670 | 77 | * $chain->dyUnlisten($globalEvents); |
|||||
671 | * } |
||||||
672 | 77 | * ``` |
|||||
673 | 76 | * to be executed when listen is called. All attached behaviors are notified through dyUnlisten. |
|||||
674 | 2 | * |
|||||
675 | * @return numeric the number of global events that were unregistered from the global event registry |
||||||
676 | 76 | */ |
|||||
677 | 5 | public function unlisten() |
|||||
678 | 1 | { |
|||||
679 | 1 | if (!$this->_listeningenabled) { |
|||||
680 | return; |
||||||
681 | 1 | } |
|||||
682 | 4 | ||||||
683 | 3 | $fx = $this->getClassFxEvents($this); |
|||||
684 | 4 | ||||||
685 | 4 | foreach ($fx as $func) { |
|||||
686 | 4 | $this->detachEventHandler($func, [$this, $func]); |
|||||
687 | 4 | } |
|||||
688 | 4 | ||||||
689 | 4 | if (is_a($this, IDynamicMethods::class)) { |
|||||
690 | 4 | $this->detachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, [$this, '__dycall']); |
|||||
691 | array_push($fx, TComponent::GLOBAL_RAISE_EVENT_LISTENER); |
||||||
692 | } |
||||||
693 | 4 | ||||||
694 | 4 | $this->_listeningenabled = false; |
|||||
695 | |||||||
696 | $this->callBehaviorsMethod('dyUnlisten', $return, $fx); |
||||||
697 | |||||||
698 | 1 | return count($fx); |
|||||
0 ignored issues
–
show
|
|||||||
699 | 1 | } |
|||||
700 | |||||||
701 | 1 | /** |
|||||
702 | * Gets the state of listening to global events |
||||||
703 | * @return bool is Listening to global broadcast enabled |
||||||
704 | */ |
||||||
705 | public function getListeningToGlobalEvents() |
||||||
706 | { |
||||||
707 | return $this->_listeningenabled; |
||||||
708 | } |
||||||
709 | |||||||
710 | |||||||
711 | /** |
||||||
712 | * Calls a method. |
||||||
713 | * Do not call this method directly. This is a PHP magic method that we override |
||||||
714 | * to allow behaviors, dynamic events (intra-object/behavior events), |
||||||
715 | * undefined dynamic and global events, and |
||||||
716 | * to allow using the following syntax to call a property setter or getter. |
||||||
717 | 5 | * ```php |
|||||
718 | * $this->getPropertyName($value); // if there's a $this->getjsPropertyName() method |
||||||
719 | 5 | * $this->setPropertyName($value); // if there's a $this->setjsPropertyName() method |
|||||
720 | 5 | * ``` |
|||||
721 | 3 | * |
|||||
722 | 2 | * Additional object behaviors override class behaviors. |
|||||
723 | 1 | * dynamic and global events do not fail even if they aren't implemented. |
|||||
724 | 1 | * Any intra-object/behavior dynamic events that are not implemented by the behavior |
|||||
725 | 1 | * return the first function paramater or null when no parameters are specified. |
|||||
726 | 1 | * |
|||||
727 | 1 | * @param string $method method name that doesn't exist and is being called on the object |
|||||
728 | 1 | * @param mixed $args method parameters |
|||||
729 | 1 | * @throws TInvalidOperationException If the property is not defined or read-only or |
|||||
730 | 1 | * method is undefined |
|||||
731 | 1 | * @return mixed result of the method call, or false if 'fx' or 'dy' function but |
|||||
732 | * is not found in the class, otherwise it runs |
||||||
733 | 1 | */ |
|||||
734 | 1 | public function __call($method, $args) |
|||||
735 | 1 | { |
|||||
736 | $getset = substr($method, 0, 3); |
||||||
737 | if (($getset == 'get') || ($getset == 'set')) { |
||||||
738 | $propname = substr($method, 3); |
||||||
739 | 1 | $jsmethod = $getset . 'js' . $propname; |
|||||
740 | if (Prado::method_visible($this, $jsmethod)) { |
||||||
741 | if (count($args) > 0) { |
||||||
742 | if ($args[0] && !($args[0] instanceof TJavaScriptString)) { |
||||||
743 | $args[0] = new TJavaScriptString($args[0]); |
||||||
744 | } |
||||||
745 | } |
||||||
746 | return $this->$jsmethod(...$args); |
||||||
747 | } |
||||||
748 | |||||||
749 | if (($getset == 'set') && Prado::method_visible($this, 'getjs' . $propname)) { |
||||||
750 | throw new TInvalidOperationException('component_property_readonly', $this::class, $method); |
||||||
751 | } |
||||||
752 | } |
||||||
753 | 3 | if ($this->callBehaviorsMethod($method, $return, ...$args)) { |
|||||
754 | return $return; |
||||||
755 | 3 | } |
|||||
756 | 2 | ||||||
757 | 3 | // don't throw an exception for __magicMethods() or any other weird methods natively implemented by php |
|||||
758 | 1 | if (!method_exists($this, $method)) { |
|||||
759 | 2 | throw new TUnknownMethodException('component_method_undefined', $this::class, $method); |
|||||
760 | 2 | } |
|||||
761 | 2 | } |
|||||
762 | 1 | ||||||
763 | 2 | /** |
|||||
764 | 2 | * This is the magic method that is called when a static function is not found. |
|||||
765 | 1 | * It checks the class if it has an ISingleton instance, in which case its behaviors |
|||||
766 | * are checked for the static method. This further checks the Class-wide behaviors |
||||||
767 | 2 | * (added with {@see \Prado\TComponent::attachClassBehavior}) for the static method. |
|||||
768 | 2 | * When the static method is found (in either the ISingleton behaviors or the class-wide |
|||||
769 | 2 | * behaviors), the behavior's static method is called and the results returned. |
|||||
770 | 2 | * @param string $method The method name of the static call. |
|||||
771 | 2 | * @param array $args The array of arguments passed to the static call. |
|||||
772 | * @return mixed the result of the static call. |
||||||
773 | * @since 4.3.0 |
||||||
774 | 2 | */ |
|||||
775 | 2 | public static function __callStatic(string $method, array $args) |
|||||
776 | { |
||||||
777 | $checkedClasses = []; |
||||||
778 | 1 | if (is_a(static::class, ISingleton::class, true) && ($singleton = (static::class)::singleton(false)) && $singleton->getBehaviorsEnabled()) { |
|||||
779 | 1 | foreach ($singleton->getBehaviors() as $behavior) { |
|||||
780 | $class = $behavior::class; |
||||||
781 | 3 | $lclass = strtolower($class); |
|||||
782 | if (!isset($checkedClasses[$lclass])) { |
||||||
783 | if ($behavior->getEnabled() && method_exists($class, $method)) { |
||||||
784 | return forward_static_call_array([$class, $method], $args); |
||||||
785 | } |
||||||
786 | $checkedClasses[$lclass] = true; |
||||||
787 | } |
||||||
788 | } |
||||||
789 | } |
||||||
790 | 1 | ||||||
791 | if (isset(self::$_um[$lclass = strtolower(static::class)])) { |
||||||
792 | 1 | foreach (self::$_um[$lclass] as $pbehavior) { |
|||||
793 | $class = $behavior = $pbehavior->getBehavior(); |
||||||
794 | if (is_array($behavior)) { |
||||||
795 | $class = $behavior['class']; |
||||||
796 | } elseif (is_object($behavior)) { |
||||||
797 | $class = $behavior::class; |
||||||
798 | } |
||||||
799 | |||||||
800 | $lclass = strtolower($class); |
||||||
801 | if (!isset($checkedClasses[$lclass])) { |
||||||
802 | if ((!($behavior instanceof IBaseBehavior) || $behavior->getEnabled()) && method_exists($class, $method)) { |
||||||
803 | return forward_static_call_array([$class, $method], $args); |
||||||
804 | 9 | } |
|||||
805 | $checkedClasses[$lclass] = true; |
||||||
806 | 9 | } |
|||||
807 | 6 | } |
|||||
808 | 8 | } |
|||||
809 | 2 | ||||||
810 | 2 | // don't throw an exception for __magicMethods() or any other weird methods natively implemented by php |
|||||
811 | 2 | if (!method_exists(static::class, $method)) { |
|||||
812 | throw new TUnknownMethodException('component_method_undefined', static::class, $method); |
||||||
813 | } |
||||||
814 | } |
||||||
815 | 8 | ||||||
816 | /** |
||||||
817 | * Returns a property value or an event handler list by property or event name. |
||||||
818 | * Do not call this method. This is a PHP magic method that we override |
||||||
819 | * to allow using the following syntax to read a property: |
||||||
820 | * ```php |
||||||
821 | * $value = $component->PropertyName; |
||||||
822 | * $value = $component->jsPropertyName; // return JavaScript literal |
||||||
823 | * ``` |
||||||
824 | * and to obtain the event handler list for an event, |
||||||
825 | * ```php |
||||||
826 | * $eventHandlerList = $component->EventName; |
||||||
827 | 11 | * ``` |
|||||
828 | * This will also return the global event handler list when specifing an 'fx' |
||||||
829 | 11 | * event, |
|||||
830 | 8 | * ```php |
|||||
831 | 7 | * $globalEventHandlerList = $component->fxEventName; |
|||||
832 | 2 | * ``` |
|||||
833 | 2 | * When behaviors are enabled, this will return the behavior of a specific |
|||||
834 | 2 | * name, a property of a behavior, or an object 'on' event defined by the behavior. |
|||||
835 | * @param string $name the property name or the event name |
||||||
836 | * @throws TInvalidOperationException if the property/event is not defined. |
||||||
837 | * @return mixed the property value or the event handler list as {@see \Prado\Collections\TWeakCallableCollection} |
||||||
838 | 7 | */ |
|||||
839 | public function __get($name) |
||||||
840 | { |
||||||
841 | if (Prado::method_visible($this, $getter = 'get' . $name)) { |
||||||
842 | // getting a property |
||||||
843 | return $this->$getter(); |
||||||
844 | } elseif (Prado::method_visible($this, $jsgetter = 'getjs' . $name)) { |
||||||
845 | // getting a javascript property |
||||||
846 | return (string) $this->$jsgetter(); |
||||||
847 | } elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) { |
||||||
848 | // getting an event (handler list) |
||||||
849 | $name = strtolower($name); |
||||||
850 | if (!isset($this->_e[$name])) { |
||||||
851 | 4 | $this->_e[$name] = new TWeakCallableCollection(); |
|||||
852 | } |
||||||
853 | 4 | return $this->_e[$name]; |
|||||
854 | 4 | } elseif (strncasecmp($name, 'fx', 2) === 0) { |
|||||
855 | 4 | // getting a global event (handler list) |
|||||
856 | $name = strtolower($name); |
||||||
857 | 4 | if (!isset(self::$_ue[$name])) { |
|||||
858 | self::$_ue[$name] = new TWeakCallableCollection(); |
||||||
859 | } |
||||||
860 | return self::$_ue[$name]; |
||||||
861 | } elseif ($this->getBehaviorsEnabled()) { |
||||||
862 | // getting a behavior property/event (handler list) |
||||||
863 | $name = strtolower($name); |
||||||
864 | if (isset($this->_m[$name])) { |
||||||
865 | return $this->_m[$name]; |
||||||
866 | } elseif ($this->_m !== null) { |
||||||
867 | foreach ($this->_m->toArray() as $behavior) { |
||||||
868 | if ($behavior->getEnabled() && (property_exists($behavior, $name) || $behavior->canGetProperty($name) || $behavior->hasEvent($name))) { |
||||||
869 | return $behavior->$name; |
||||||
870 | 2 | } |
|||||
871 | } |
||||||
872 | 2 | } |
|||||
873 | 2 | } |
|||||
874 | 1 | throw new TInvalidOperationException('component_property_undefined', $this::class, $name); |
|||||
875 | } |
||||||
876 | 2 | ||||||
877 | 2 | /** |
|||||
878 | * Sets value of a component property. |
||||||
879 | 2 | * Do not call this method. This is a PHP magic method that we override |
|||||
880 | 2 | * to allow using the following syntax to set a property or attach an event handler. |
|||||
881 | * ```php |
||||||
882 | * $this->PropertyName = $value; |
||||||
883 | * $this->jsPropertyName = $value; // $value will be treated as a JavaScript literal |
||||||
884 | * $this->EventName = $handler; |
||||||
885 | * $this->fxEventName = $handler; //global event listener |
||||||
886 | * $this->EventName = function($sender, $param) {...}; |
||||||
887 | * ``` |
||||||
888 | * When behaviors are enabled, this will also set a behaviors properties and events. |
||||||
889 | * @param string $name the property name or event name |
||||||
890 | * @param mixed $value the property value or event handler |
||||||
891 | * @throws TInvalidOperationException If the property is not defined or read-only. |
||||||
892 | */ |
||||||
893 | public function __set($name, $value) |
||||||
894 | 176 | { |
|||||
895 | if (Prado::method_visible($this, $setter = 'set' . $name)) { |
||||||
896 | 176 | if (strncasecmp($name, 'js', 2) === 0 && $value && !($value instanceof TJavaScriptLiteral)) { |
|||||
897 | 176 | $value = new TJavaScriptLiteral($value); |
|||||
898 | 3 | } |
|||||
899 | 3 | return $this->$setter($value); |
|||||
900 | 3 | } elseif (Prado::method_visible($this, $jssetter = 'setjs' . $name)) { |
|||||
901 | 3 | if ($value && !($value instanceof TJavaScriptString)) { |
|||||
902 | $value = new TJavaScriptString($value); |
||||||
903 | } |
||||||
904 | return $this->$jssetter($value); |
||||||
905 | 3 | } elseif ((strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) || strncasecmp($name, 'fx', 2) === 0) { |
|||||
906 | return $this->attachEventHandler($name, $value); |
||||||
0 ignored issues
–
show
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 The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||||
907 | } elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->getBehaviorsEnabled()) { |
||||||
908 | $sets = 0; |
||||||
909 | foreach ($this->_m->toArray() as $behavior) { |
||||||
910 | if ($behavior->getEnabled() && (property_exists($behavior, $name) || $behavior->canSetProperty($name) || $behavior->hasEvent($name))) { |
||||||
911 | $behavior->$name = $value; |
||||||
912 | $sets++; |
||||||
913 | } |
||||||
914 | } |
||||||
915 | 211 | if ($sets) { |
|||||
916 | return $value; |
||||||
917 | 211 | } |
|||||
918 | 211 | } |
|||||
919 | 211 | ||||||
920 | if (Prado::method_visible($this, 'get' . $name) || Prado::method_visible($this, 'getjs' . $name)) { |
||||||
921 | 178 | throw new TInvalidOperationException('component_property_readonly', $this::class, $name); |
|||||
922 | 54 | } else { |
|||||
923 | 177 | throw new TInvalidOperationException('component_property_undefined', $this::class, $name); |
|||||
924 | 3 | } |
|||||
925 | 3 | } |
|||||
926 | 3 | ||||||
927 | /** |
||||||
928 | * Checks if a property value is null, there are no events in the object |
||||||
929 | * event list or global event list registered under the name, and, if |
||||||
930 | * behaviors are enabled, |
||||||
931 | 176 | * Do not call this method. This is a PHP magic method that we override |
|||||
932 | * to allow using isset() to detect if a component property is set or not. |
||||||
933 | * This also works for global events. When behaviors are enabled, it |
||||||
934 | * will check for a behavior of the specified name, and also check |
||||||
935 | * the behavior for events and properties. |
||||||
936 | * @param string $name the property name or the event name |
||||||
937 | * @since 3.2.3 |
||||||
938 | */ |
||||||
939 | public function __isset($name) |
||||||
940 | { |
||||||
941 | 92 | if (Prado::method_visible($this, $getter = 'get' . $name)) { |
|||||
942 | return $this->$getter() !== null; |
||||||
943 | 92 | } elseif (Prado::method_visible($this, $jsgetter = 'getjs' . $name)) { |
|||||
944 | 63 | return $this->$jsgetter() !== null; |
|||||
945 | 63 | } elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) { |
|||||
946 | 14 | $name = strtolower($name); |
|||||
947 | return isset($this->_e[$name]) && $this->_e[$name]->getCount(); |
||||||
948 | 63 | } elseif (strncasecmp($name, 'fx', 2) === 0) { |
|||||
949 | 38 | $name = strtolower($name); |
|||||
950 | 38 | return isset(self::$_ue[$name]) && self::$_ue[$name]->getCount(); |
|||||
951 | 38 | } elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->getBehaviorsEnabled()) { |
|||||
952 | 5 | $name = strtolower($name); |
|||||
953 | if (isset($this->_m[$name])) { |
||||||
954 | 38 | return true; |
|||||
955 | 6 | } |
|||||
956 | 6 | foreach ($this->_m->toArray() as $behavior) { |
|||||
957 | 6 | if ($behavior->getEnabled() && (property_exists($behavior, $name) || $behavior->canGetProperty($name) || $behavior->hasEvent($name))) { |
|||||
958 | 6 | return isset($behavior->$name); |
|||||
959 | } |
||||||
960 | } |
||||||
961 | } |
||||||
962 | 4 | return false; |
|||||
963 | } |
||||||
964 | |||||||
965 | /** |
||||||
966 | * Sets a component property to be null. Clears the object or global |
||||||
967 | * events. When enabled, loops through all behaviors and unsets the |
||||||
968 | * property or event. |
||||||
969 | * Do not call this method. This is a PHP magic method that we override |
||||||
970 | * to allow using unset() to set a component property to be null. |
||||||
971 | * @param string $name the property name or the event name |
||||||
972 | * @throws TInvalidOperationException if the property is read only. |
||||||
973 | * @since 3.2.3 |
||||||
974 | */ |
||||||
975 | public function __unset($name) |
||||||
976 | { |
||||||
977 | if (Prado::method_visible($this, $setter = 'set' . $name)) { |
||||||
978 | $this->$setter(null); |
||||||
979 | } elseif (Prado::method_visible($this, $jssetter = 'setjs' . $name)) { |
||||||
980 | $this->$jssetter(null); |
||||||
981 | } elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) { |
||||||
982 | $this->_e[strtolower($name)]->clear(); |
||||||
983 | } elseif (strncasecmp($name, 'fx', 2) === 0) { |
||||||
984 | $this->getEventHandlers($name)->remove([$this, $name]); |
||||||
985 | } elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->getBehaviorsEnabled()) { |
||||||
986 | $name = strtolower($name); |
||||||
987 | if (isset($this->_m[$name])) { |
||||||
988 | $this->detachBehavior($name); |
||||||
989 | } else { |
||||||
990 | $unset = 0; |
||||||
991 | foreach ($this->_m->toArray() as $behavior) { |
||||||
992 | if ($behavior->getEnabled()) { |
||||||
993 | unset($behavior->$name); |
||||||
994 | $unset++; |
||||||
995 | } |
||||||
996 | } |
||||||
997 | if (!$unset && Prado::method_visible($this, 'get' . $name)) { |
||||||
998 | throw new TInvalidOperationException('component_property_readonly', $this::class, $name); |
||||||
999 | } |
||||||
1000 | } |
||||||
1001 | } elseif (Prado::method_visible($this, 'get' . $name)) { |
||||||
1002 | throw new TInvalidOperationException('component_property_readonly', $this::class, $name); |
||||||
1003 | } |
||||||
1004 | } |
||||||
1005 | |||||||
1006 | /** |
||||||
1007 | * Determines whether a property is defined. |
||||||
1008 | * A property is defined if there is a getter or setter method |
||||||
1009 | * defined in the class. Note, property names are case-insensitive. |
||||||
1010 | 44 | * @param string $name the property name |
|||||
1011 | * @return bool whether the property is defined |
||||||
1012 | 44 | */ |
|||||
1013 | 44 | public function hasProperty($name) |
|||||
1014 | { |
||||||
1015 | return $this->canGetProperty($name) || $this->canSetProperty($name); |
||||||
1016 | } |
||||||
1017 | |||||||
1018 | /** |
||||||
1019 | * Determines whether a property can be read. |
||||||
1020 | * A property can be read if the class has a getter method |
||||||
1021 | * for the property name. Note, property name is case-insensitive. |
||||||
1022 | * This also checks for getjs. When enabled, it loops through all |
||||||
1023 | * active behaviors for the get property when undefined by the object. |
||||||
1024 | * @param string $name the property name |
||||||
1025 | 38 | * @return bool whether the property can be read |
|||||
1026 | */ |
||||||
1027 | 38 | public function canGetProperty($name) |
|||||
1028 | { |
||||||
1029 | 38 | if (Prado::method_visible($this, 'get' . $name) || Prado::method_visible($this, 'getjs' . $name)) { |
|||||
1030 | 38 | return true; |
|||||
1031 | 1 | } elseif ($this->_m !== null && $this->getBehaviorsEnabled()) { |
|||||
1032 | foreach ($this->_m->toArray() as $behavior) { |
||||||
1033 | if ($behavior->getEnabled() && $behavior->canGetProperty($name)) { |
||||||
1034 | 2 | return true; |
|||||
1035 | } |
||||||
1036 | } |
||||||
1037 | } |
||||||
1038 | return false; |
||||||
1039 | } |
||||||
1040 | |||||||
1041 | /** |
||||||
1042 | * Determines whether a property can be set. |
||||||
1043 | * A property can be written if the class has a setter method |
||||||
1044 | * for the property name. Note, property name is case-insensitive. |
||||||
1045 | * This also checks for setjs. When enabled, it loops through all |
||||||
1046 | * active behaviors for the set property when undefined by the object. |
||||||
1047 | * @param string $name the property name |
||||||
1048 | * @return bool whether the property can be written |
||||||
1049 | */ |
||||||
1050 | public function canSetProperty($name) |
||||||
1051 | { |
||||||
1052 | if (Prado::method_visible($this, 'set' . $name) || Prado::method_visible($this, 'setjs' . $name)) { |
||||||
1053 | return true; |
||||||
1054 | } elseif ($this->_m !== null && $this->getBehaviorsEnabled()) { |
||||||
1055 | foreach ($this->_m->toArray() as $behavior) { |
||||||
1056 | if ($behavior->getEnabled() && $behavior->canSetProperty($name)) { |
||||||
1057 | return true; |
||||||
1058 | } |
||||||
1059 | } |
||||||
1060 | } |
||||||
1061 | return false; |
||||||
1062 | } |
||||||
1063 | |||||||
1064 | /** |
||||||
1065 | * Evaluates a property path. |
||||||
1066 | * A property path is a sequence of property names concatenated by '.' character. |
||||||
1067 | * For example, 'Parent.Page' refers to the 'Page' property of the component's |
||||||
1068 | * 'Parent' property value (which should be a component also). |
||||||
1069 | * When a property is not defined by an object, this also loops through all |
||||||
1070 | * active behaviors of the object. |
||||||
1071 | * @param string $path property path |
||||||
1072 | * @return mixed the property path value |
||||||
1073 | */ |
||||||
1074 | public function getSubProperty($path) |
||||||
1075 | { |
||||||
1076 | $object = $this; |
||||||
1077 | foreach (explode('.', $path) as $property) { |
||||||
1078 | $object = $object->$property; |
||||||
1079 | } |
||||||
1080 | return $object; |
||||||
1081 | } |
||||||
1082 | |||||||
1083 | /** |
||||||
1084 | * Sets a value to a property path. |
||||||
1085 | * A property path is a sequence of property names concatenated by '.' character. |
||||||
1086 | * For example, 'Parent.Page' refers to the 'Page' property of the component's |
||||||
1087 | * 'Parent' property value (which should be a component also). |
||||||
1088 | * When a property is not defined by an object, this also loops through all |
||||||
1089 | * active behaviors of the object. |
||||||
1090 | * @param string $path property path |
||||||
1091 | * @param mixed $value the property path value |
||||||
1092 | */ |
||||||
1093 | public function setSubProperty($path, $value) |
||||||
1094 | { |
||||||
1095 | $object = $this; |
||||||
1096 | if (($pos = strrpos($path, '.')) === false) { |
||||||
1097 | $property = $path; |
||||||
1098 | } else { |
||||||
1099 | $object = $this->getSubProperty(substr($path, 0, $pos)); |
||||||
1100 | $property = substr($path, $pos + 1); |
||||||
1101 | } |
||||||
1102 | $object->$property = $value; |
||||||
1103 | } |
||||||
1104 | |||||||
1105 | /** |
||||||
1106 | * Calls a method on a component's behaviors. When the method is a |
||||||
1107 | * dynamic event, it is raised with all the behaviors. When a class implements |
||||||
1108 | * a dynamic event (eg. for patching), the class can customize raising the |
||||||
1109 | 182 | * dynamic event with the classes behaviors using this method. |
|||||
1110 | * Dynamic [dy] and global [fx] events call {@see __dycall} when $this |
||||||
1111 | 182 | * implements IDynamicMethods. Finally, this catches all unexecuted |
|||||
1112 | 182 | * Dynamic [dy] and global [fx] events and returns the first $args parameter; |
|||||
1113 | 1 | * acting as a passthrough (filter) of the first $args parameter. In dy/fx methods, |
|||||
1114 | 1 | * there can be no $args parameters, the first parameter used as a pass through |
|||||
1115 | * filter, or act as a return variable with the first $args parameter being |
||||||
1116 | * the default return value. |
||||||
1117 | 182 | * @param string $method The method being called or dynamic/global event being raised. |
|||||
1118 | 181 | * @param mixed &$return The return value. |
|||||
1119 | * @param array $args The arguments to the method being called. |
||||||
1120 | * @return bool Was the method handled. |
||||||
1121 | 182 | * @since 4.3.0 |
|||||
1122 | 182 | */ |
|||||
1123 | public function callBehaviorsMethod($method, &$return, ...$args): bool |
||||||
1124 | 182 | { |
|||||
1125 | if ($this->_m !== null && $this->getBehaviorsEnabled()) { |
||||||
1126 | 182 | if (strncasecmp($method, 'dy', 2) === 0) { |
|||||
1127 | 60 | if ($callchain = $this->getCallChain($method, ...$args)) { |
|||||
1128 | 60 | $return = $callchain->call(...$args); |
|||||
1129 | 60 | return true; |
|||||
1130 | 1 | } |
|||||
1131 | 1 | } else { |
|||||
1132 | foreach ($this->_m->toArray() as $behavior) { |
||||||
1133 | 60 | if ($behavior->getEnabled() && Prado::method_visible($behavior, $method)) { |
|||||
1134 | 60 | if ($behavior instanceof IClassBehavior) { |
|||||
1135 | 59 | array_unshift($args, $this); |
|||||
1136 | } |
||||||
1137 | $return = $behavior->$method(...$args); |
||||||
1138 | return true; |
||||||
1139 | 59 | } |
|||||
1140 | } |
||||||
1141 | } |
||||||
1142 | } |
||||||
1143 | if (strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) { |
||||||
1144 | if ($this instanceof IDynamicMethods) { |
||||||
1145 | $return = $this->__dycall($method, $args); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1146 | return true; |
||||||
1147 | } |
||||||
1148 | $return = $args[0] ?? null; |
||||||
1149 | return true; |
||||||
1150 | } |
||||||
1151 | return false; |
||||||
1152 | } |
||||||
1153 | |||||||
1154 | /** |
||||||
1155 | 59 | * This gets the chain of methods implemented by attached and enabled behaviors. |
|||||
1156 | 59 | * This method disregards the {behaviorsEnabled |
|||||
1157 | 59 | * @param string $method The name of the behaviors method being chained. |
|||||
1158 | * @param array $args The arguments to the behaviors method being chained. |
||||||
1159 | * @return ?TCallChain The chain of methods implemented by behaviors or null when |
||||||
1160 | 59 | * there are no methods to call. |
|||||
1161 | 1 | * @since 4.3.0 |
|||||
1162 | 1 | */ |
|||||
1163 | protected function getCallChain($method, ...$args): ?TCallChain |
||||||
1164 | 59 | { |
|||||
1165 | 59 | $classArgs = $callchain = null; |
|||||
1166 | 1 | foreach ($this->_m->toArray() as $behavior) { |
|||||
1167 | if ($behavior->getEnabled() && (Prado::method_visible($behavior, $method) || ($behavior instanceof IDynamicMethods))) { |
||||||
1168 | 59 | if ($classArgs === null) { |
|||||
1169 | $classArgs = $args; |
||||||
1170 | array_unshift($classArgs, $this); |
||||||
1171 | 59 | } |
|||||
1172 | if (!$callchain) { |
||||||
1173 | $callchain = new TCallChain($method); |
||||||
1174 | } |
||||||
1175 | $callchain->addCall([$behavior, $method], ($behavior instanceof IClassBehavior) ? $classArgs : $args); |
||||||
1176 | } |
||||||
1177 | } |
||||||
1178 | 59 | return $callchain; |
|||||
1179 | } |
||||||
1180 | 59 | ||||||
1181 | 1 | /** |
|||||
1182 | * Determines whether a method is defined. When behaviors are enabled, this |
||||||
1183 | * will loop through all enabled behaviors checking for the method as well. |
||||||
1184 | 59 | * Nested behaviors within behaviors are not supported but the nested behavior can |
|||||
1185 | 2 | * affect the primary behavior like any behavior affects their owner. |
|||||
1186 | * Note, method name are case-insensitive. |
||||||
1187 | 58 | * @param string $name the method name |
|||||
1188 | * @return bool |
||||||
1189 | * @since 4.2.2 |
||||||
1190 | 59 | */ |
|||||
1191 | 60 | public function hasMethod($name) |
|||||
1192 | { |
||||||
1193 | if (Prado::method_visible($this, $name) || strncasecmp($name, 'dy', 2) === 0) { |
||||||
1194 | 173 | return true; |
|||||
1195 | 1 | } elseif ($this->_m !== null && $this->getBehaviorsEnabled()) { |
|||||
1196 | foreach ($this->_m->toArray() as $behavior) { |
||||||
1197 | //Prado::method_visible($behavior, $name) rather than $behavior->hasMethod($name) b/c only one layer is supported, @4.2.2 |
||||||
1198 | 182 | if ($behavior->getEnabled() && Prado::method_visible($behavior, $name)) { |
|||||
1199 | 181 | return true; |
|||||
1200 | } |
||||||
1201 | } |
||||||
1202 | 182 | } |
|||||
1203 | return false; |
||||||
1204 | 182 | } |
|||||
1205 | |||||||
1206 | /** |
||||||
1207 | * Determines whether an event is defined. |
||||||
1208 | * An event is defined if the class has a method whose name is the event name |
||||||
1209 | * prefixed with 'on', 'fx', or 'dy'. |
||||||
1210 | * Every object responds to every 'fx' and 'dy' event as they are in a universally |
||||||
1211 | * accepted event space. 'on' event must be declared by the object. |
||||||
1212 | * When enabled, this will loop through all active behaviors for 'on' events |
||||||
1213 | * defined by the behavior. |
||||||
1214 | * Note, event name is case-insensitive. |
||||||
1215 | * @param string $name the event name |
||||||
1216 | * @return bool |
||||||
1217 | */ |
||||||
1218 | public function hasEvent($name) |
||||||
1219 | { |
||||||
1220 | if ((strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) || strncasecmp($name, 'fx', 2) === 0 || strncasecmp($name, 'dy', 2) === 0) { |
||||||
1221 | return true; |
||||||
1222 | } elseif ($this->_m !== null && $this->getBehaviorsEnabled()) { |
||||||
1223 | foreach ($this->_m->toArray() as $behavior) { |
||||||
1224 | 2 | if ($behavior->getEnabled() && $behavior->hasEvent($name)) { |
|||||
1225 | return true; |
||||||
1226 | 2 | } |
|||||
1227 | } |
||||||
1228 | 2 | } |
|||||
1229 | return false; |
||||||
1230 | } |
||||||
1231 | 2 | ||||||
1232 | 1 | /** |
|||||
1233 | 1 | * Checks if an event has any handlers. This function also checks through all |
|||||
1234 | * the behaviors for 'on' events when behaviors are enabled. |
||||||
1235 | * 'dy' dynamic events are not handled by this function. |
||||||
1236 | * @param string $name the event name |
||||||
1237 | * @return bool whether an event has been attached one or several handlers |
||||||
1238 | */ |
||||||
1239 | public function hasEventHandler($name) |
||||||
1240 | { |
||||||
1241 | $name = strtolower($name); |
||||||
1242 | if (strncasecmp($name, 'fx', 2) === 0) { |
||||||
1243 | return isset(self::$_ue[$name]) && self::$_ue[$name]->getCount() > 0; |
||||||
1244 | } else { |
||||||
1245 | if (isset($this->_e[$name]) && $this->_e[$name]->getCount() > 0) { |
||||||
1246 | return true; |
||||||
1247 | } elseif ($this->_m !== null && $this->getBehaviorsEnabled()) { |
||||||
1248 | foreach ($this->_m->toArray() as $behavior) { |
||||||
1249 | if ($behavior->getEnabled() && $behavior->hasEventHandler($name)) { |
||||||
1250 | return true; |
||||||
1251 | } |
||||||
1252 | } |
||||||
1253 | } |
||||||
1254 | 2 | } |
|||||
1255 | return false; |
||||||
1256 | 2 | } |
|||||
1257 | |||||||
1258 | 2 | /** |
|||||
1259 | 2 | * Returns the list of attached event handlers for an 'on' or 'fx' event. This function also |
|||||
1260 | * checks through all the behaviors for 'on' event lists when behaviors are enabled. |
||||||
1261 | * @param mixed $name |
||||||
1262 | 2 | * @throws TInvalidOperationException if the event is not defined |
|||||
1263 | 2 | * @return TWeakCallableCollection list of attached event handlers for an event |
|||||
1264 | 2 | */ |
|||||
1265 | 1 | public function getEventHandlers($name) |
|||||
1266 | 1 | { |
|||||
1267 | if (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) { |
||||||
1268 | $name = strtolower($name); |
||||||
1269 | if (!isset($this->_e[$name])) { |
||||||
1270 | $this->_e[$name] = new TWeakCallableCollection(); |
||||||
1271 | } |
||||||
1272 | return $this->_e[$name]; |
||||||
1273 | } elseif (strncasecmp($name, 'fx', 2) === 0) { |
||||||
1274 | $name = strtolower($name); |
||||||
1275 | if (!isset(self::$_ue[$name])) { |
||||||
1276 | self::$_ue[$name] = new TWeakCallableCollection(); |
||||||
1277 | } |
||||||
1278 | return self::$_ue[$name]; |
||||||
1279 | } elseif ($this->_m !== null && $this->getBehaviorsEnabled()) { |
||||||
1280 | foreach ($this->_m->toArray() as $behavior) { |
||||||
1281 | if ($behavior->getEnabled() && $behavior->hasEvent($name)) { |
||||||
1282 | return $behavior->getEventHandlers($name); |
||||||
1283 | } |
||||||
1284 | } |
||||||
1285 | } |
||||||
1286 | throw new TInvalidOperationException('component_event_undefined', $this::class, $name); |
||||||
1287 | } |
||||||
1288 | |||||||
1289 | 1 | /** |
|||||
1290 | * Attaches an event handler to an event. |
||||||
1291 | 1 | * |
|||||
1292 | 1 | * The handler must be a valid PHP callback, i.e., a string referring to |
|||||
1293 | 1 | * a global function name, or an array containing two elements with |
|||||
1294 | * the first element being an object and the second element a method name |
||||||
1295 | * of the object. In Prado, you can also use method path to refer to |
||||||
1296 | * an event handler. For example, array($object,'Parent.buttonClicked') |
||||||
1297 | * uses a method path that refers to the method $object->Parent->buttonClicked(...). |
||||||
1298 | * |
||||||
1299 | * The event handler must be of the following signature, |
||||||
1300 | * ```php |
||||||
1301 | * function handlerName($sender, $param) {} |
||||||
1302 | * function handlerName($sender, $param, $name) {} |
||||||
1303 | * ``` |
||||||
1304 | * where $sender represents the object that raises the event, |
||||||
1305 | * and $param is the event parameter. $name refers to the event name |
||||||
1306 | * being handled. |
||||||
1307 | * |
||||||
1308 | * This is a convenient method to add an event handler. |
||||||
1309 | * It is equivalent to {@see getEventHandlers}($name)->add($handler). |
||||||
1310 | * For complete management of event handlers, use {@see getEventHandlers} |
||||||
1311 | * to get the event handler list first, and then do various |
||||||
1312 | 1 | * {@see \Prado\Collections\TWeakCallableCollection} operations to append, insert or remove |
|||||
1313 | * event handlers. You may also do these operations like |
||||||
1314 | 1 | * getting and setting properties, e.g., |
|||||
1315 | 1 | * ```php |
|||||
1316 | * $component->OnClick[] = array($object,'buttonClicked'); |
||||||
1317 | * $component->OnClick->insertAt(0,array($object,'buttonClicked')); |
||||||
1318 | * $component->OnClick[] = function ($sender, $param) { ... }; |
||||||
1319 | * ``` |
||||||
1320 | * which are equivalent to the following |
||||||
1321 | * ```php |
||||||
1322 | * $component->getEventHandlers('OnClick')->add(array($object,'buttonClicked')); |
||||||
1323 | * $component->getEventHandlers('OnClick')->insertAt(0,array($object,'buttonClicked')); |
||||||
1324 | * ``` |
||||||
1325 | * |
||||||
1326 | 6 | * Due to the nature of {@see getEventHandlers}, any active behaviors defining |
|||||
1327 | * new 'on' events, this method will pass through to the behavior transparently. |
||||||
1328 | 6 | * |
|||||
1329 | 6 | * @param string $name the event name |
|||||
1330 | * @param callable $handler the event handler |
||||||
1331 | 1 | * @param null|numeric $priority the priority of the handler, defaults to null which translates into the |
|||||
1332 | * default priority of 10.0 within {@see \Prado\Collections\TWeakCallableCollection} |
||||||
1333 | * @throws TInvalidOperationException if the event does not exist |
||||||
1334 | */ |
||||||
1335 | public function attachEventHandler($name, $handler, $priority = null) |
||||||
1336 | { |
||||||
1337 | $this->getEventHandlers($name)->add($handler, $priority); |
||||||
1338 | } |
||||||
1339 | |||||||
1340 | /** |
||||||
1341 | * Detaches an existing event handler. |
||||||
1342 | 6 | * This method is the opposite of {@see attachEventHandler}. It will detach |
|||||
1343 | * any 'on' events defined by an objects active behaviors as well. |
||||||
1344 | 6 | * @param string $name event name |
|||||
1345 | 6 | * @param callable $handler the event handler to be removed |
|||||
1346 | * @param null|false|numeric $priority the priority of the handler, defaults to false which translates |
||||||
1347 | 1 | * to an item of any priority within {@see \Prado\Collections\TWeakCallableCollection}; null means the default priority |
|||||
1348 | * @return bool if the removal is successful |
||||||
1349 | */ |
||||||
1350 | public function detachEventHandler($name, $handler, $priority = false) |
||||||
1351 | { |
||||||
1352 | if ($this->hasEventHandler($name)) { |
||||||
1353 | try { |
||||||
1354 | $this->getEventHandlers($name)->remove($handler, $priority); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1355 | return true; |
||||||
1356 | } catch (\Exception $e) { |
||||||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
|
|||||||
1357 | } |
||||||
1358 | } |
||||||
1359 | return false; |
||||||
1360 | } |
||||||
1361 | |||||||
1362 | /** |
||||||
1363 | * Raises an event. This raises both inter-object 'on' events and global 'fx' events. |
||||||
1364 | * This method represents the happening of an event and will |
||||||
1365 | * invoke all attached event handlers for the event in {@see \Prado\Collections\TWeakCallableCollection} order. |
||||||
1366 | * This method does not handle intra-object/behavior dynamic 'dy' events. |
||||||
1367 | * |
||||||
1368 | 6 | * There are ways to handle event responses. By default {@see EVENT_RESULT_FILTER}, |
|||||
1369 | * all event responses are stored in an array, filtered for null responses, and returned. |
||||||
1370 | 6 | * If {@see EVENT_RESULT_ALL} is specified, all returned results will be stored along |
|||||
1371 | 5 | * with the sender and param in an array |
|||||
1372 | * ```php |
||||||
1373 | 6 | * $result[] = array('sender'=>$sender,'param'=>$param,'response'=>$response); |
|||||
1374 | * ``` |
||||||
1375 | * |
||||||
1376 | * If {@see EVENT_RESULT_FEED_FORWARD} is specified, then each handler result is then |
||||||
1377 | 6 | * fed forward as the parameters for the next event. This allows for events to filter data |
|||||
1378 | * directly by affecting the event parameters |
||||||
1379 | * |
||||||
1380 | 6 | * If a callable function is set in the response type or the post function filter is specified then the |
|||||
1381 | 6 | * result of each called event handler is post processed by the callable function. Used in |
|||||
1382 | 1 | * combination with {@see EVENT_RESULT_FEED_FORWARD}, any event (and its result) can be chained. |
|||||
1383 | * |
||||||
1384 | 6 | * When raising a global 'fx' event, registered handlers in the global event list for |
|||||
1385 | 6 | * {@see GLOBAL_RAISE_EVENT_LISTENER} are always added into the set of event handlers. In this way, |
|||||
1386 | * these global events are always raised for every global 'fx' event. The registered handlers for global |
||||||
1387 | 6 | * raiseEvent events have priorities. Any registered global raiseEvent event handlers with a priority less than zero |
|||||
1388 | 1 | * are added before the main event handlers being raised and any registered global raiseEvent event handlers |
|||||
1389 | * with a priority equal or greater than zero are added after the main event handlers being raised. In this way |
||||||
1390 | 6 | * all {@see GLOBAL_RAISE_EVENT_LISTENER} handlers are always called for every raised 'fx' event. |
|||||
1391 | 6 | * |
|||||
1392 | 6 | * Behaviors may implement the following functions with TBehaviors: |
|||||
1393 | 6 | * ```php |
|||||
1394 | * public function dyPreRaiseEvent($name, $sender, $param, $responsetype, $postfunction, TCallChain $chain) { |
||||||
1395 | * .... // Your logic |
||||||
1396 | * return $chain->dyPreRaiseEvent($name, $sender, $param, $responsetype, $postfunction); //eg, the event name may be filtered/changed |
||||||
1397 | * } |
||||||
1398 | * public function dyIntraRaiseEventTestHandler($handler, $sender, $param, $name, TCallChain $chain) { |
||||||
1399 | * .... // Your logic |
||||||
1400 | * return $chain->dyIntraRaiseEventTestHandler($handler, $sender, $param, $name); //should this particular handler be executed? true/false |
||||||
1401 | * } |
||||||
1402 | * public function dyIntraRaiseEventPostHandler($name, $sender, $param, $handler, $response, TCallChain $chain) { |
||||||
1403 | * .... // Your logic |
||||||
1404 | * return $chain->dyIntraRaiseEventPostHandler($name, $sender, $param, $handler, $response); //contains the per handler response |
||||||
1405 | * } |
||||||
1406 | * public function dyPostRaiseEvent($responses, $name, $sender, $param, $responsetype, $postfunction, TCallChain $chain) { |
||||||
1407 | * .... // Your logic |
||||||
1408 | * return $chain->dyPostRaiseEvent($responses, $name, $sender, $param, $responsetype, $postfunction); |
||||||
1409 | 6 | * } |
|||||
1410 | * ``` |
||||||
1411 | 6 | * to be executed when raiseEvent is called. The 'intra' dynamic events are called per handler in |
|||||
1412 | 5 | * the handler loop. TClassBehaviors prepend the object being raised. |
|||||
1413 | * |
||||||
1414 | 6 | * dyPreRaiseEvent has the effect of being able to change the event being raised. This intra |
|||||
1415 | * object/behavior event returns the name of the desired event to be raised. It will pass through |
||||||
1416 | * if no dynamic event is specified, or if the original event name is returned. |
||||||
1417 | * dyIntraRaiseEventTestHandler returns true or false as to whether a specific handler should be |
||||||
1418 | 6 | * called for a specific raised event (and associated event arguments) |
|||||
1419 | 6 | * dyIntraRaiseEventPostHandler does not return anything. This allows behaviors to access the results |
|||||
1420 | * of an event handler in the per handler loop. |
||||||
1421 | * dyPostRaiseEvent returns the responses. This allows for any post processing of the event |
||||||
1422 | 6 | * results from the sum of all event handlers |
|||||
1423 | * |
||||||
1424 | * When handling a catch-all {@see __dycall}, the method name is the name of the event |
||||||
1425 | 6 | * and the parameters are the sender, the param, and then the name of the event. |
|||||
1426 | 6 | * |
|||||
1427 | 6 | * In the rare circumstance that the event handlers need to be raised in reverse order, then |
|||||
1428 | 6 | * specifying {@see \Prado\TEventResults::EVENT_REVERSE} can be used to reverse the order of the |
|||||
1429 | 6 | * handlers. |
|||||
1430 | * |
||||||
1431 | * @param string $name the event name |
||||||
1432 | * @param mixed $sender the event sender object |
||||||
1433 | * @param \Prado\TEventParameter $param the event parameter |
||||||
1434 | * @param null|numeric $responsetype how the results of the event are tabulated. default: {@see EVENT_RESULT_FILTER} The default filters out |
||||||
1435 | * null responses. optional |
||||||
1436 | * @param null|callable $postfunction any per handler filtering of the response result needed is passed through |
||||||
1437 | * this if not null. default: null. optional |
||||||
1438 | * @throws TInvalidOperationException if the event is undefined |
||||||
1439 | 8 | * @throws TInvalidDataValueException If an event handler is invalid |
|||||
1440 | * @return mixed the results of the event |
||||||
1441 | 8 | */ |
|||||
1442 | public function raiseEvent($name, $sender, $param, $responsetype = null, $postfunction = null) |
||||||
1443 | { |
||||||
1444 | $p = $param; |
||||||
0 ignored issues
–
show
|
|||||||
1445 | if (is_callable($responsetype)) { |
||||||
1446 | $postfunction = $responsetype; |
||||||
1447 | $responsetype = null; |
||||||
1448 | } |
||||||
1449 | |||||||
1450 | if ($responsetype === null) { |
||||||
1451 | $responsetype = TEventResults::EVENT_RESULT_FILTER; |
||||||
1452 | } |
||||||
1453 | |||||||
1454 | $name = strtolower($name); |
||||||
1455 | $responses = []; |
||||||
1456 | |||||||
1457 | if ($param instanceof IEventParameter) { |
||||||
0 ignored issues
–
show
|
|||||||
1458 | $param->setEventName($name); |
||||||
1459 | } |
||||||
1460 | |||||||
1461 | $this->callBehaviorsMethod('dyPreRaiseEvent', $name, $name, $sender, $param, $responsetype, $postfunction); |
||||||
1462 | |||||||
1463 | 6 | if ($this->hasEventHandler($name) || $this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER)) { |
|||||
1464 | $handlers = $this->getEventHandlers($name); |
||||||
1465 | 6 | $handlerArray = $handlers->toArray(); |
|||||
1466 | 6 | if (strncasecmp($name, 'fx', 2) === 0 && $this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER)) { |
|||||
1467 | $globalhandlers = $this->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER); |
||||||
1468 | 6 | $handlerArray = array_merge($globalhandlers->toArrayBelowPriority(0), $handlerArray, $globalhandlers->toArrayAbovePriority(0)); |
|||||
0 ignored issues
–
show
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
![]() 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
![]() |
|||||||
1469 | 6 | } |
|||||
1470 | 6 | $response = null; |
|||||
1471 | 2 | if ($responsetype & TEventResults::EVENT_REVERSE) { |
|||||
1472 | $handlerArray = array_reverse($handlerArray); |
||||||
1473 | } |
||||||
1474 | 6 | foreach ($handlerArray as $handler) { |
|||||
1475 | 6 | $this->callBehaviorsMethod('dyIntraRaiseEventTestHandler', $return, $handler, $sender, $param, $name); |
|||||
1476 | 1 | if ($return === false) { |
|||||
1477 | continue; |
||||||
1478 | 6 | } |
|||||
1479 | 6 | ||||||
1480 | if (is_string($handler)) { |
||||||
1481 | if (($pos = strrpos($handler, '.')) !== false) { |
||||||
1482 | $object = $this->getSubProperty(substr($handler, 0, $pos)); |
||||||
1483 | 6 | $method = substr($handler, $pos + 1); |
|||||
1484 | if (Prado::method_visible($object, $method) || strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) { |
||||||
1485 | if ($method == '__dycall') { |
||||||
1486 | $response = $object->__dycall($name, [$sender, $param]); |
||||||
1487 | } else { |
||||||
1488 | $response = $object->$method($sender, $param); |
||||||
1489 | } |
||||||
1490 | } else { |
||||||
1491 | throw new TInvalidDataValueException('component_eventhandler_invalid', $this::class, $name, $handler); |
||||||
1492 | } |
||||||
1493 | } else { |
||||||
1494 | 37 | $response = call_user_func($handler, $sender, $param); |
|||||
1495 | } |
||||||
1496 | 37 | } elseif (is_callable($handler, true)) { |
|||||
1497 | 6 | if (is_object($handler) || is_string($handler[0])) { |
|||||
1498 | 4 | $response = call_user_func($handler, $sender, $param); |
|||||
1499 | } else { |
||||||
1500 | 6 | [$object, $method] = $handler; |
|||||
1501 | if (($pos = strrpos($method, '.')) !== false) { |
||||||
1502 | $object = $object->getSubProperty(substr($method, 0, $pos)); |
||||||
1503 | 37 | $method = substr($method, $pos + 1); |
|||||
1504 | } |
||||||
1505 | if (Prado::method_visible($object, $method) || strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) { |
||||||
1506 | if ($method == '__dycall') { |
||||||
1507 | $response = $object->__dycall($name, [$sender, $param]); |
||||||
1508 | } else { |
||||||
1509 | $response = $object->$method($sender, $param); |
||||||
1510 | } |
||||||
1511 | } else { |
||||||
1512 | throw new TInvalidDataValueException('component_eventhandler_invalid', $this::class, $name, $handler[1]); |
||||||
1513 | 1 | } |
|||||
1514 | } |
||||||
1515 | 1 | } else { |
|||||
1516 | 1 | throw new TInvalidDataValueException('component_eventhandler_invalid', $this::class, $name, gettype($handler)); |
|||||
1517 | 1 | } |
|||||
1518 | |||||||
1519 | $this->callBehaviorsMethod('dyIntraRaiseEventPostHandler', $return, $name, $sender, $param, $handler, $response); |
||||||
1520 | 1 | ||||||
1521 | if ($postfunction) { |
||||||
1522 | $response = call_user_func_array($postfunction, [$sender, $param, $this, $response]); |
||||||
1523 | } |
||||||
1524 | 1 | ||||||
1525 | if ($responsetype & TEventResults::EVENT_RESULT_ALL) { |
||||||
1526 | $responses[] = ['sender' => $sender, 'param' => $param, 'response' => $response]; |
||||||
1527 | } else { |
||||||
1528 | $responses[] = $response; |
||||||
1529 | } |
||||||
1530 | 1 | ||||||
1531 | if ($response !== null && ($responsetype & TEventResults::EVENT_RESULT_FEED_FORWARD)) { |
||||||
1532 | 1 | $param = $response; |
|||||
1533 | 1 | } |
|||||
1534 | 1 | } |
|||||
1535 | } elseif (strncasecmp($name, 'on', 2) === 0 && !$this->hasEvent($name)) { |
||||||
1536 | 1 | throw new TInvalidOperationException('component_event_undefined', $this::class, $name); |
|||||
1537 | } |
||||||
1538 | 1 | ||||||
1539 | if ($responsetype & TEventResults::EVENT_RESULT_FILTER) { |
||||||
1540 | $responses = array_filter($responses); |
||||||
1541 | } |
||||||
1542 | |||||||
1543 | $this->callBehaviorsMethod('dyPostRaiseEvent', $responses, $responses, $name, $sender, $param, $responsetype, $postfunction); |
||||||
1544 | |||||||
1545 | return $responses; |
||||||
1546 | } |
||||||
1547 | |||||||
1548 | /** |
||||||
1549 | * Evaluates a PHP expression in the context of this control. |
||||||
1550 | * |
||||||
1551 | * Behaviors may implement the function: |
||||||
1552 | * ```php |
||||||
1553 | * public function dyEvaluateExpressionFilter($expression, TCallChain $chain) { |
||||||
1554 | * return $chain->dyEvaluateExpressionFilter(str_replace('foo', 'bar', $expression)); //example |
||||||
1555 | * } |
||||||
1556 | * ``` |
||||||
1557 | * to be executed when evaluateExpression is called. All attached behaviors are notified through |
||||||
1558 | * dyEvaluateExpressionFilter. The chaining is important in this function due to the filtering |
||||||
1559 | * pass-through effect. |
||||||
1560 | * |
||||||
1561 | 27 | * @param string $expression PHP expression |
|||||
1562 | * @throws TInvalidOperationException if the expression is invalid |
||||||
1563 | 27 | * @return mixed the expression result |
|||||
1564 | 3 | */ |
|||||
1565 | public function evaluateExpression($expression) |
||||||
1566 | 27 | { |
|||||
1567 | 1 | $this->callBehaviorsMethod('dyEvaluateExpressionFilter', $expression, $expression); |
|||||
1568 | try { |
||||||
1569 | 27 | return eval("return $expression;"); |
|||||
0 ignored issues
–
show
|
|||||||
1570 | 26 | } catch (\Throwable $e) { |
|||||
0 ignored issues
–
show
catch (\Throwable $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 function fx() {
try {
doSomething();
return true;
}
catch (\Exception $e) {
return false;
}
return false;
}
In the above example, the last ![]() |
|||||||
1571 | throw new TInvalidOperationException('component_expression_invalid', $this::class, $expression, $e->getMessage()); |
||||||
1572 | 27 | } |
|||||
1573 | 27 | } |
|||||
1574 | |||||||
1575 | 27 | /** |
|||||
1576 | 27 | * Evaluates a list of PHP statements. |
|||||
1577 | 27 | * |
|||||
1578 | 27 | * Behaviors may implement the function: |
|||||
1579 | * ```php |
||||||
1580 | * public function dyEvaluateStatementsFilter($statements, TCallChain $chain) { |
||||||
1581 | * return $chain->dyEvaluateStatementsFilter(str_replace('foo', 'bar', $statements)); //example |
||||||
1582 | * } |
||||||
1583 | * ``` |
||||||
1584 | * to be executed when evaluateStatements is called. All attached behaviors are notified through |
||||||
1585 | * dyEvaluateStatementsFilter. The chaining is important in this function due to the filtering |
||||||
1586 | * pass-through effect. |
||||||
1587 | * |
||||||
1588 | * @param string $statements PHP statements |
||||||
1589 | * @throws TInvalidOperationException if the statements are invalid |
||||||
1590 | * @return string content echoed or printed by the PHP statements |
||||||
1591 | */ |
||||||
1592 | public function evaluateStatements($statements) |
||||||
1593 | { |
||||||
1594 | $this->callBehaviorsMethod('dyEvaluateStatementsFilter', $statements, $statements); |
||||||
1595 | try { |
||||||
1596 | ob_start(); |
||||||
1597 | if (eval($statements) === false) { |
||||||
0 ignored issues
–
show
|
|||||||
1598 | 18 | throw new \Exception(''); |
|||||
1599 | } |
||||||
1600 | 18 | $content = ob_get_contents(); |
|||||
1601 | 18 | ob_end_clean(); |
|||||
1602 | 18 | return $content; |
|||||
1603 | 18 | } catch (\Exception $e) { |
|||||
1604 | 18 | throw new TInvalidOperationException('component_statements_invalid', $this::class, $statements, $e->getMessage()); |
|||||
1605 | 18 | } |
|||||
1606 | } |
||||||
1607 | |||||||
1608 | /** |
||||||
1609 | * This method is invoked after the component is instantiated by a template. |
||||||
1610 | * When this method is invoked, the component's properties have been initialized. |
||||||
1611 | * The default implementation of this method will invoke |
||||||
1612 | * the potential parent component's {@see addParsedObject}. |
||||||
1613 | * This method can be overridden. |
||||||
1614 | * |
||||||
1615 | * Behaviors may implement the function: |
||||||
1616 | * ```php |
||||||
1617 | * public function dyCreatedOnTemplate($parent, TCallChain $chain) { |
||||||
1618 | * return $chain->dyCreatedOnTemplate($parent); //example |
||||||
1619 | * } |
||||||
1620 | * ``` |
||||||
1621 | 8 | * to be executed when createdOnTemplate is called. All attached behaviors are notified through |
|||||
1622 | * dyCreatedOnTemplate. |
||||||
1623 | 8 | * |
|||||
1624 | 8 | * @param \Prado\TComponent $parent potential parent of this control |
|||||
1625 | 8 | * @see addParsedObject |
|||||
1626 | */ |
||||||
1627 | 8 | public function createdOnTemplate($parent) |
|||||
1628 | { |
||||||
1629 | $this->callBehaviorsMethod('dyCreatedOnTemplate', $parent, $parent); |
||||||
1630 | $parent->addParsedObject($this); |
||||||
1631 | } |
||||||
1632 | |||||||
1633 | /** |
||||||
1634 | * Processes an object that is created during parsing template. |
||||||
1635 | * The object can be either a component or a static text string. |
||||||
1636 | * This method can be overridden to customize the handling of newly created objects in template. |
||||||
1637 | * Only framework developers and control developers should use this method. |
||||||
1638 | * |
||||||
1639 | * Behaviors may implement the function: |
||||||
1640 | * ```php |
||||||
1641 | 8 | * public function dyAddParsedObject($object, TCallChain $chain) { |
|||||
1642 | * return $chain-> dyAddParsedObject($object); |
||||||
1643 | 8 | * } |
|||||
1644 | 8 | * ``` |
|||||
1645 | 8 | * to be executed when addParsedObject is called. All attached behaviors are notified through |
|||||
1646 | * dyAddParsedObject. |
||||||
1647 | 8 | * |
|||||
1648 | * @param \Prado\TComponent|string $object text string or component parsed and instantiated in template |
||||||
1649 | * @see createdOnTemplate |
||||||
1650 | */ |
||||||
1651 | public function addParsedObject($object) |
||||||
1652 | { |
||||||
1653 | $this->callBehaviorsMethod('dyAddParsedObject', $return, $object); |
||||||
1654 | } |
||||||
1655 | |||||||
1656 | /** |
||||||
1657 | *This is the method registered for all instanced objects should a class behavior be added after |
||||||
1658 | * the class is instanced. Only when the class to which the behavior is being added is in this |
||||||
1659 | * object's class hierarchy, via {@see getClassHierarchy}, is the behavior added to this instance. |
||||||
1660 | * @param mixed $sender the application |
||||||
1661 | * @param TClassBehaviorEventParameter $param |
||||||
1662 | * @since 3.2.3 |
||||||
1663 | */ |
||||||
1664 | public function fxAttachClassBehavior($sender, $param) |
||||||
1665 | { |
||||||
1666 | if ($this->isa($param->getClass())) { |
||||||
1667 | if (($behavior = $param->getBehavior()) instanceof IBehavior) { |
||||||
1668 | $behavior = clone $behavior; |
||||||
1669 | } |
||||||
1670 | return $this->attachBehavior($param->getName(), $behavior, $param->getPriority()); |
||||||
1671 | } |
||||||
1672 | } |
||||||
1673 | |||||||
1674 | /** |
||||||
1675 | * This is the method registered for all instanced objects should a class behavior be removed after |
||||||
1676 | 16 | * the class is instanced. Only when the class to which the behavior is being added is in this |
|||||
1677 | * object's class hierarchy, via {@see getClassHierarchy}, is the behavior removed from this instance. |
||||||
1678 | 16 | * @param mixed $sender the application |
|||||
1679 | 16 | * @param TClassBehaviorEventParameter $param |
|||||
1680 | 16 | * @since 3.2.3 |
|||||
1681 | 16 | */ |
|||||
1682 | 16 | public function fxDetachClassBehavior($sender, $param) |
|||||
1683 | { |
||||||
1684 | 1 | if ($this->isa($param->getClass())) { |
|||||
1685 | return $this->detachBehavior($param->getName(), $param->getPriority()); |
||||||
1686 | 2 | } |
|||||
1687 | } |
||||||
1688 | |||||||
1689 | /** |
||||||
1690 | * instanceBehavior is an internal method that takes a Behavior Object, a class name, or array of |
||||||
1691 | * ['class' => 'MyBehavior', 'property1' => 'Value1'...] and creates a Behavior in return. eg. |
||||||
1692 | * ```php |
||||||
1693 | * $b = $this->instanceBehavior('MyBehavior'); |
||||||
1694 | * $b = $this->instanceBehavior(['class' => 'MyBehavior', 'property1' => 'Value1']); |
||||||
1695 | * $b = $this->instanceBehavior(new MyBehavior); |
||||||
1696 | * ``` |
||||||
1697 | * If the behavior is an array, the key IBaseBehavior::CONFIG_KEY is stripped and used to initialize |
||||||
1698 | * the behavior. |
||||||
1699 | * |
||||||
1700 | * @param array|IBaseBehavior|string $behavior string, Behavior, or array of ['class' => 'MyBehavior', 'property1' => 'Value1' ...]. |
||||||
1701 | * @throws TInvalidDataTypeException if the behavior is not an {@see \Prado\Util\IBaseBehavior} |
||||||
1702 | * @return IBaseBehavior&TComponent an instance of $behavior or $behavior itself |
||||||
1703 | * @since 4.2.0 |
||||||
1704 | 16 | */ |
|||||
1705 | protected static function instanceBehavior($behavior) |
||||||
1706 | 16 | { |
|||||
1707 | 16 | $config = null; |
|||||
1708 | 16 | $isArray = false; |
|||||
1709 | 16 | $init = false; |
|||||
1710 | 16 | if (is_string($behavior) || (($isArray = is_array($behavior)) && isset($behavior['class']))) { |
|||||
1711 | if ($isArray && array_key_exists(IBaseBehavior::CONFIG_KEY, $behavior)) { |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1712 | 1 | $config = $behavior[IBaseBehavior::CONFIG_KEY]; |
|||||
1713 | unset($behavior[IBaseBehavior::CONFIG_KEY]); |
||||||
1714 | 2 | } |
|||||
1715 | $behavior = Prado::createComponent($behavior); |
||||||
1716 | $init = true; |
||||||
1717 | } |
||||||
1718 | if (!($behavior instanceof IBaseBehavior)) { |
||||||
1719 | throw new TInvalidDataTypeException('component_not_a_behavior', $behavior::class); |
||||||
1720 | } |
||||||
1721 | if ($init) { |
||||||
1722 | 5 | $behavior->init($config); |
|||||
1723 | } |
||||||
1724 | 5 | return $behavior; |
|||||
1725 | 5 | } |
|||||
1726 | 5 | ||||||
1727 | 5 | ||||||
1728 | 5 | /** |
|||||
1729 | * This will add a class behavior to all classes instanced (that are listening) and future newly instanced objects. |
||||||
1730 | * This registers the behavior for future instances and pushes the changes to all the instances that are listening as well. |
||||||
1731 | * The universal class behaviors are stored in an inverted stack with the latest class behavior being at the first position in the array. |
||||||
1732 | * This is done so class behaviors are added last first. |
||||||
1733 | * @param string $name name the key of the class behavior |
||||||
1734 | * @param object|string $behavior class behavior or name of the object behavior per instance |
||||||
1735 | * @param null|array|IBaseBehavior|string $class string of class or class on which to attach this behavior. Defaults to null which will error |
||||||
1736 | * but more important, if this is on PHP 5.3 it will use Late Static Binding to derive the class |
||||||
1737 | * it should extend. |
||||||
1738 | 5 | * ```php |
|||||
1739 | * TPanel::attachClassBehavior('javascripts', new TJsPanelClassBehavior()); |
||||||
1740 | 5 | * TApplication::attachClassBehavior('jpegize', \Prado\Util\Behaviors\TJPEGizeAssetBehavior::class, \Prado\Web\TFileAsset::class); |
|||||
1741 | 5 | * ``` |
|||||
1742 | * An array is used to initialize values of the behavior. eg. ['class' => '\\MyBehavior', 'property' => 'value']. |
||||||
1743 | 5 | * @param null|numeric $priority priority of behavior, default: null the default |
|||||
1744 | 5 | * priority of the {@see \Prado\Collections\TWeakCallableCollection} Optional. |
|||||
1745 | * @throws TInvalidOperationException if the class behavior is being added to a |
||||||
1746 | 5 | * {@see \Prado\TComponent}; due to recursion. |
|||||
1747 | 5 | * @throws TInvalidOperationException if the class behavior is already defined |
|||||
1748 | * @return array|object the behavior if its an IClassBehavior and an array of all |
||||||
1749 | 5 | * behaviors that have been attached from 'fxAttachClassBehavior' when the Class |
|||||
1750 | 5 | * Behavior being attached is a per instance IBaseBehavior. |
|||||
1751 | * @since 3.2.3 |
||||||
1752 | 5 | */ |
|||||
1753 | public static function attachClassBehavior($name, $behavior, $class = null, $priority = null) |
||||||
1754 | { |
||||||
1755 | if (!$class) { |
||||||
1756 | $class = get_called_class(); |
||||||
1757 | } |
||||||
1758 | if (!$class) { |
||||||
1759 | throw new TInvalidOperationException('component_no_class_provided_nor_late_binding'); |
||||||
1760 | } |
||||||
1761 | |||||||
1762 | $class = strtolower($class); |
||||||
1763 | if ($class === strtolower(TComponent::class)) { |
||||||
1764 | throw new TInvalidOperationException('component_no_tcomponent_class_behaviors'); |
||||||
1765 | } |
||||||
1766 | if (empty(self::$_um[$class])) { |
||||||
1767 | self::$_um[$class] = []; |
||||||
1768 | } |
||||||
1769 | $name = strtolower($name !== null ? $name : ''); |
||||||
0 ignored issues
–
show
|
|||||||
1770 | if (!empty($name) && !is_numeric($name) && isset(self::$_um[$class][$name])) { |
||||||
1771 | throw new TInvalidOperationException('component_class_behavior_defined', $class, $name); |
||||||
1772 | } |
||||||
1773 | $behaviorObject = self::instanceBehavior($behavior); |
||||||
1774 | $behaviorObject->setName($name); |
||||||
1775 | $isClassBehavior = $behaviorObject instanceof \Prado\Util\IClassBehavior; |
||||||
1776 | $param = new TClassBehaviorEventParameter($class, $name, $isClassBehavior ? $behaviorObject : $behavior, $priority); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1777 | if (empty($name) || is_numeric($name)) { |
||||||
1778 | self::$_um[$class][] = $param; |
||||||
1779 | } else { |
||||||
1780 | self::$_um[$class] = [$name => $param] + self::$_um[$class]; |
||||||
1781 | } |
||||||
1782 | $results = $behaviorObject->raiseEvent('fxAttachClassBehavior', null, $param); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1783 | return $isClassBehavior ? $behaviorObject : $results; |
||||||
1784 | } |
||||||
1785 | |||||||
1786 | /** |
||||||
1787 | * This will remove a behavior from a class. It unregisters it from future instances and |
||||||
1788 | * pulls the changes from all the instances that are listening as well. |
||||||
1789 | * PHP 5.3 uses Late Static Binding to derive the static class upon which this method is called. |
||||||
1790 | * @param string $name the key of the class behavior |
||||||
1791 | * @param string $class class on which to attach this behavior. Defaults to null. |
||||||
1792 | * @param null|false|numeric $priority priority: false is any priority, null is default |
||||||
1793 | * {@see \Prado\Collections\TWeakCallableCollection} priority, and numeric is a specific priority. |
||||||
1794 | * @throws TInvalidOperationException if the the class cannot be derived from Late Static Binding and is not |
||||||
1795 | * not supplied as a parameter. |
||||||
1796 | * @return null|array|object the behavior if its an IClassBehavior and an array of all behaviors |
||||||
1797 | * that have been detached from 'fxDetachClassBehavior' when the Class Behavior being |
||||||
1798 | * attached is a per instance IBehavior. Null if no behavior of $name to detach. |
||||||
1799 | * @since 3.2.3 |
||||||
1800 | */ |
||||||
1801 | public static function detachClassBehavior($name, $class = null, $priority = false) |
||||||
1802 | { |
||||||
1803 | if (!$class) { |
||||||
1804 | $class = get_called_class(); |
||||||
1805 | } |
||||||
1806 | if (!$class) { |
||||||
1807 | throw new TInvalidOperationException('component_no_class_provided_nor_late_binding'); |
||||||
1808 | } |
||||||
1809 | |||||||
1810 | $class = strtolower($class); |
||||||
1811 | $name = strtolower($name); |
||||||
1812 | if (empty(self::$_um[$class]) || !isset(self::$_um[$class][$name])) { |
||||||
1813 | return null; |
||||||
1814 | } |
||||||
1815 | $param = self::$_um[$class][$name]; |
||||||
1816 | $behavior = $param->getBehavior(); |
||||||
1817 | $behaviorObject = self::instanceBehavior($behavior); |
||||||
1818 | $behaviorObject->setName($name); |
||||||
1819 | $isClassBehavior = $behaviorObject instanceof IClassBehavior; |
||||||
1820 | unset(self::$_um[$class][$name]); |
||||||
1821 | if (empty(self::$_um[$class])) { |
||||||
1822 | unset(self::$_um[$class]); |
||||||
1823 | } |
||||||
1824 | $results = $behaviorObject->raiseEvent('fxDetachClassBehavior', null, $param); |
||||||
1825 | return $isClassBehavior ? $behaviorObject : $results; |
||||||
1826 | } |
||||||
1827 | |||||||
1828 | /** |
||||||
1829 | * Returns the named behavior object. If the $behaviorname is not found, but is |
||||||
1830 | * an existing class or interface, this will return the first instanceof. |
||||||
1831 | * The name 'asa' stands for 'as a'. |
||||||
1832 | * @param string $behaviorname the behavior name or the class name of the behavior. |
||||||
1833 | * @return object the behavior object of name or class, or null if the behavior does not exist |
||||||
1834 | * @since 3.2.3 |
||||||
1835 | */ |
||||||
1836 | public function asa($behaviorname) |
||||||
1837 | { |
||||||
1838 | $behaviorname = strtolower($behaviorname); |
||||||
1839 | if (isset($this->_m[$behaviorname])) { |
||||||
1840 | return $this->_m[$behaviorname]; |
||||||
1841 | } |
||||||
1842 | if ((class_exists($behaviorname, false) || interface_exists($behaviorname, false)) && $this->_m) { |
||||||
1843 | foreach ($this->_m->toArray() as $behavior) { |
||||||
1844 | if ($behavior instanceof $behaviorname) { |
||||||
1845 | return $behavior; |
||||||
1846 | } |
||||||
1847 | } |
||||||
1848 | } |
||||||
1849 | return null; |
||||||
1850 | } |
||||||
1851 | |||||||
1852 | /** |
||||||
1853 | * Returns whether or not the object or any of the behaviors are of a particular class. |
||||||
1854 | * The name 'isa' stands for 'is a'. This first checks if $this is an instanceof the class. |
||||||
1855 | * Then it checks if the $class is in the hierarchy, which includes first level traits. |
||||||
1856 | * It then checks each Behavior. If a behavior implements {@see \Prado\Util\IInstanceCheck}, |
||||||
1857 | * then the behavior can determine what it is an instanceof. If this behavior function returns true, |
||||||
1858 | * then this method returns true. If the behavior instance checking function returns false, |
||||||
1859 | * then no further checking is performed as it is assumed to be correct. |
||||||
1860 | * |
||||||
1861 | * If the behavior instance check function returns nothing or null or the behavior |
||||||
1862 | * doesn't implement the {@see \Prado\Util\IInstanceCheck} interface, then the default instanceof occurs. |
||||||
1863 | * The default isa behavior is to check if the behavior is an instanceof the class. |
||||||
1864 | * |
||||||
1865 | * The behavior {@see \Prado\Util\IInstanceCheck} is to allow a behavior to have the host object |
||||||
1866 | * act as a completely different object. |
||||||
1867 | * |
||||||
1868 | * @param mixed|string $class class or string |
||||||
1869 | * @return bool whether or not the object or a behavior is an instance of a particular class |
||||||
1870 | * @since 3.2.3 |
||||||
1871 | */ |
||||||
1872 | public function isa($class) |
||||||
1873 | { |
||||||
1874 | if ($this instanceof $class || in_array(strtolower(is_object($class) ? $class::class : $class), $this->getClassHierarchy(true))) { |
||||||
1875 | return true; |
||||||
1876 | } |
||||||
1877 | if ($this->_m !== null && $this->getBehaviorsEnabled()) { |
||||||
1878 | foreach ($this->_m->toArray() as $behavior) { |
||||||
1879 | if (!$behavior->getEnabled()) { |
||||||
1880 | continue; |
||||||
1881 | } |
||||||
1882 | |||||||
1883 | $check = null; |
||||||
1884 | if (($behavior->isa(\Prado\Util\IInstanceCheck::class)) && $check = $behavior->isinstanceof($class, $this)) { |
||||||
1885 | return true; |
||||||
1886 | } |
||||||
1887 | if ($check === null && ($behavior->isa($class))) { |
||||||
1888 | return true; |
||||||
1889 | } |
||||||
1890 | } |
||||||
1891 | } |
||||||
1892 | return false; |
||||||
1893 | } |
||||||
1894 | |||||||
1895 | /** |
||||||
1896 | * Returns all the behaviors attached to the TComponent. IBaseBehavior[s] may |
||||||
1897 | * be attached but not {@see \Prado\Util\IBaseBehavior::getEnabled Enabled}. |
||||||
1898 | * @param ?string $class Filters the result by class, default null for no filter. |
||||||
1899 | * @return array The behaviors [optionally filtered] attached to the TComponent. |
||||||
1900 | * @since 4.2.2 |
||||||
1901 | */ |
||||||
1902 | public function getBehaviors(?string $class = null) |
||||||
1903 | { |
||||||
1904 | if ($class === null) { |
||||||
1905 | return isset($this->_m) ? $this->_m->toArray() : []; |
||||||
1906 | } elseif (class_exists($class, false) || interface_exists($class, false)) { |
||||||
1907 | return array_filter($this->_m->toArray(), fn ($b) => $b instanceof $class); |
||||||
1908 | } |
||||||
1909 | return []; |
||||||
1910 | } |
||||||
1911 | |||||||
1912 | /** |
||||||
1913 | * Attaches a list of behaviors to the component. |
||||||
1914 | * Each behavior is indexed by its name and should be an instance of |
||||||
1915 | * {@see \Prado\Util\IBaseBehavior}, a string specifying the behavior class, or a |
||||||
1916 | * {@see \Prado\Util\TClassBehaviorEventParameter}. |
||||||
1917 | * @param array $behaviors list of behaviors to be attached to the component |
||||||
1918 | * @param bool $cloneIBehavior Should IBehavior be cloned before attaching. |
||||||
1919 | * Default is false. |
||||||
1920 | * @since 3.2.3 |
||||||
1921 | */ |
||||||
1922 | public function attachBehaviors($behaviors, bool $cloneIBehavior = false) |
||||||
1923 | { |
||||||
1924 | foreach ($behaviors as $name => $behavior) { |
||||||
1925 | if ($behavior instanceof TClassBehaviorEventParameter) { |
||||||
1926 | $paramBehavior = $behavior->getBehavior(); |
||||||
1927 | if ($cloneIBehavior && ($paramBehavior instanceof IBehavior)) { |
||||||
1928 | $paramBehavior = clone $paramBehavior; |
||||||
1929 | } |
||||||
1930 | $this->attachBehavior($behavior->getName(), $paramBehavior, $behavior->getPriority()); |
||||||
1931 | } else { |
||||||
1932 | if ($cloneIBehavior && ($behavior instanceof IBehavior)) { |
||||||
1933 | $behavior = clone $behavior; |
||||||
1934 | } |
||||||
1935 | $this->attachBehavior($name, $behavior); |
||||||
1936 | } |
||||||
1937 | } |
||||||
1938 | } |
||||||
1939 | |||||||
1940 | /** |
||||||
1941 | * Detaches select behaviors from the component. |
||||||
1942 | * Each behavior is indexed by its name and should be an instance of |
||||||
1943 | * {@see \Prado\Util\IBaseBehavior}, a string specifying the behavior class, or a |
||||||
1944 | * {@see \Prado\Util\TClassBehaviorEventParameter}. |
||||||
1945 | * @param array $behaviors list of behaviors to be detached from the component |
||||||
1946 | * @since 3.2.3 |
||||||
1947 | */ |
||||||
1948 | public function detachBehaviors($behaviors) |
||||||
1949 | { |
||||||
1950 | if ($this->_m !== null) { |
||||||
1951 | foreach ($behaviors as $name => $behavior) { |
||||||
1952 | if ($behavior instanceof TClassBehaviorEventParameter) { |
||||||
1953 | $this->detachBehavior($behavior->getName(), $behavior->getPriority()); |
||||||
1954 | } else { |
||||||
1955 | $this->detachBehavior(is_string($behavior) ? $behavior : $name); |
||||||
1956 | } |
||||||
1957 | } |
||||||
1958 | } |
||||||
1959 | } |
||||||
1960 | |||||||
1961 | /** |
||||||
1962 | * Detaches all behaviors from the component. |
||||||
1963 | * @since 3.2.3 |
||||||
1964 | */ |
||||||
1965 | public function clearBehaviors() |
||||||
1966 | { |
||||||
1967 | if ($this->_m !== null) { |
||||||
1968 | foreach ($this->_m->getKeys() as $name) { |
||||||
1969 | $this->detachBehavior($name); |
||||||
1970 | } |
||||||
1971 | $this->_m = null; |
||||||
1972 | } |
||||||
1973 | } |
||||||
1974 | |||||||
1975 | /** |
||||||
1976 | * Attaches a behavior to this component. |
||||||
1977 | * This method will create the behavior object based on the given |
||||||
1978 | * configuration. After that, the behavior object will be initialized |
||||||
1979 | * by calling its {@see \Prado\Util\IBaseBehavior::attach} method. |
||||||
1980 | * |
||||||
1981 | * Already attached behaviors may implement the function: |
||||||
1982 | * ```php |
||||||
1983 | * public function dyAttachBehavior($name,$behavior[, ?TCallChain $chain = null]) { |
||||||
1984 | * if ($chain) |
||||||
1985 | * return $chain->dyDetachBehavior($name, $behavior); |
||||||
1986 | * } |
||||||
1987 | * ``` |
||||||
1988 | * to be executed when attachBehavior is called. All attached behaviors are notified through |
||||||
1989 | * dyAttachBehavior. |
||||||
1990 | * |
||||||
1991 | * @param null|numeric|string $name the behavior's name. It should uniquely identify this behavior. |
||||||
1992 | * @param array|IBaseBehavior|string $behavior the behavior configuration. This is the name of the Behavior Class |
||||||
1993 | * instanced by {@see \Prado\PradoBase::createComponent}, or is a Behavior, or is an array of |
||||||
1994 | * ['class'=>'TBehavior' property1='value 1' property2='value2'...] with the class and properties |
||||||
1995 | * with values. |
||||||
1996 | * @param null|numeric $priority |
||||||
1997 | * @return IBaseBehavior the behavior object |
||||||
1998 | * @since 3.2.3 |
||||||
1999 | */ |
||||||
2000 | public function attachBehavior($name, $behavior, $priority = null) |
||||||
2001 | { |
||||||
2002 | $name = strtolower($name !== null ? $name : ''); |
||||||
2003 | if ($this->_m && isset($this->_m[$name])) { |
||||||
2004 | $this->detachBehavior($name); |
||||||
2005 | } |
||||||
2006 | $behavior = self::instanceBehavior($behavior); |
||||||
2007 | if ($this->_m === null) { |
||||||
2008 | $this->_m = new TPriorityMap(); |
||||||
2009 | } |
||||||
2010 | if (empty($name) || is_numeric($name)) { |
||||||
2011 | $name = $this->_m->getNextIntegerKey(); |
||||||
2012 | } |
||||||
2013 | $this->_m->add($name, $behavior, $priority); |
||||||
2014 | $behavior->setName($name); |
||||||
2015 | $behavior->attach($this); |
||||||
2016 | $this->callBehaviorsMethod('dyAttachBehavior', $return, $name, $behavior); |
||||||
2017 | return $behavior; |
||||||
2018 | } |
||||||
2019 | |||||||
2020 | /** |
||||||
2021 | * Detaches a behavior from the component. |
||||||
2022 | * The behavior's {@see \Prado\Util\IBaseBehavior::detach} method will be invoked. |
||||||
2023 | * |
||||||
2024 | * Behaviors may implement the function: |
||||||
2025 | * ```php |
||||||
2026 | * public function dyDetachBehavior($name, $behavior[, ?TCallChain $chain = null]) { |
||||||
2027 | * if ($chain) |
||||||
2028 | * return $chain->dyDetachBehavior($name, $behavior); |
||||||
2029 | * } |
||||||
2030 | * ``` |
||||||
2031 | * to be executed when detachBehavior is called. All attached behaviors are notified through |
||||||
2032 | * dyDetachBehavior. |
||||||
2033 | * |
||||||
2034 | * @param string $name the behavior's name. It uniquely identifies the behavior. |
||||||
2035 | * @param false|numeric $priority the behavior's priority. This defaults to false, which is any priority. |
||||||
2036 | * @return null|IBaseBehavior the detached behavior. Null if the behavior does not exist. |
||||||
2037 | * @since 3.2.3 |
||||||
2038 | */ |
||||||
2039 | public function detachBehavior($name, $priority = false) |
||||||
2040 | { |
||||||
2041 | $name = strtolower($name); |
||||||
2042 | if ($this->_m != null && ($behavior = $this->_m->itemAt($name, $priority))) { |
||||||
2043 | $this->callBehaviorsMethod('dyDetachBehavior', $return, $name, $behavior); |
||||||
2044 | $behavior->detach($this); |
||||||
2045 | $this->_m->remove($name, $priority); |
||||||
2046 | return $behavior; |
||||||
2047 | } |
||||||
2048 | return null; |
||||||
2049 | } |
||||||
2050 | |||||||
2051 | /** |
||||||
2052 | * Enables all behaviors attached to this component independent of the behaviors |
||||||
2053 | * |
||||||
2054 | * Behaviors may implement the function: |
||||||
2055 | * ```php |
||||||
2056 | * public function dyEnableBehaviors([?TCallChain $chain = null]) { |
||||||
2057 | * if ($chain) |
||||||
2058 | * return $chain->dyEnableBehaviors(); |
||||||
2059 | * } |
||||||
2060 | * ``` |
||||||
2061 | * to be executed when enableBehaviors is called. All attached behaviors are notified through |
||||||
2062 | * dyEnableBehaviors. |
||||||
2063 | * @since 3.2.3 |
||||||
2064 | */ |
||||||
2065 | public function enableBehaviors() |
||||||
2066 | { |
||||||
2067 | if (!$this->_behaviorsenabled) { |
||||||
2068 | $this->_behaviorsenabled = true; |
||||||
2069 | $this->callBehaviorsMethod('dyEnableBehaviors', $return); |
||||||
2070 | } |
||||||
2071 | } |
||||||
2072 | |||||||
2073 | /** |
||||||
2074 | * Disables all behaviors attached to this component independent of the behaviors |
||||||
2075 | * |
||||||
2076 | * Behaviors may implement the function: |
||||||
2077 | * ```php |
||||||
2078 | * public function dyDisableBehaviors([?TCallChain $chain = null]) { |
||||||
2079 | * if ($chain) |
||||||
2080 | * return $chain->dyDisableBehaviors(); |
||||||
2081 | * } |
||||||
2082 | * ``` |
||||||
2083 | * to be executed when disableBehaviors is called. All attached behaviors are notified through |
||||||
2084 | * dyDisableBehaviors. |
||||||
2085 | * @since 3.2.3 |
||||||
2086 | */ |
||||||
2087 | public function disableBehaviors() |
||||||
2088 | { |
||||||
2089 | if ($this->_behaviorsenabled) { |
||||||
2090 | $callchain = $this->getCallChain('dyDisableBehaviors'); |
||||||
2091 | $this->_behaviorsenabled = false; |
||||||
2092 | if ($callchain) { // normal dynamic events won't work because behaviors are disabled. |
||||||
2093 | $callchain->call(); |
||||||
2094 | } |
||||||
2095 | } |
||||||
2096 | } |
||||||
2097 | |||||||
2098 | |||||||
2099 | /** |
||||||
2100 | * Returns if all the behaviors are turned on or off for the object. |
||||||
2101 | * @return bool whether or not all behaviors are enabled (true) or not (false) |
||||||
2102 | * @since 3.2.3 |
||||||
2103 | */ |
||||||
2104 | public function getBehaviorsEnabled() |
||||||
2105 | { |
||||||
2106 | return $this->_behaviorsenabled; |
||||||
2107 | } |
||||||
2108 | |||||||
2109 | /** |
||||||
2110 | * Enables an attached object behavior. This cannot enable or disable whole class behaviors. |
||||||
2111 | * A behavior is only effective when it is enabled. |
||||||
2112 | * A behavior is enabled when first attached. |
||||||
2113 | * |
||||||
2114 | * Behaviors may implement the function: |
||||||
2115 | * ```php |
||||||
2116 | * public function dyEnableBehavior($name, $behavior[, ?TCallChain $chain = null]) { |
||||||
2117 | * if ($chain) |
||||||
2118 | * return $chain->dyEnableBehavior($name, $behavior); |
||||||
2119 | * } |
||||||
2120 | * ``` |
||||||
2121 | * to be executed when enableBehavior is called. All attached behaviors are notified through |
||||||
2122 | * dyEnableBehavior. |
||||||
2123 | * |
||||||
2124 | * @param string $name the behavior's name. It uniquely identifies the behavior. |
||||||
2125 | * @return bool Was the behavior found and enabled. |
||||||
2126 | * @since 3.2.3 |
||||||
2127 | */ |
||||||
2128 | public function enableBehavior($name): bool |
||||||
2129 | { |
||||||
2130 | $name = strtolower($name); |
||||||
2131 | if ($this->_m != null && isset($this->_m[$name])) { |
||||||
2132 | $behavior = $this->_m[$name]; |
||||||
2133 | if ($behavior->getEnabled() === false) { |
||||||
2134 | $behavior->setEnabled(true); |
||||||
2135 | $this->callBehaviorsMethod('dyEnableBehavior', $return, $name, $behavior); |
||||||
2136 | } |
||||||
2137 | return true; |
||||||
2138 | } |
||||||
2139 | return false; |
||||||
2140 | } |
||||||
2141 | |||||||
2142 | /** |
||||||
2143 | * Disables an attached behavior. This cannot enable or disable whole class behaviors. |
||||||
2144 | * A behavior is only effective when it is enabled. |
||||||
2145 | * |
||||||
2146 | * Behaviors may implement the function: |
||||||
2147 | * ```php |
||||||
2148 | * public function dyDisableBehavior($name, $behavior[, ?TCallChain $chain = null]) { |
||||||
2149 | * if ($chain) |
||||||
2150 | * return $chain->dyDisableBehavior($name, $behavior); |
||||||
2151 | * } |
||||||
2152 | * ``` |
||||||
2153 | * to be executed when disableBehavior is called. All attached behaviors are notified through |
||||||
2154 | * dyDisableBehavior. |
||||||
2155 | * |
||||||
2156 | * @param string $name the behavior's name. It uniquely identifies the behavior. |
||||||
2157 | * @return bool Was the behavior found and disabled. |
||||||
2158 | * @since 3.2.3 |
||||||
2159 | */ |
||||||
2160 | public function disableBehavior($name): bool |
||||||
2161 | { |
||||||
2162 | $name = strtolower($name); |
||||||
2163 | if ($this->_m != null && isset($this->_m[$name])) { |
||||||
2164 | $behavior = $this->_m[$name]; |
||||||
2165 | if ($behavior->getEnabled() === true) { |
||||||
2166 | $behavior->setEnabled(false); |
||||||
2167 | $this->callBehaviorsMethod('dyDisableBehavior', $return, $name, $behavior); |
||||||
2168 | } |
||||||
2169 | return true; |
||||||
2170 | } |
||||||
2171 | return false; |
||||||
2172 | } |
||||||
2173 | |||||||
2174 | /** |
||||||
2175 | * Returns an array with the names of all variables of that object that should be serialized. |
||||||
2176 | * Do not call this method. This is a PHP magic method that will be called automatically |
||||||
2177 | * prior to any serialization. |
||||||
2178 | */ |
||||||
2179 | public function __sleep() |
||||||
2180 | { |
||||||
2181 | $a = (array) $this; |
||||||
2182 | $a = array_keys($a); |
||||||
2183 | $exprops = []; |
||||||
2184 | $this->_getZappableSleepProps($exprops); |
||||||
2185 | return array_diff($a, $exprops); |
||||||
2186 | } |
||||||
2187 | |||||||
2188 | /** |
||||||
2189 | * Returns an array with the names of all variables of this object that should NOT be serialized |
||||||
2190 | * because their value is the default one or useless to be cached for the next page loads. |
||||||
2191 | * Reimplement in derived classes to add new variables, but remember to also to call the parent |
||||||
2192 | * implementation first. |
||||||
2193 | * @param array $exprops by reference |
||||||
2194 | */ |
||||||
2195 | protected function _getZappableSleepProps(&$exprops) |
||||||
2196 | { |
||||||
2197 | if ($this->_listeningenabled === false) { |
||||||
2198 | $exprops[] = "\0*\0_listeningenabled"; |
||||||
2199 | } |
||||||
2200 | if ($this->_behaviorsenabled === true) { |
||||||
2201 | $exprops[] = "\0*\0_behaviorsenabled"; |
||||||
2202 | } |
||||||
2203 | $exprops[] = "\0*\0_e"; |
||||||
2204 | if ($this->_m === null) { |
||||||
2205 | $exprops[] = "\0*\0_m"; |
||||||
2206 | } |
||||||
2207 | } |
||||||
2208 | } |
||||||
2209 |