1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* A base class for all objects/classes in Agile Toolkit. |
4
|
|
|
* Do not directly inherit from this class, instead use one of |
5
|
|
|
* AbstractModel, AbstractController or AbstractView. |
6
|
|
|
*/ |
7
|
|
|
abstract class AbstractObject |
8
|
|
|
{ |
9
|
|
|
const DOC = 'core-features/objects'; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Reference to the current model. Read only. Use setModel() |
13
|
|
|
* |
14
|
|
|
* @var Model |
15
|
|
|
*/ |
16
|
|
|
public $model; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Reference to the current controller. Read only. Use setController() |
20
|
|
|
* |
21
|
|
|
* @var Controller |
22
|
|
|
*/ |
23
|
|
|
public $controller; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Exception class to use when $this->exception() is called. When |
27
|
|
|
* you later call ->exception() method, you can either override |
28
|
|
|
* or postfix your exception with second argument. |
29
|
|
|
* |
30
|
|
|
* |
31
|
|
|
* $default_exception='PathFinder' by default would |
32
|
|
|
* return 'Exception_PathFinder' from exception(). |
33
|
|
|
* |
34
|
|
|
* $default_exceptoin='PathFinder' in combination with |
35
|
|
|
* ->exception('Blah','_NotFound') will return |
36
|
|
|
* 'Exception_PathFinder_NotFound' |
37
|
|
|
* |
38
|
|
|
* $default_exception='BaseException' in combination with |
39
|
|
|
* ->exception('Blah', 'PathFinder') will create |
40
|
|
|
* 'Exception_PathFinder' exception. |
41
|
|
|
* |
42
|
|
|
* and finally |
43
|
|
|
* |
44
|
|
|
* $default_exception='PathFinder' in combination with |
45
|
|
|
* ->exception('Blah','NotFound') will return |
46
|
|
|
* 'Exception_NotFound'; |
47
|
|
|
* |
48
|
|
|
* @todo implement test-cases for the above |
49
|
|
|
* |
50
|
|
|
* @var string |
51
|
|
|
*/ |
52
|
|
|
public $default_exception = 'BaseException'; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Default controller to initialize when calling setModel() |
56
|
|
|
* |
57
|
|
|
* @var string |
58
|
|
|
*/ |
59
|
|
|
public $default_controller = null; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Setting this to true will output additional debug info about object |
63
|
|
|
* |
64
|
|
|
* @var boolean |
65
|
|
|
*/ |
66
|
|
|
public $debug = null; |
67
|
|
|
|
68
|
|
|
// {{{ Object hierarchy management |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Unique object name |
72
|
|
|
* |
73
|
|
|
* @var string |
74
|
|
|
*/ |
75
|
|
|
public $name; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Name of the object in owner's element array |
79
|
|
|
* |
80
|
|
|
* @var string |
81
|
|
|
*/ |
82
|
|
|
public $short_name; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* short_name => object hash of children objects |
86
|
|
|
* |
87
|
|
|
* @var array |
88
|
|
|
*/ |
89
|
|
|
public $elements = array(); |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* Link to object into which we added this object |
93
|
|
|
* |
94
|
|
|
* @var AbstractObject |
95
|
|
|
*/ |
96
|
|
|
public $owner; |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Always points to current Application |
100
|
|
|
* |
101
|
|
|
* @var App_CLI |
102
|
|
|
*/ |
103
|
|
|
public $app; |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @deprecated 4.3.0 Left for compatibility with ATK 4.2 and lower, use ->app instead |
107
|
|
|
*/ |
108
|
|
|
public $api; |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* When this object is added, owner->elements[$this->short_name] |
112
|
|
|
* will be == $this;. |
113
|
|
|
* |
114
|
|
|
* @var boolean |
115
|
|
|
*/ |
116
|
|
|
public $auto_track_element = false; |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* To make sure you have called parent::init() properly. |
120
|
|
|
* |
121
|
|
|
* @var boolean |
122
|
|
|
*/ |
123
|
|
|
public $_initialized = false; |
124
|
|
|
|
125
|
|
|
|
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Initialize object. Always call parent::init(). Do not call directly. |
129
|
|
|
*/ |
130
|
|
|
public function init() |
131
|
|
|
{ |
132
|
|
|
/* |
133
|
|
|
* This method is called for initialization |
134
|
|
|
*/ |
135
|
|
|
$this->_initialized = true; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* This is default constructor of ATK4. Please do not re-define it |
140
|
|
|
* and avoid calling it directly. Always use add() and init() methods. |
141
|
|
|
* |
142
|
|
|
* @param array $options will initialize class properties |
143
|
|
|
*/ |
144
|
|
|
public function __construct($options = array()) |
145
|
|
|
{ |
146
|
|
|
foreach ($options as $key => $val) { |
147
|
|
|
if ($key !== 'name') { |
148
|
|
|
$this->$key = $val; |
149
|
|
|
} |
150
|
|
|
} |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/* \section Object Management Methods */ |
154
|
|
|
/** |
155
|
|
|
* Clones associated controller and model. If cloning views, add them to |
156
|
|
|
* the owner. |
157
|
|
|
* |
158
|
|
|
* @return AbstractObject Copy of this object |
159
|
|
|
*/ |
160
|
|
|
public function __clone() |
161
|
|
|
{ |
162
|
|
|
if ($this->model && is_object($this->model)) { |
163
|
|
|
$this->model = clone $this->model; |
164
|
|
|
} |
165
|
|
|
if ($this->controller && is_object($this->controller)) { |
166
|
|
|
$this->controller = clone $this->controller; |
167
|
|
|
} |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* Converts into string "Object View(myapp_page_view)". |
172
|
|
|
* |
173
|
|
|
* @return string |
174
|
|
|
*/ |
175
|
|
|
public function __toString() |
176
|
|
|
{ |
177
|
|
|
return 'Object '.get_class($this).'('.$this->name.')'; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* Removes object from parent and prevents it from rendering |
182
|
|
|
* \code |
183
|
|
|
* $view = $this->add('View'); |
184
|
|
|
* $view -> destroy(); |
185
|
|
|
* \endcode. |
186
|
|
|
*/ |
187
|
|
|
public function destroy($recursive = true) |
188
|
|
|
{ |
189
|
|
|
if ($recursive) { |
190
|
|
|
foreach ($this->elements as $el) { |
191
|
|
|
if ($el instanceof self) { |
192
|
|
|
$el->destroy(); |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
} |
196
|
|
|
/* |
197
|
|
|
if (@$this->model && $this->model instanceof AbstractObject) { |
198
|
|
|
$this->model->destroy(); |
199
|
|
|
unset($this->model); |
200
|
|
|
} |
201
|
|
|
if (@$this->controller && $this->controller instanceof AbstractObject) { |
202
|
|
|
$this->controller->destroy(); |
203
|
|
|
unset($this->controller); |
204
|
|
|
} |
205
|
|
|
*/ |
206
|
|
|
$this->owner->_removeElement($this->short_name); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* Remove child element if it exists. |
211
|
|
|
* |
212
|
|
|
* @param string $short_name short name of the element |
213
|
|
|
* |
214
|
|
|
* @return $this |
215
|
|
|
*/ |
216
|
|
|
public function removeElement($short_name) |
217
|
|
|
{ |
218
|
|
|
if (is_object($this->elements[$short_name])) { |
219
|
|
|
$this->elements[$short_name]->destroy(); |
220
|
|
|
} else { |
221
|
|
|
unset($this->elements[$short_name]); |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
return $this; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Actually removes the element. |
229
|
|
|
* |
230
|
|
|
* @param string $short_name short name |
231
|
|
|
* |
232
|
|
|
* @return $this |
233
|
|
|
* @access private |
234
|
|
|
*/ |
235
|
|
|
public function _removeElement($short_name) |
236
|
|
|
{ |
237
|
|
|
unset($this->elements[$short_name]); |
238
|
|
|
if ($this->_element_name_counts[$short_name] === 1) { |
239
|
|
|
unset($this->_element_name_counts[$short_name]); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
return $this; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Creates one more instance of $this object. |
247
|
|
|
* |
248
|
|
|
* @param array $properties Set initial properties for new object |
249
|
|
|
* |
250
|
|
|
* @return self |
251
|
|
|
*/ |
252
|
|
|
public function newInstance($properties = null) |
253
|
|
|
{ |
254
|
|
|
return $this->owner->add(get_class($this), $properties); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Creates new object and adds it as a child of current object. |
259
|
|
|
* Returns new object. |
260
|
|
|
* |
261
|
|
|
* @param array|string|object $class Name of the new class. Can also be array with 0=>name and |
262
|
|
|
* rest of array will be considered as $options or object. |
263
|
|
|
* @param array|string $options Short name or array of properties. |
264
|
|
|
* 0=>name will be used as a short-name or your object. |
265
|
|
|
* @param string $template_spot Tag where output will appear |
266
|
|
|
* @param array|string $template_branch Redefine template |
267
|
|
|
* |
268
|
|
|
* @link http://agiletoolkit.org/learn/understand/base/adding |
269
|
|
|
* |
270
|
|
|
* @return AbstractObject |
271
|
|
|
*/ |
272
|
|
|
public function add( |
273
|
|
|
$class, |
274
|
|
|
$options = null, |
275
|
|
|
$template_spot = null, |
276
|
|
|
$template_branch = null |
277
|
|
|
) { |
278
|
|
|
if (is_array($class)) { |
279
|
|
|
if (!$class[0]) { |
280
|
|
|
throw $this->exception('When passing class as array, use ["Class", "option"=>123] format') |
281
|
|
|
->addMoreInfo('class', var_export($class, true)); |
282
|
|
|
} |
283
|
|
|
$o = $class; |
284
|
|
|
$class = $o[0]; |
285
|
|
|
unset($o[0]); |
286
|
|
|
$options = $options ? array_merge($options, $o) : $o; |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
if (is_string($options)) { |
290
|
|
|
$options = array('name' => $options); |
291
|
|
|
} |
292
|
|
|
if (!is_array($options)) { |
293
|
|
|
$options = array(); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
if (is_object($class)) { |
297
|
|
|
// Object specified, just add the object, do not create anything |
298
|
|
|
if (!($class instanceof self)) { |
299
|
|
|
throw $this->exception( |
300
|
|
|
'You may only add objects based on AbstractObject' |
301
|
|
|
); |
302
|
|
|
} |
303
|
|
|
if (!$class->short_name) { |
304
|
|
|
$class->short_name = str_replace('\\', '_', strtolower(get_class($class))); |
305
|
|
|
} |
306
|
|
|
if (!$class->app) { |
307
|
|
|
$class->app = $this->app; |
308
|
|
|
$class->api = $this->app; // compatibility with ATK 4.2 and lower |
|
|
|
|
309
|
|
|
} |
310
|
|
|
$class->short_name = $this->_unique_element($class->short_name); |
311
|
|
|
$class->name = $this->_shorten($this->name.'_'.$class->short_name); |
312
|
|
|
|
313
|
|
|
$this->elements[$class->short_name] = $class; |
314
|
|
|
if ($class instanceof AbstractView) { |
315
|
|
|
/** @type AbstractView $class */ |
316
|
|
|
$class->owner->elements[$class->short_name] = true; |
317
|
|
|
} |
318
|
|
|
$class->owner = $this; |
319
|
|
|
if ($class instanceof AbstractView) { |
320
|
|
|
/** @type AbstractView $class */ |
321
|
|
|
// Scrutinizer complains that $this->template is not defined and it really is not :) |
322
|
|
|
if (!isset($this->template) || !$this->template) { |
|
|
|
|
323
|
|
|
$class->initializeTemplate($template_spot, $template_branch); |
324
|
|
|
} |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
return $class; |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
if (!is_string($class) || !$class) { |
331
|
|
|
throw $this->exception('Class is not valid') |
332
|
|
|
->addMoreInfo('class', $class); |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
$class = str_replace('/', '\\', $class); |
336
|
|
|
|
337
|
|
|
if ($class[0] == '.') { |
338
|
|
|
// Relative class name specified, extract current namespace |
339
|
|
|
// and make new class name relative to this namespace |
340
|
|
|
$ns = get_class($this); |
341
|
|
|
$ns = substr($ns, 0, strrpos($ns, '\\')); |
342
|
|
|
$class = $ns.'\\'.substr($class, 2); |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
$short_name = isset($options['name']) |
346
|
|
|
? $options['name'] |
347
|
|
|
: str_replace('\\', '_', strtolower($class)); |
348
|
|
|
|
349
|
|
|
// Adding same controller twice will return existing one |
350
|
|
|
if (isset($this->elements[$short_name])) { |
351
|
|
|
if ($this->elements[$short_name] instanceof AbstractController) { |
352
|
|
|
return $this->elements[$short_name]; |
353
|
|
|
} |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
$short_name = $this->_unique_element($short_name); |
357
|
|
|
|
358
|
|
|
if (isset($this->elements[$short_name])) { |
359
|
|
|
throw $this->exception($class.' with requested name already exists') |
360
|
|
|
->addMoreInfo('class', $class) |
361
|
|
|
->addMoreInfo('new_short_name', $short_name) |
362
|
|
|
->addMoreInfo('object', $this) |
363
|
|
|
->addMoreInfo('counts', json_encode($this->_element_name_counts)) |
364
|
|
|
->addThis($this); |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
$class_name_nodash = str_replace('-', '', $class); |
368
|
|
|
/* |
369
|
|
|
* Even though this might break some applications, |
370
|
|
|
* your loading must be configured properly instead |
371
|
|
|
* of relying on this |
372
|
|
|
* |
373
|
|
|
if (!class_exists($class_name_nodash, false) |
374
|
|
|
&& isset($this->app->pathfinder) |
375
|
|
|
) { |
376
|
|
|
$this->app->pathfinder->loadClass($class); |
377
|
|
|
}*/ |
378
|
|
|
$element = new $class_name_nodash($options); |
379
|
|
|
|
380
|
|
|
if (!($element instanceof self)) { |
381
|
|
|
throw $this->exception( |
382
|
|
|
'You can add only classes based on AbstractObject' |
383
|
|
|
); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
$element->owner = $this; |
387
|
|
|
$element->app = $this->app; |
388
|
|
|
$element->api = $this->app; // compatibility with ATK 4.2 and lower |
|
|
|
|
389
|
|
|
$element->name = $this->_shorten($this->name.'_'.$short_name); |
390
|
|
|
$element->short_name = $short_name; |
391
|
|
|
|
392
|
|
|
if (!$element->auto_track_element) { |
393
|
|
|
// dont store extra reference to models and controlers |
394
|
|
|
// for purposes of better garbage collection |
395
|
|
|
$this->elements[$short_name] = true; |
396
|
|
|
} else { |
397
|
|
|
$this->elements[$short_name] = $element; |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
// Initialize template before init() starts |
401
|
|
|
if ($element instanceof AbstractView) { |
402
|
|
|
$element->initializeTemplate($template_spot, $template_branch); |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
// Avoid using this hook. Agile Toolkit creates LOTS of objects, |
406
|
|
|
// so you'll get significantly slower code if you try to use this |
407
|
|
|
$this->app->hook('beforeObjectInit', array(&$element)); |
408
|
|
|
|
409
|
|
|
// Initialize element |
410
|
|
|
$element->init(); |
411
|
|
|
|
412
|
|
|
// Make sure init()'s parent was called. It's a popular coder's mistake. |
413
|
|
|
if (!$element->_initialized) { |
414
|
|
|
throw $element->exception( |
415
|
|
|
'You should call parent::init() when you override it' |
416
|
|
|
) |
417
|
|
|
->addMoreInfo('object_name', $element->name) |
418
|
|
|
->addMoreInfo('class', get_class($element)); |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
// Great hook to affect children recursively |
422
|
|
|
$this->hook('afterAdd', array($element)); |
423
|
|
|
|
424
|
|
|
return $element; |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
/** |
428
|
|
|
* Find child element by its short name. Use in chaining. |
429
|
|
|
* Exception if not found. |
430
|
|
|
* |
431
|
|
|
* @param string $short_name Short name of the child element |
432
|
|
|
* |
433
|
|
|
* @return AbstractObject |
434
|
|
|
*/ |
435
|
|
|
public function getElement($short_name) |
436
|
|
|
{ |
437
|
|
|
if (!isset($this->elements[$short_name])) { |
438
|
|
|
throw $this->exception('Child element not found') |
439
|
|
|
->addMoreInfo('element', $short_name); |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
return $this->elements[$short_name]; |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
/** |
446
|
|
|
* Find child element. Use in condition. |
447
|
|
|
* |
448
|
|
|
* @param string $short_name Short name of the child element |
449
|
|
|
* |
450
|
|
|
* @return AbstractObject|bool |
451
|
|
|
*/ |
452
|
|
|
public function hasElement($short_name) |
453
|
|
|
{ |
454
|
|
|
return isset($this->elements[$short_name]) |
455
|
|
|
? $this->elements[$short_name] |
456
|
|
|
: false; |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* Names object accordingly. May not work on some objects. |
461
|
|
|
* |
462
|
|
|
* @param string $short_name Short name of the child element |
463
|
|
|
* |
464
|
|
|
* @return $this |
465
|
|
|
*/ |
466
|
|
|
public function rename($short_name) |
467
|
|
|
{ |
468
|
|
|
unset($this->owner->elements[$this->short_name]); |
469
|
|
|
$this->name = $this->name.'_'.$short_name; |
470
|
|
|
$this->short_name = $short_name; |
471
|
|
|
|
472
|
|
|
if (!$this->auto_track_element) { |
473
|
|
|
$this->owner->elements[$short_name] = true; |
474
|
|
|
} else { |
475
|
|
|
$this->owner->elements[$short_name] = $this; |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
return $this; |
479
|
|
|
} |
480
|
|
|
// }}} |
481
|
|
|
|
482
|
|
|
// {{{ Model and Controller handling |
483
|
|
|
/** |
484
|
|
|
* Associate controller with the object. |
485
|
|
|
* |
486
|
|
|
* @param string|object $controller Class or instance of controller |
487
|
|
|
* @param string|array $name Name or property for new controller |
488
|
|
|
* |
489
|
|
|
* @return AbstractController Newly added controller |
490
|
|
|
*/ |
491
|
|
|
public function setController($controller, $name = null) |
492
|
|
|
{ |
493
|
|
|
$controller = $this->app->normalizeClassName($controller, 'Controller'); |
494
|
|
|
|
495
|
|
|
return $this->add($controller, $name); |
496
|
|
|
} |
497
|
|
|
|
498
|
|
|
/** |
499
|
|
|
* Associate model with object. |
500
|
|
|
* |
501
|
|
|
* @param string|object $model Class or instance of model |
502
|
|
|
* |
503
|
|
|
* @return AbstractModel Newly added Model |
504
|
|
|
*/ |
505
|
|
|
public function setModel($model) |
506
|
|
|
{ |
507
|
|
|
$model = $this->app->normalizeClassName($model, 'Model'); |
508
|
|
|
$this->model = $this->add($model); |
509
|
|
|
|
510
|
|
|
return $this->model; |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
/** |
514
|
|
|
* Return current model. |
515
|
|
|
* |
516
|
|
|
* @return AbstractModel Currently associated model object |
517
|
|
|
*/ |
518
|
|
|
public function getModel() |
519
|
|
|
{ |
520
|
|
|
return $this->model; |
521
|
|
|
} |
522
|
|
|
// }}} |
523
|
|
|
|
524
|
|
|
// {{{ Session management: http://agiletoolkit.org/doc/session |
525
|
|
|
/** |
526
|
|
|
* Remember data in object-relevant session data. |
527
|
|
|
* |
528
|
|
|
* @param string $key Key for the data |
529
|
|
|
* @param mixed $value Value |
530
|
|
|
* |
531
|
|
|
* @return mixed $value |
532
|
|
|
*/ |
533
|
|
|
public function memorize($key, $value) |
534
|
|
|
{ |
535
|
|
|
/** @type App_Web $this->app */ |
536
|
|
|
|
537
|
|
|
if (!session_id()) { |
538
|
|
|
$this->app->initializeSession(); |
539
|
|
|
} |
540
|
|
|
|
541
|
|
|
if ($value instanceof Model) { |
542
|
|
|
unset($_SESSION['o'][$this->name][$key]); |
543
|
|
|
$_SESSION['s'][$this->name][$key] = serialize($value); |
544
|
|
|
|
545
|
|
|
return $value; |
546
|
|
|
} |
547
|
|
|
|
548
|
|
|
unset($_SESSION['s'][$this->name][$key]); |
549
|
|
|
$_SESSION['o'][$this->name][$key] = $value; |
550
|
|
|
|
551
|
|
|
return $value; |
552
|
|
|
} |
553
|
|
|
|
554
|
|
|
/** |
555
|
|
|
* Similar to memorize, but if value for key exist, will return it. |
556
|
|
|
* |
557
|
|
|
* @param string $key Data Key |
558
|
|
|
* @param mixed $default Default value |
559
|
|
|
* |
560
|
|
|
* @return mixed Previously memorized data or $default |
561
|
|
|
*/ |
562
|
|
|
public function learn($key, $default = null) |
563
|
|
|
{ |
564
|
|
|
/** @type App_Web $this->app */ |
565
|
|
|
|
566
|
|
|
if (!session_id()) { |
567
|
|
|
$this->app->initializeSession(false); |
568
|
|
|
} |
569
|
|
|
|
570
|
|
|
if (!isset($_SESSION['o'][$this->name][$key]) |
571
|
|
|
|| is_null($_SESSION['o'][$this->name][$key]) |
572
|
|
|
) { |
573
|
|
|
if (is_callable($default)) { |
574
|
|
|
$default = call_user_func($default); |
575
|
|
|
} |
576
|
|
|
|
577
|
|
|
return $this->memorize($key, $default); |
578
|
|
|
} else { |
579
|
|
|
return $this->recall($key); |
580
|
|
|
} |
581
|
|
|
} |
582
|
|
|
|
583
|
|
|
/** |
584
|
|
|
* Forget session data for arg $key. If $key is omitted will forget all |
585
|
|
|
* associated session data. |
586
|
|
|
* |
587
|
|
|
* @param string $key Optional key of data to forget |
588
|
|
|
* |
589
|
|
|
* @return $this |
590
|
|
|
*/ |
591
|
|
|
public function forget($key = null) |
592
|
|
|
{ |
593
|
|
|
/** @type App_Web $this->app */ |
594
|
|
|
|
595
|
|
|
if (!session_id()) { |
596
|
|
|
$this->app->initializeSession(false); |
597
|
|
|
} |
598
|
|
|
|
599
|
|
|
// Prevent notice generation when using custom session handler |
600
|
|
|
if (!isset($_SESSION)) { |
601
|
|
|
return $this; |
602
|
|
|
} |
603
|
|
|
|
604
|
|
|
if (is_null($key)) { |
605
|
|
|
unset($_SESSION['o'][$this->name]); |
606
|
|
|
unset($_SESSION['s'][$this->name]); |
607
|
|
|
} else { |
608
|
|
|
unset($_SESSION['o'][$this->name][$key]); |
609
|
|
|
unset($_SESSION['s'][$this->name][$key]); |
610
|
|
|
} |
611
|
|
|
|
612
|
|
|
return $this; |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
/** |
616
|
|
|
* Returns session data for this object. If not previously set, then |
617
|
|
|
* $default is returned. |
618
|
|
|
* |
619
|
|
|
* @param string $key Data Key |
620
|
|
|
* @param mixed $default Default value |
621
|
|
|
* |
622
|
|
|
* @return mixed Previously memorized data or $default |
623
|
|
|
*/ |
624
|
|
|
public function recall($key, $default = null) |
625
|
|
|
{ |
626
|
|
|
/** @type App_Web $this->app */ |
627
|
|
|
|
628
|
|
|
if (!session_id()) { |
629
|
|
|
$this->app->initializeSession(false); |
630
|
|
|
} |
631
|
|
|
|
632
|
|
|
if (!isset($_SESSION['o'][$this->name][$key]) |
633
|
|
|
|| is_null($_SESSION['o'][$this->name][$key]) |
634
|
|
|
) { |
635
|
|
|
if (!isset($_SESSION['s'][$this->name][$key])) { |
636
|
|
|
return $default; |
637
|
|
|
} |
638
|
|
|
$v = $this->add(unserialize($_SESSION['s'][$this->name][$key])); |
639
|
|
|
$v->init(); |
640
|
|
|
|
641
|
|
|
return $v; |
642
|
|
|
} |
643
|
|
|
|
644
|
|
|
return $_SESSION['o'][$this->name][$key]; |
645
|
|
|
} |
646
|
|
|
// }}} |
647
|
|
|
|
648
|
|
|
// {{{ Exception handling: http://agiletoolkit.org/doc/exception |
649
|
|
|
/** |
650
|
|
|
* Returns relevant exception class. Use this method with "throw". |
651
|
|
|
* |
652
|
|
|
* @param string $message Static text of exception. |
653
|
|
|
* @param string $type Exception class or class postfix |
654
|
|
|
* @param string $code Optional error code |
655
|
|
|
* |
656
|
|
|
* @return BaseException |
657
|
|
|
*/ |
658
|
|
|
public function exception($message = 'Undefined Exception', $type = null, $code = null) |
659
|
|
|
{ |
660
|
|
|
if ($type === null) { |
661
|
|
|
$type = $this->default_exception; |
662
|
|
|
} elseif ($type[0] == '_') { |
663
|
|
|
if ($this->default_exception == 'BaseException') { |
664
|
|
|
$type = 'Exception_'.substr($type, 1); |
665
|
|
|
} else { |
666
|
|
|
$type = $this->default_exception.'_'.substr($type, 1); |
667
|
|
|
} |
668
|
|
|
} elseif ($type != 'BaseException') { |
669
|
|
|
$type = $this->app->normalizeClassName($type, 'Exception'); |
670
|
|
|
} |
671
|
|
|
|
672
|
|
|
// Localization support |
673
|
|
|
$message = $this->app->_($message); |
674
|
|
|
|
675
|
|
|
if ($type == 'Exception') { |
676
|
|
|
$type = 'BaseException'; |
677
|
|
|
} |
678
|
|
|
|
679
|
|
|
$e = new $type($message, $code); |
680
|
|
|
if (!($e instanceof BaseException)) { |
681
|
|
|
throw $e; |
682
|
|
|
} |
683
|
|
|
$e->owner = $this; |
684
|
|
|
$e->app = $this->app; |
685
|
|
|
$e->api = $this->app; // compatibility with ATK 4.2 and lower |
|
|
|
|
686
|
|
|
$e->init(); |
687
|
|
|
|
688
|
|
|
return $e; |
689
|
|
|
} |
690
|
|
|
// }}} |
691
|
|
|
|
692
|
|
|
// {{{ Code which can be potentially obsoleted. |
693
|
|
|
/** |
694
|
|
|
* Reports fatal error. Use ->exception instead. |
695
|
|
|
* |
696
|
|
|
* @param string $error error text |
697
|
|
|
* @param int $shift relative offset in backtrace |
698
|
|
|
* |
699
|
|
|
* @obsolete |
700
|
|
|
*/ |
701
|
|
|
public function fatal($error, $shift = 0) |
702
|
|
|
{ |
703
|
|
|
return $this->upCall( |
704
|
|
|
'outputFatal', |
705
|
|
|
array( |
706
|
|
|
$error, |
707
|
|
|
$shift, |
708
|
|
|
) |
709
|
|
|
); |
710
|
|
|
} |
711
|
|
|
|
712
|
|
|
/** |
713
|
|
|
* Records debug information. |
714
|
|
|
* |
715
|
|
|
* @param string $msg information |
716
|
|
|
* |
717
|
|
|
* @obsolete |
718
|
|
|
*/ |
719
|
|
|
public $_info = array(); |
720
|
|
|
public function info($msg) |
721
|
|
|
{ |
722
|
|
|
/* |
723
|
|
|
* Call this function to send some information to Application. Example: |
724
|
|
|
* |
725
|
|
|
* $this->info("User tried buying traffic without enough money in bank"); |
726
|
|
|
*/ |
727
|
|
|
$args = func_get_args(); |
728
|
|
|
array_shift($args); |
729
|
|
|
$this->_info[] = vsprintf($msg, $args); |
730
|
|
|
} |
731
|
|
|
|
732
|
|
|
/** |
733
|
|
|
* Turns on debug mode for this object. |
734
|
|
|
* Using first argument as string is obsolete. |
735
|
|
|
* |
736
|
|
|
* @param bool|string $msg "true" to start debugging |
737
|
|
|
* @param string $file obsolete |
738
|
|
|
* @param string $line obsolete |
739
|
|
|
*/ |
740
|
|
|
public function debug($msg = true, $file = null, $line = null) |
741
|
|
|
{ |
742
|
|
|
if (is_bool($msg)) { |
743
|
|
|
$this->debug = $msg; |
744
|
|
|
|
745
|
|
|
return $this; |
746
|
|
|
} |
747
|
|
|
|
748
|
|
|
if (is_object($msg)) { |
749
|
|
|
throw $this->exception('Do not debug objects'); |
750
|
|
|
} |
751
|
|
|
|
752
|
|
|
// The rest of this method is obsolete |
753
|
|
View Code Duplication |
if ((isset($this->debug) && $this->debug) |
754
|
|
|
|| (isset($this->app->debug) && $this->app->debug) |
755
|
|
|
) { |
756
|
|
|
$this->app->outputDebug($this, $msg, $file/*, $line*/); |
|
|
|
|
757
|
|
|
} |
758
|
|
|
} |
759
|
|
|
|
760
|
|
|
/** |
761
|
|
|
* Records warning. |
762
|
|
|
* |
763
|
|
|
* @param string $msg information |
764
|
|
|
* @param int $shift relative offset in backtrace |
765
|
|
|
* |
766
|
|
|
* @obsolete |
767
|
|
|
*/ |
768
|
|
|
public function warning($msg, $shift = 0) |
769
|
|
|
{ |
770
|
|
|
$this->upCall( |
771
|
|
|
'outputWarning', |
772
|
|
|
array( |
773
|
|
|
$msg, |
774
|
|
|
$shift, |
775
|
|
|
) |
776
|
|
|
); |
777
|
|
|
} |
778
|
|
|
|
779
|
|
|
/** |
780
|
|
|
* Call specified method for this class and all parents up to app. |
781
|
|
|
* |
782
|
|
|
* @param string $type information |
783
|
|
|
* @param array $args relative offset in backtrace |
784
|
|
|
* |
785
|
|
|
* @obsolete |
786
|
|
|
*/ |
787
|
|
|
public function upCall($type, $args = array()) |
788
|
|
|
{ |
789
|
|
|
/* |
790
|
|
|
* Try to handle something on our own and in case we are not able, |
791
|
|
|
* pass to parent. Such as messages, notifications and request for |
792
|
|
|
* additional info or descriptions are passed this way. |
793
|
|
|
*/ |
794
|
|
|
if (method_exists($this, $type)) { |
795
|
|
|
return call_user_func_array( |
796
|
|
|
array( |
797
|
|
|
$this, |
798
|
|
|
$type, |
799
|
|
|
), |
800
|
|
|
$args |
801
|
|
|
); |
802
|
|
|
} |
803
|
|
|
if (!$this->owner) { |
804
|
|
|
return false; |
805
|
|
|
} |
806
|
|
|
|
807
|
|
|
return $this->owner->upCall($type, $args); |
808
|
|
|
} |
809
|
|
|
// }}} |
810
|
|
|
|
811
|
|
|
// {{{ Hooks: http://agiletoolkit.org/doc/hooks |
812
|
|
|
public $hooks = array(); |
813
|
|
|
|
814
|
|
|
/** |
815
|
|
|
* If priority is negative, then hooks will be executed in reverse order. |
816
|
|
|
* |
817
|
|
|
* @param string $hook_spot Hook identifier to bind on |
818
|
|
|
* @param AbstractObject|callable $callable Will be called on hook() |
819
|
|
|
* @param array $arguments Arguments are passed to $callable |
820
|
|
|
* @param int $priority Lower priority is called sooner |
821
|
|
|
* |
822
|
|
|
* @return $this |
823
|
|
|
*/ |
824
|
|
|
public function addHook($hook_spot, $callable, $arguments = array(), $priority = 5) |
825
|
|
|
{ |
826
|
|
|
if (!is_array($arguments)) { |
827
|
|
|
throw $this->exception('Incorrect arguments'); |
828
|
|
|
} |
829
|
|
|
if (is_string($hook_spot) && strpos($hook_spot, ',') !== false) { |
830
|
|
|
$hook_spot = explode(',', $hook_spot); |
831
|
|
|
} |
832
|
|
|
if (is_array($hook_spot)) { |
833
|
|
|
foreach ($hook_spot as $h) { |
834
|
|
|
$this->addHook($h, $callable, $arguments, $priority); |
835
|
|
|
} |
836
|
|
|
|
837
|
|
|
return $this; |
838
|
|
|
} |
839
|
|
|
if (!is_callable($callable) |
840
|
|
|
&& ($callable instanceof self |
841
|
|
|
&& !$callable->hasMethod($hook_spot)) |
842
|
|
|
) { |
843
|
|
|
throw $this->exception('Hook does not exist'); |
844
|
|
|
} |
845
|
|
View Code Duplication |
if (is_object($callable) && !is_callable($callable)) { |
846
|
|
|
$callable = array($callable, $hook_spot); |
847
|
|
|
// short for addHook('test', $this); to call $this->test(); |
848
|
|
|
} |
849
|
|
|
|
850
|
|
|
$this->hooks[$hook_spot][$priority][] = array($callable, $arguments); |
851
|
|
|
|
852
|
|
|
return $this; |
853
|
|
|
} |
854
|
|
|
|
855
|
|
|
/** |
856
|
|
|
* Delete all hooks for specified spot. |
857
|
|
|
* |
858
|
|
|
* @param string $hook_spot Hook identifier to bind on |
859
|
|
|
* |
860
|
|
|
* @return $this |
861
|
|
|
*/ |
862
|
|
|
public function removeHook($hook_spot) |
863
|
|
|
{ |
864
|
|
|
unset($this->hooks[$hook_spot]); |
865
|
|
|
|
866
|
|
|
return $this; |
867
|
|
|
} |
868
|
|
|
|
869
|
|
|
/** |
870
|
|
|
* Execute all callables assigned to $hook_spot. |
871
|
|
|
* |
872
|
|
|
* @param string $hook_spot Hook identifier |
873
|
|
|
* @param array $arg Additional arguments to callables |
874
|
|
|
* |
875
|
|
|
* @return mixed Array of responses or value specified to breakHook |
876
|
|
|
*/ |
877
|
|
|
public function hook($hook_spot, $arg = array()) |
878
|
|
|
{ |
879
|
|
|
if (!is_array($arg)) { |
880
|
|
|
throw $this->exception( |
881
|
|
|
'Incorrect arguments, or hook does not exist' |
882
|
|
|
); |
883
|
|
|
} |
884
|
|
|
$return = array(); |
885
|
|
|
if ($arg === UNDEFINED) { |
886
|
|
|
$arg = array(); |
887
|
|
|
} |
888
|
|
|
|
889
|
|
|
if (isset($this->hooks[$hook_spot])) { |
890
|
|
|
if (is_array($this->hooks[$hook_spot])) { |
891
|
|
|
krsort($this->hooks[$hook_spot]); // lower priority is called sooner |
892
|
|
|
$hook_backup = $this->hooks[$hook_spot]; |
893
|
|
|
|
894
|
|
|
try { |
895
|
|
|
while ($_data = array_pop($this->hooks[$hook_spot])) { |
896
|
|
|
foreach ($_data as $prio => &$data) { |
897
|
|
|
|
898
|
|
|
// Our extension |
899
|
|
|
if (is_string($data[0]) |
900
|
|
|
&& !preg_match( |
901
|
|
|
'/^[a-zA-Z_][a-zA-Z0-9_]*$/', |
902
|
|
|
$data[0] |
903
|
|
|
) |
904
|
|
|
) { |
905
|
|
|
$result = eval($data[0]); |
906
|
|
|
} elseif (is_callable($data[0])) { |
907
|
|
|
$result = call_user_func_array( |
908
|
|
|
$data[0], |
909
|
|
|
array_merge( |
910
|
|
|
array($this), |
911
|
|
|
$arg, |
912
|
|
|
$data[1] |
913
|
|
|
) |
914
|
|
|
); |
915
|
|
|
} else { |
916
|
|
|
if (!is_array($data[0])) { |
917
|
|
|
$data[0] = array( |
918
|
|
|
'STATIC', |
919
|
|
|
$data[0], |
920
|
|
|
); |
921
|
|
|
} |
922
|
|
|
throw $this->exception( |
923
|
|
|
'Cannot call hook. Function might not exist.' |
924
|
|
|
) |
925
|
|
|
->addMoreInfo('hook', $hook_spot) |
926
|
|
|
->addMoreInfo('arg1', $data[0][0]) |
927
|
|
|
->addMoreInfo('arg2', $data[0][1]); |
928
|
|
|
} |
929
|
|
|
$return[] = $result; |
930
|
|
|
} |
931
|
|
|
} |
932
|
|
|
|
933
|
|
|
} catch (Exception_Hook $e) { |
934
|
|
|
/** @type Exception_Hook $e */ |
935
|
|
|
$this->hooks[$hook_spot] = $hook_backup; |
936
|
|
|
|
937
|
|
|
return $e->return_value; |
938
|
|
|
} |
939
|
|
|
|
940
|
|
|
$this->hooks[$hook_spot] = $hook_backup; |
941
|
|
|
} |
942
|
|
|
} |
943
|
|
|
|
944
|
|
|
return $return; |
945
|
|
|
} |
946
|
|
|
|
947
|
|
|
/** |
948
|
|
|
* When called from inside a hook callable, will stop execution of other |
949
|
|
|
* callables on same hook. The passed argument will be returned by the |
950
|
|
|
* hook method. |
951
|
|
|
* |
952
|
|
|
* @param mixed $return What would hook() return? |
953
|
|
|
*/ |
954
|
|
|
public function breakHook($return) |
955
|
|
|
{ |
956
|
|
|
/** @type Exception_Hook $e */ |
957
|
|
|
$e = $this->exception(null, 'Hook'); |
958
|
|
|
$e->return_value = $return; |
959
|
|
|
throw $e; |
960
|
|
|
} |
961
|
|
|
// }}} |
962
|
|
|
|
963
|
|
|
// {{{ Dynamic Methods: http://agiletoolkit.org/learn/dynamic |
964
|
|
|
/** |
965
|
|
|
* Call method is used to display exception for non-existant methods and |
966
|
|
|
* provides ability to extend objects with addMethod(). |
967
|
|
|
* |
968
|
|
|
* @param string $method Name of the method |
969
|
|
|
* @param array $arguments Arguments |
970
|
|
|
* |
971
|
|
|
* @return mixed |
972
|
|
|
*/ |
973
|
|
|
public function __call($method, $arguments) |
974
|
|
|
{ |
975
|
|
|
if (($ret = $this->tryCall($method, $arguments))) { |
976
|
|
|
return $ret[0]; |
977
|
|
|
} |
978
|
|
|
throw $this->exception( |
979
|
|
|
'Method is not defined for this object', |
980
|
|
|
'Logic' |
981
|
|
|
) |
982
|
|
|
->addMoreInfo('class', get_class($this)) |
983
|
|
|
->addMoreInfo('method', $method) |
984
|
|
|
->addMoreInfo('arguments', var_export($arguments, true)); |
985
|
|
|
} |
986
|
|
|
|
987
|
|
|
/** |
988
|
|
|
* Attempts to call dynamic method. Returns array containing result or false. |
989
|
|
|
* |
990
|
|
|
* @param string $method Name of the method |
991
|
|
|
* @param array $arguments Arguments |
992
|
|
|
* |
993
|
|
|
* @return mixed |
994
|
|
|
*/ |
995
|
|
|
public function tryCall($method, $arguments) |
996
|
|
|
{ |
997
|
|
|
if ($ret = $this->hook('method-'.$method, $arguments)) { |
998
|
|
|
return $ret; |
999
|
|
|
} |
1000
|
|
|
array_unshift($arguments, $this); |
1001
|
|
|
if (($ret = $this->app->hook('global-method-'.$method, $arguments))) { |
1002
|
|
|
return $ret; |
1003
|
|
|
} |
1004
|
|
|
} |
1005
|
|
|
|
1006
|
|
|
/** |
1007
|
|
|
* Add new method for this object. |
1008
|
|
|
* |
1009
|
|
|
* @param string|array $name Name of new method of $this object |
1010
|
|
|
* @param callable $callable Callback |
1011
|
|
|
* |
1012
|
|
|
* @return $this |
1013
|
|
|
*/ |
1014
|
|
|
public function addMethod($name, $callable) |
1015
|
|
|
{ |
1016
|
|
View Code Duplication |
if (is_string($name) && strpos($name, ',') !== false) { |
1017
|
|
|
$name = explode(',', $name); |
1018
|
|
|
} |
1019
|
|
|
if (is_array($name)) { |
1020
|
|
|
foreach ($name as $h) { |
1021
|
|
|
$this->addMethod($h, $callable); |
1022
|
|
|
} |
1023
|
|
|
|
1024
|
|
|
return $this; |
1025
|
|
|
} |
1026
|
|
View Code Duplication |
if (is_object($callable) && !is_callable($callable)) { |
1027
|
|
|
$callable = array($callable, $name); |
1028
|
|
|
} |
1029
|
|
|
if ($this->hasMethod($name)) { |
1030
|
|
|
throw $this->exception('Registering method twice'); |
1031
|
|
|
} |
1032
|
|
|
$this->addHook('method-'.$name, $callable); |
1033
|
|
|
|
1034
|
|
|
return $this; |
1035
|
|
|
} |
1036
|
|
|
|
1037
|
|
|
/** |
1038
|
|
|
* Return if this object has specified method (either native or dynamic). |
1039
|
|
|
* |
1040
|
|
|
* @param string $name Name of the method |
1041
|
|
|
* |
1042
|
|
|
* @return bool |
1043
|
|
|
*/ |
1044
|
|
|
public function hasMethod($name) |
1045
|
|
|
{ |
1046
|
|
|
return method_exists($this, $name) |
1047
|
|
|
|| isset($this->hooks['method-'.$name]) |
1048
|
|
|
|| isset($this->app->hooks['global-method-'.$name]); |
1049
|
|
|
} |
1050
|
|
|
|
1051
|
|
|
/** |
1052
|
|
|
* Remove dynamically registered method. |
1053
|
|
|
* |
1054
|
|
|
* @param string $name Name of the method |
1055
|
|
|
* |
1056
|
|
|
* @return $this |
1057
|
|
|
*/ |
1058
|
|
|
public function removeMethod($name) |
1059
|
|
|
{ |
1060
|
|
|
$this->removeHook('method-'.$name); |
1061
|
|
|
|
1062
|
|
|
return $this; |
1063
|
|
|
} |
1064
|
|
|
// }}} |
1065
|
|
|
|
1066
|
|
|
// {{{ Logger: to be moved out |
1067
|
|
|
/** |
1068
|
|
|
* Output string into log file. |
1069
|
|
|
* |
1070
|
|
|
* @param string $var var |
1071
|
|
|
* @param string $msg msg |
1072
|
|
|
* |
1073
|
|
|
* @obsolete |
1074
|
|
|
*/ |
1075
|
|
|
public function logVar($var, $msg = '') |
1076
|
|
|
{ |
1077
|
|
|
$this->app->getLogger()->logVar($var, $msg); |
1078
|
|
|
} |
1079
|
|
|
|
1080
|
|
|
/** |
1081
|
|
|
* Output string into info file. |
1082
|
|
|
* |
1083
|
|
|
* @param string $info info |
1084
|
|
|
* @param string $msg msg |
1085
|
|
|
* |
1086
|
|
|
* @obsolete |
1087
|
|
|
*/ |
1088
|
|
|
public function logInfo($info, $msg = '') |
1089
|
|
|
{ |
1090
|
|
|
$this->app->getLogger()->logLine($msg.' '.$info."\n"); |
1091
|
|
|
} |
1092
|
|
|
|
1093
|
|
|
/** |
1094
|
|
|
* Output string into error file. |
1095
|
|
|
* |
1096
|
|
|
* @param string $error error |
1097
|
|
|
* @param string $msg msg |
1098
|
|
|
* |
1099
|
|
|
* @obsolete |
1100
|
|
|
*/ |
1101
|
|
|
public function logError($error, $msg = '') |
1102
|
|
|
{ |
1103
|
|
|
if (is_object($error)) { |
1104
|
|
|
// we got exception object obviously |
1105
|
|
|
$error = $error->getMessage(); |
1106
|
|
|
} |
1107
|
|
|
$this->app->getLogger()->logLine($msg.' '.$error."\n", null, 'error'); |
1108
|
|
|
} |
1109
|
|
|
// }}} |
1110
|
|
|
|
1111
|
|
|
/** |
1112
|
|
|
* A handy shortcut for foreach(){ .. } code. Make your callable return |
1113
|
|
|
* "false" if you would like to break the loop. |
1114
|
|
|
* |
1115
|
|
|
* @param string|callable $callable will be executed for each member |
1116
|
|
|
* |
1117
|
|
|
* @return $this |
1118
|
|
|
*/ |
1119
|
|
|
public function each($callable) |
1120
|
|
|
{ |
1121
|
|
|
if ($this instanceof Iterator) { |
1122
|
|
|
|
1123
|
|
|
if (is_string($callable)) { |
1124
|
|
|
foreach ($this as $obj) { |
|
|
|
|
1125
|
|
|
if ($obj->$callable() === false) { |
1126
|
|
|
break; |
1127
|
|
|
} |
1128
|
|
|
} |
1129
|
|
|
|
1130
|
|
|
return $this; |
1131
|
|
|
} |
1132
|
|
|
|
1133
|
|
|
foreach ($this as $obj) { |
|
|
|
|
1134
|
|
|
if (call_user_func($callable, $obj) === false) { |
1135
|
|
|
break; |
1136
|
|
|
} |
1137
|
|
|
} |
1138
|
|
|
|
1139
|
|
|
} else { |
1140
|
|
|
throw $this->exception('Calling each() on non-iterative object'); |
1141
|
|
|
} |
1142
|
|
|
|
1143
|
|
|
return $this; |
1144
|
|
|
} |
1145
|
|
|
|
1146
|
|
|
/** |
1147
|
|
|
* This method will find private methods started with test_ in |
1148
|
|
|
* the current class and will execute each method in succession |
1149
|
|
|
* by passing $t argument to it. Before each test execution |
1150
|
|
|
* takes place, $t->prepareForTest($test) will be called. It must |
1151
|
|
|
* return non-false for test to be carried out. |
1152
|
|
|
* |
1153
|
|
|
* $test will be an array containing keys for 'name', 'object' and |
1154
|
|
|
* 'class' |
1155
|
|
|
* |
1156
|
|
|
* @param Tester $tester |
1157
|
|
|
*/ |
1158
|
|
|
public function runTests(Tester $tester = null) |
1159
|
|
|
{ |
1160
|
|
|
$test = array('object' => $this->name, 'class' => get_class($this)); |
1161
|
|
|
|
1162
|
|
|
foreach (get_class_methods($this) as $method) { |
1163
|
|
|
if (strpos($method, 'test_') === 0) { |
1164
|
|
|
$test['name'] = substr($method, 5); |
1165
|
|
|
} else { |
1166
|
|
|
continue; |
1167
|
|
|
} |
1168
|
|
|
|
1169
|
|
|
if ($tester && $tester->prepareForTest($test) === false) { |
1170
|
|
|
continue; |
1171
|
|
|
} |
1172
|
|
|
|
1173
|
|
|
if ($tester !== null) { |
1174
|
|
|
/** @type Model $r */ |
1175
|
|
|
$r = $tester->results; |
1176
|
|
|
$r->unload(); |
1177
|
|
|
$r->set($test); |
1178
|
|
|
} |
1179
|
|
|
|
1180
|
|
|
// Proceed with test |
1181
|
|
|
$me = memory_get_peak_usage(); |
1182
|
|
|
$ms = microtime(true); |
1183
|
|
|
$this->_ticks = 0; |
1184
|
|
|
declare (ticks = 1); |
1185
|
|
|
register_tick_function(array($this, '_ticker')); |
1186
|
|
|
|
1187
|
|
|
// Execute here |
1188
|
|
|
try { |
1189
|
|
|
$result = $this->$method($tester); |
1190
|
|
|
} catch (Exception $e) { |
1191
|
|
|
unregister_tick_function(array($this, '_ticker')); |
1192
|
|
|
$time = microtime(true) - $ms; |
1193
|
|
|
$memory = (memory_get_peak_usage()) - $me; |
1194
|
|
|
$ticks = $this->_ticks; |
1195
|
|
|
|
1196
|
|
|
if ($e instanceof Exception_SkipTests) { |
1197
|
|
|
if ($tester !== null) { |
1198
|
|
|
$r['exception'] = 'SKIPPED'; |
|
|
|
|
1199
|
|
|
$r->saveAndUnload(); |
1200
|
|
|
} |
1201
|
|
|
|
1202
|
|
|
return array( |
1203
|
|
|
'skipped' => $e->getMessage(), |
1204
|
|
|
); |
1205
|
|
|
} |
1206
|
|
|
|
1207
|
|
|
if ($tester !== null) { |
1208
|
|
|
$r['time'] = $time; |
1209
|
|
|
$r['memory'] = $memory; |
1210
|
|
|
$r['ticks'] = $ticks; |
1211
|
|
|
$r['exception'] = $e; |
1212
|
|
|
$r->saveAndUnload(); |
1213
|
|
|
} |
1214
|
|
|
|
1215
|
|
|
continue; |
1216
|
|
|
} |
1217
|
|
|
|
1218
|
|
|
// Unregister |
1219
|
|
|
unregister_tick_function(array($this, '_ticker')); |
1220
|
|
|
$time = microtime(true) - $ms; |
1221
|
|
|
$memory = (memory_get_peak_usage()) - $me; |
1222
|
|
|
$ticks = $this->_ticks - 3; // there are always minimum of 3 ticks |
1223
|
|
|
|
1224
|
|
|
if ($tester !== null) { |
1225
|
|
|
$r['time'] = $time; |
1226
|
|
|
$r['memory'] = $memory; |
1227
|
|
|
$r['ticks'] = $ticks; |
1228
|
|
|
$r['is_success'] = true; |
1229
|
|
|
$r['result'] = $result; |
1230
|
|
|
$r->saveAndUnload(); |
1231
|
|
|
} |
1232
|
|
|
} |
1233
|
|
|
} |
1234
|
|
|
|
1235
|
|
|
private $_ticks; |
1236
|
|
|
public function _ticker() |
1237
|
|
|
{ |
1238
|
|
|
++$this->_ticks; |
1239
|
|
|
} |
1240
|
|
|
|
1241
|
|
|
/** |
1242
|
|
|
* Method used internally for shortening object names. |
1243
|
|
|
* |
1244
|
|
|
* @param string $desired Desired name of new object. |
1245
|
|
|
* |
1246
|
|
|
* @return string Shortened name of new object. |
1247
|
|
|
*/ |
1248
|
|
|
public function _shorten($desired) |
1249
|
|
|
{ |
1250
|
|
|
if (strlen($desired) > $this->app->max_name_length |
1251
|
|
|
&& $this->app->max_name_length !== false |
1252
|
|
|
) { |
1253
|
|
|
$len = $this->app->max_name_length - 10; |
1254
|
|
|
if ($len < 5) { |
1255
|
|
|
$len = $this->app->max_name_length; |
1256
|
|
|
} |
1257
|
|
|
|
1258
|
|
|
$key = substr($desired, 0, $len); |
1259
|
|
|
$rest = substr($desired, $len); |
1260
|
|
|
|
1261
|
|
|
if (!$this->app->unique_hashes[$key]) { |
1262
|
|
|
$this->app->unique_hashes[$key] = dechex(crc32($key)); |
1263
|
|
|
} |
1264
|
|
|
$desired = $this->app->unique_hashes[$key].'__'.$rest; |
1265
|
|
|
}; |
1266
|
|
|
|
1267
|
|
|
return $desired; |
1268
|
|
|
} |
1269
|
|
|
|
1270
|
|
|
private $_element_name_counts = array(); |
1271
|
|
|
public function _unique_element($desired = null) |
1272
|
|
|
{ |
1273
|
|
|
$postfix = @++$this->_element_name_counts[$desired]; |
1274
|
|
|
|
1275
|
|
|
return $desired.($postfix > 1 ? ('_'.$postfix) : ''); |
1276
|
|
|
} |
1277
|
|
|
|
1278
|
|
|
/** |
1279
|
|
|
* This funcion given the associative $array and desired new key will return |
1280
|
|
|
* the best matching key which is not yet in the array. |
1281
|
|
|
* For example, if you have array('foo'=>x,'bar'=>x) and $desired is 'foo' |
1282
|
|
|
* function will return 'foo_2'. If 'foo_2' key also exists in that array, |
1283
|
|
|
* then 'foo_3' is returned and so on. |
1284
|
|
|
* |
1285
|
|
|
* @param array &$array Reference to array which stores key=>value pairs |
1286
|
|
|
* @param string $desired Desired key for new object |
1287
|
|
|
* |
1288
|
|
|
* @return string unique key for new object |
1289
|
|
|
*/ |
1290
|
|
|
public function _unique(&$array, $desired = null) |
1291
|
|
|
{ |
1292
|
|
|
if (!is_array($array)) { |
1293
|
|
|
throw $this->exception('not array'); |
1294
|
|
|
} |
1295
|
|
|
$postfix = count($array); |
1296
|
|
|
$attempted_key = $desired; |
1297
|
|
|
while (array_key_exists($attempted_key, $array)) { |
1298
|
|
|
// already used, move on |
1299
|
|
|
$attempted_key = ($desired ?: 'undef').'_'.(++$postfix); |
1300
|
|
|
} |
1301
|
|
|
|
1302
|
|
|
return $attempted_key; |
1303
|
|
|
} |
1304
|
|
|
|
1305
|
|
|
/** |
1306
|
|
|
* Always call parent if you redefine this/. |
1307
|
|
|
*/ |
1308
|
|
|
public function __destruct() |
1309
|
|
|
{ |
1310
|
|
|
} |
1311
|
|
|
|
1312
|
|
|
/** |
1313
|
|
|
* Do not serialize objects. |
1314
|
|
|
* |
1315
|
|
|
* @return mixed |
1316
|
|
|
*/ |
1317
|
|
|
public function __sleep() |
1318
|
|
|
{ |
1319
|
|
|
return array('name'); |
1320
|
|
|
} |
1321
|
|
|
} |
1322
|
|
|
|
This property has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.