1
|
|
|
<?php |
2
|
|
|
namespace samson\core; |
3
|
|
|
|
4
|
|
|
// TODO: Разобраться почему с вызовом m()->render() во вьюхе, и почему не передаются параметры |
5
|
|
|
use samsonframework\core\ResourcesInterface; |
6
|
|
|
use samsonframework\core\SystemInterface; |
7
|
|
|
use samsonphp\core\exception\ControllerActionNotFound; |
8
|
|
|
use samsonphp\core\exception\ViewPathNotFound; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Модуль системы |
12
|
|
|
* |
13
|
|
|
* @author Vitaly Iegorov <[email protected]> |
14
|
|
|
* @version 1.0 |
15
|
|
|
*/ |
16
|
|
|
class Module implements iModule, \ArrayAccess |
|
|
|
|
17
|
|
|
{ |
18
|
|
|
/** Static module instances collection */ |
19
|
|
|
public static $instances = array(); |
20
|
|
|
|
21
|
|
|
/** Uniquer identifier to check pointers */ |
22
|
|
|
public $uid; |
23
|
|
|
|
24
|
|
|
/** @var ResourcesInterface Pointer to module resource map */ |
25
|
|
|
public $resourceMap; |
26
|
|
|
|
27
|
|
|
/** @var array TODO: WTF? */ |
28
|
|
|
public $composerParameters = array(); |
29
|
|
|
|
30
|
|
|
/** Module views collection */ |
31
|
|
|
protected $views = array(); |
32
|
|
|
|
33
|
|
|
/** Module location */ |
34
|
|
|
protected $path = ''; |
35
|
|
|
|
36
|
|
|
/** Unique module identifier */ |
37
|
|
|
protected $id = ''; |
38
|
|
|
|
39
|
|
|
/** Path to view for rendering */ |
40
|
|
|
protected $view_path = self::VD_POINTER_DEF; |
41
|
|
|
|
42
|
|
|
/** Pointer to view data entry */ |
43
|
|
|
protected $data = array(self::VD_POINTER_DEF => array(self::VD_HTML => '')); |
44
|
|
|
|
45
|
|
|
/** Collection of data for view rendering, filled with default pointer */ |
46
|
|
|
protected $view_data = array(self::VD_POINTER_DEF => array(self::VD_HTML => '')); |
47
|
|
|
|
48
|
|
|
/** Name of current view context entry */ |
49
|
|
|
protected $view_context = self::VD_POINTER_DEF; |
50
|
|
|
|
51
|
|
|
/** Unique module cache path in local web-application */ |
52
|
|
|
protected $cache_path; |
53
|
|
|
|
54
|
|
|
/** @var SystemInterface Instance for interaction with framework */ |
55
|
|
|
protected $system; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Constructor |
59
|
|
|
* |
60
|
|
|
* @param string $id Module unique identifier |
61
|
|
|
* @param string $path Module location |
62
|
|
|
* @param ResourcesInterface $resourceMap Pointer to module resource map |
63
|
|
|
* @param SystemInterface $system |
64
|
|
|
*/ |
65
|
|
|
public function __construct($id, $path, ResourcesInterface $resourceMap, SystemInterface $system) |
66
|
|
|
{ |
67
|
|
|
// Inject generic module dependencies |
68
|
|
|
$this->system = $system; |
69
|
|
|
|
70
|
|
|
// Store pointer to module resource map |
71
|
|
|
$this->resourceMap = &$resourceMap; |
72
|
|
|
// Save views list |
73
|
|
|
$this->views = $resourceMap->views; |
|
|
|
|
74
|
|
|
|
75
|
|
|
// Set default view context name |
76
|
|
|
$this->view_context = self::VD_POINTER_DEF; |
77
|
|
|
|
78
|
|
|
// Set up default view data pointer |
79
|
|
|
$this->data = &$this->view_data[$this->view_context]; |
80
|
|
|
|
81
|
|
|
// Set module identifier |
82
|
|
|
$this->id = $id; |
83
|
|
|
|
84
|
|
|
// Set path to module |
85
|
|
|
$this->path(realpath($path)); |
86
|
|
|
|
87
|
|
|
// Generate unique module identifier |
88
|
|
|
$this->uid = rand(0, 9999999) . '_' . microtime(true); |
89
|
|
|
|
90
|
|
|
// Add to module identifier to view data stack |
91
|
|
|
$this->data['id'] = $this->id; |
92
|
|
|
|
93
|
|
|
// Generate unique module cache path in local web-application |
94
|
|
|
$this->cache_path = __SAMSON_CWD__ . __SAMSON_CACHE_PATH . $this->id . '/'; |
95
|
|
|
|
96
|
|
|
// Save ONLY ONE copy of this instance in static instances collection, |
97
|
|
|
// avoiding rewriting by cloned modules |
98
|
|
|
!isset(self::$instances[$this->id]) ? self::$instances[$this->id] = &$this : ''; |
99
|
|
|
|
100
|
|
|
// Make view path relative to module - remove module path from view path |
101
|
|
|
$this->views = str_replace($this->path, '', $this->views); |
102
|
|
|
|
103
|
|
|
//elapsed('Registering module: '.$this->id.'('.$path.')' ); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** @see iModule::path() */ |
107
|
|
|
public function path($value = null) |
108
|
|
|
{ |
109
|
|
|
// Если передан параметр - установим его |
110
|
|
|
if (func_num_args()) { |
111
|
|
|
$this->path = isset($value{0}) ? rtrim(normalizepath($value), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : ''; |
112
|
|
|
|
113
|
|
|
return $this; |
114
|
|
|
} // Вернем относительный путь к файлам модуля |
115
|
|
|
else return $this->path; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** @see iModule::title() */ |
119
|
|
|
public function title($title = null) |
120
|
|
|
{ |
121
|
|
|
return $this->set('title', $title); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** @see iModule::set() */ |
125
|
|
|
public function set($field, $value = null) |
126
|
|
|
{ |
127
|
|
|
$this->__set($field, $value); |
128
|
|
|
|
129
|
|
|
return $this; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** @see iModule::id() */ |
133
|
|
|
public function id() |
134
|
|
|
{ |
135
|
|
|
return $this->id; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** @see iModuleViewable::toView() */ |
139
|
|
|
public function toView($prefix = null, array $restricted = array()) |
140
|
|
|
{ |
141
|
|
|
// Get all module data variables |
142
|
|
|
$view_data = array_merge($this->data, get_object_vars($this)); |
143
|
|
|
|
144
|
|
|
// Remove plain HTML from view data |
145
|
|
|
unset($view_data[self::VD_HTML]); |
146
|
|
|
|
147
|
|
|
return $view_data; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** @see iModule::html() */ |
151
|
|
|
public function html($value = null) |
152
|
|
|
{ |
153
|
|
|
//elapsed($this->id.' - Setting HTML for '.array_search( $this->data, $this->view_data ).'('.strlen($value).')'); |
154
|
|
|
|
155
|
|
|
// Если передан параметр то установим его |
156
|
|
|
if (func_num_args()) { |
157
|
|
|
$this->data[self::VD_HTML] = $value; |
158
|
|
|
} else { |
159
|
|
|
return $this->data['html']; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
return $this; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
public function view($viewPath) |
166
|
|
|
{ |
167
|
|
|
// Find full path to view file |
168
|
|
|
$viewPath = $this->findView($viewPath); |
169
|
|
|
|
170
|
|
|
// We could not find view |
171
|
|
|
if ($viewPath !== false) { |
172
|
|
|
// Switch view context to founded module view |
173
|
|
|
$this->viewContext($viewPath); |
174
|
|
|
|
175
|
|
|
// Set current view path |
176
|
|
|
$this->view_path = $viewPath; |
177
|
|
|
} else { |
178
|
|
|
throw(new ViewPathNotFound($viewPath)); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
// Продолжим цепирование |
182
|
|
|
return $this; |
|
|
|
|
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Find view file by its part in module view resources and return full path to it. |
187
|
|
|
* |
188
|
|
|
* @param string $viewPath Part of path to module view file |
189
|
|
|
* @return string Full path to view file |
190
|
|
|
*/ |
191
|
|
|
public function findView($viewPath) |
192
|
|
|
{ |
193
|
|
|
// Remove file extension for correct array searching |
194
|
|
|
$viewPath = str_replace(array('.php', '.vphp'), '', $viewPath); |
195
|
|
|
|
196
|
|
|
// Try to find passed view_path in resources views collection |
197
|
|
|
if (sizeof($view = preg_grep('/' . addcslashes($viewPath, '/\\') . '(\.php|\.vphp)/ui', $this->views))) { |
198
|
|
|
// Sort view paths to get the shortest path |
199
|
|
|
usort($view, array($this, 'sortStrings')); |
200
|
|
|
|
201
|
|
|
// Set current full view path as last found view |
202
|
|
|
return end($view); |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
return false; |
|
|
|
|
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* Perform module view context switching |
210
|
|
|
* @param string $view_path New view context name |
211
|
|
|
*/ |
212
|
|
|
protected function viewContext($view_path) |
213
|
|
|
{ |
214
|
|
|
// Pointer to NEW view data context |
215
|
|
|
$new = &$this->view_data[$view_path]; |
216
|
|
|
|
217
|
|
|
// Pointer to OLD view data context |
218
|
|
|
$old = &$this->view_data[$this->view_context]; |
219
|
|
|
|
220
|
|
|
// If we are trying to switch to NEW view context |
221
|
|
|
if ($this->view_context !== $view_path) { |
222
|
|
|
//elapsed( $this->id.' - Switching view context from '.$this->view_context.' to '.$view_path ); |
223
|
|
|
|
224
|
|
|
// Create new entry in view data collection if it does not exists |
225
|
|
|
if (!isset($this->view_data[$view_path])) { |
226
|
|
|
// Create new view data record |
227
|
|
|
$new = array(); |
228
|
|
|
|
229
|
|
|
// If current view data context has view data |
230
|
|
|
if (isset($old)) { |
231
|
|
|
//elapsed($old); |
232
|
|
|
//elapsed( $this->id.' - Copying previous view context view data '.$this->view_context.' to new view context '.$view_path.'('.sizeof($old).')'); |
233
|
|
|
|
234
|
|
|
// Copy default view context view data to new view context |
235
|
|
|
$new = array_merge($new, $old); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
// Clear plain HTML for new view context |
239
|
|
|
$new[self::VD_HTML] = ''; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
// Change view data pointer to appropriate view data entry |
243
|
|
|
$this->data = &$new; |
244
|
|
|
|
245
|
|
|
// Save current context name |
246
|
|
|
$this->view_context = $view_path; |
247
|
|
|
} |
248
|
|
|
//else elapsed( $this->id.' - NO need to switch view context from '.$this->view_context.' to '.$view_path ); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/** @see iModule::render() */ |
252
|
|
|
public function render($controller = null) |
253
|
|
|
{ |
254
|
|
|
// Switch current system active module |
255
|
|
|
$old = &$this->system->active($this); |
256
|
|
|
|
257
|
|
|
// If specific controller action should be run |
258
|
|
|
if (isset($controller)) { |
259
|
|
|
// Define if this is a procedural controller or OOP |
260
|
|
|
$callback = function_exists($controller) |
261
|
|
|
? $controller |
262
|
|
|
: array($this, iModule::OBJ_PREFIX . $controller); |
263
|
|
|
|
264
|
|
|
// If this controller action is present |
265
|
|
|
if (is_callable($callback)) { |
266
|
|
|
// Perform controller action |
267
|
|
|
call_user_func_array($callback, url()->parameters); |
268
|
|
|
} else { |
269
|
|
|
throw(new ControllerActionNotFound($this->id . '#' . $controller)); |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
// Output view |
274
|
|
|
echo $this->output(); |
275
|
|
|
|
276
|
|
|
// Restore previous active module |
277
|
|
|
$this->system->active($old); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** @see iModule::output() */ |
281
|
|
|
public function output($viewPath = null) |
282
|
|
|
{ |
283
|
|
|
// If view path not specified - use current correct view path |
284
|
|
|
if (!isset($viewPath)) { |
285
|
|
|
$viewPath = $this->view_path; |
286
|
|
|
} elseif (isset($viewPath{0})) { // Direct rendering of specific view, not default view data entry |
287
|
|
|
elapsed('Outputting to a view is deprecated, split your rendering chain into ->view(..)->output()'); |
288
|
|
|
$viewPath = $this->findView($viewPath); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
//elapsed('['.$this->id.'] Rendering view context: ['.$viewPath.'] with ['.$renderer->id.']'); |
292
|
|
|
|
293
|
|
|
// Switch view context to new module view |
294
|
|
|
$this->viewContext($viewPath); |
295
|
|
|
|
296
|
|
|
//elapsed($this->id.' - Outputing '.$view_path.'-'.sizeof($this->data)); |
297
|
|
|
//elapsed(array_keys($this->view_data)); |
298
|
|
|
|
299
|
|
|
// Get current view context plain HTML |
300
|
|
|
$out = $this->data[self::VD_HTML]; |
301
|
|
|
|
302
|
|
|
// If view path specified |
303
|
|
|
if (isset($viewPath{0})) { |
304
|
|
|
// Временно изменим текущий модуль системы |
305
|
|
|
$old = $this->system->active($this); |
306
|
|
|
|
307
|
|
|
// Прорисуем представление модуля |
308
|
|
|
$out .= $this->system->render($this->path . $viewPath, $this->data); |
309
|
|
|
|
310
|
|
|
// Вернем на место текущий модуль системы |
311
|
|
|
$this->system->active($old); |
312
|
|
|
} elseif (!isset($out{0})) { // No plain HTML view data is set also |
313
|
|
|
return e('Cannot render view for module ## - No view path or data has been set', E_SAMSON_CORE_ERROR, $this->id); |
|
|
|
|
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
// Clear currently outputted view context from VCS |
317
|
|
|
unset($this->view_data[$viewPath]); |
318
|
|
|
|
319
|
|
|
// Get last element from VCS |
320
|
|
|
end($this->view_data); |
321
|
|
|
|
322
|
|
|
// Get last element from VCS name |
323
|
|
|
$this->view_context = key($this->view_data); |
324
|
|
|
|
325
|
|
|
// Set internal view data pointer to last VCS entry |
326
|
|
|
$this->data = &$this->view_data[$this->view_context]; |
327
|
|
|
|
328
|
|
|
// Return view path to previous state |
329
|
|
|
$this->view_path = $this->view_context; |
330
|
|
|
|
331
|
|
|
// Вернем результат прорисовки |
332
|
|
|
return $out; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** Обработчик уничтожения объекта */ |
336
|
|
|
public function __destruct() |
337
|
|
|
{ |
338
|
|
|
//trace('Уничтожение модуля:'.$this->id ); |
339
|
|
|
|
340
|
|
|
// Очистим коллекцию загруженых модулей |
341
|
|
|
unset(Module::$instances[$this->id]); |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
/** Magic method for calling unexisting object methods */ |
345
|
|
|
public function __call($method, $arguments) |
346
|
|
|
{ |
347
|
|
|
//elapsed($this->id.' - __Call '.$method); |
348
|
|
|
|
349
|
|
|
// If value is passed - set it |
350
|
|
|
if (sizeof($arguments)) { |
351
|
|
|
// If first argument is object or array - pass method name as second parameter |
352
|
|
|
if (is_object($arguments[0]) || is_array($arguments[0])) $this->__set($arguments[0], $method); |
353
|
|
|
// Standard logic |
354
|
|
|
else $this->__set($method, $arguments[0]); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
// Chaining |
358
|
|
|
return $this; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
// Магический метод для получения переменных представления модуля |
362
|
|
|
|
363
|
|
|
/** Обработчик сериализации объекта */ |
364
|
|
|
public function __sleep() |
365
|
|
|
{ |
366
|
|
|
return array('id', 'path', 'data', 'views'); |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
/** Обработчик десериализации объекта */ |
370
|
|
|
public function __wakeup() |
371
|
|
|
{ |
372
|
|
|
// Fill global instances |
373
|
|
|
self::$instances[$this->id] = &$this; |
374
|
|
|
|
375
|
|
|
// Set up default view data pointer |
376
|
|
|
$this->view_data[self::VD_POINTER_DEF] = $this->data; |
377
|
|
|
|
378
|
|
|
// Set reference to view context entry |
379
|
|
|
$this->data = &$this->view_data[self::VD_POINTER_DEF]; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
// TODO: Переделать обработчик в одинаковый вид для объектов и простых |
383
|
|
|
|
384
|
|
|
/** Группа методов для доступа к аттрибутам в виде массива */ |
385
|
|
|
public function offsetSet($offset, $value) |
386
|
|
|
{ |
387
|
|
|
$this->__set($offset, $value); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
public function offsetGet($offset) |
391
|
|
|
{ |
392
|
|
|
return $this->__get($offset); |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
// Магический метод для установки переменных представления модуля |
396
|
|
|
|
397
|
|
|
public function __get($field) |
398
|
|
|
{ |
399
|
|
|
// Установим пустышку как значение переменной |
400
|
|
|
$result = null; |
401
|
|
|
|
402
|
|
|
// Если указанная переменная представления существует - получим её значение |
403
|
|
|
if (isset($this->data[$field])) $result = &$this->data[$field]; |
404
|
|
|
// Выведем ошибку |
405
|
|
|
else return e('Ошибка получения данных модуля(##) - Требуемые данные(##) не найдены', E_SAMSON_CORE_ERROR, array($this->id, $field)); |
406
|
|
|
|
407
|
|
|
// Иначе вернем пустышку |
408
|
|
|
return $result; |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
public function __set($field, $value = null) |
412
|
|
|
{ |
413
|
|
|
// This is object |
414
|
|
|
if (is_object($field)) { |
415
|
|
|
$implements = class_implements($field); |
416
|
|
|
// If iModuleViewable implements is passed |
417
|
|
|
if ( |
418
|
|
|
// TODO: Remove old interface support in future |
419
|
|
|
in_array(ns_classname('iModuleViewable', 'samson\core'), $implements) |
|
|
|
|
420
|
|
|
|| in_array(AutoLoader::className('IViewSettable', 'samson\core'), $implements) |
421
|
|
|
|| in_array('samsonframework\core\RenderInterface', $implements) |
422
|
|
|
) { |
423
|
|
|
$this->_setObject($field, $value); |
424
|
|
|
} |
425
|
|
|
} // If array is passed |
426
|
|
|
else if (is_array($field)) $this->_setArray($field, $value); |
|
|
|
|
427
|
|
|
// Set view variable |
428
|
|
|
else $this->data[$field] = $value; |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
public function offsetUnset($offset) |
432
|
|
|
{ |
433
|
|
|
$this->data[$offset] = ''; |
434
|
|
|
} |
435
|
|
|
|
436
|
|
|
public function offsetExists($offset) |
437
|
|
|
{ |
438
|
|
|
return isset($this->data[$offset]); |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
/** Sort array by string length */ |
442
|
|
|
protected function sortStrings($a, $b) |
443
|
|
|
{ |
444
|
|
|
return strlen($b) - strlen($a); |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
/** |
448
|
|
|
* Create unique module cache folder structure in local web-application |
449
|
|
|
* @param string $file Path to file relative to module cache location |
450
|
|
|
* @param boolean $clear Flag to perform generic cache folder clearence |
451
|
|
|
* @return boolean TRUE if cache file has to be regenerated |
452
|
|
|
*/ |
453
|
|
|
protected function cache_refresh(& $file, $clear = true) |
454
|
|
|
{ |
455
|
|
|
// If module cache folder does not exists - create it |
456
|
|
|
if (!file_exists($this->cache_path)) mkdir($this->cache_path, 0777, TRUE); |
457
|
|
|
|
458
|
|
|
// Build full path to cached file |
459
|
|
|
$file = $this->cache_path . $file; |
460
|
|
|
|
461
|
|
|
// If cached file does not exsits |
462
|
|
|
if (file_exists($file)) return false; |
463
|
|
|
// Needed file does not exists |
464
|
|
|
else { |
465
|
|
|
// If clearence flag set to true - clear all files in module cache directory with same extension |
466
|
|
|
if ($clear) File::clear($this->cache_path, pathinfo($file, PATHINFO_EXTENSION)); |
467
|
|
|
|
468
|
|
|
// Singal for cache file regeneration |
469
|
|
|
return true; |
470
|
|
|
} |
471
|
|
|
} |
472
|
|
|
|
473
|
|
|
/** |
474
|
|
|
* |
475
|
|
|
* @param unknown $object |
476
|
|
|
* @param string $viewprefix |
477
|
|
|
*/ |
478
|
|
|
private function _setObject($object, $viewprefix = null) |
479
|
|
|
{ |
480
|
|
|
// Generate viewprefix as only lowercase classname without NS if it is not specified |
481
|
|
|
$class_name = is_string($viewprefix) ? $viewprefix : '' . mb_strtolower(classname(get_class($object)), 'UTF-8'); |
482
|
|
|
|
483
|
|
|
// Save object to view data |
484
|
|
|
$this->data[$class_name] = $object; |
485
|
|
|
|
486
|
|
|
// Add separator |
487
|
|
|
$class_name .= '_'; |
488
|
|
|
|
489
|
|
|
// Generate objects view array data and merge it with view data |
490
|
|
|
$this->data = array_merge($this->data, $object->toView($class_name)); |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
/** |
494
|
|
|
* |
495
|
|
|
* @param unknown $array |
496
|
|
|
* @param string $viewprefix |
497
|
|
|
*/ |
498
|
|
|
private function _setArray($array, $viewprefix = null) |
499
|
|
|
{ |
500
|
|
|
// Save array to view data |
501
|
|
|
$this->data[$viewprefix] = $array; |
502
|
|
|
|
503
|
|
|
// Add array values to view data |
504
|
|
|
$this->data = array_merge($this->data, $array); |
505
|
|
|
} |
506
|
|
|
} |
507
|
|
|
|
This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.