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)) { |
|
|
|
|
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 |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
1355
|
|
|
return true; |
1356
|
|
|
} catch (\Exception $e) { |
|
|
|
|
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; |
|
|
|
|
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) { |
|
|
|
|
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)); |
|
|
|
|
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;"); |
|
|
|
|
1570
|
26 |
|
} catch (\Throwable $e) { |
|
|
|
|
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) { |
|
|
|
|
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)) { |
|
|
|
|
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 : ''); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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
|
|
|
|