1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
/* |
3
|
|
|
* This file is part of the SamsonPHP\Core package. |
4
|
|
|
* (c) 2013 Vitaly Iegorov <[email protected]> |
5
|
|
|
* |
6
|
|
|
* For the full copyright and license information, please view the LICENSE |
7
|
|
|
* file that was distributed with this source code. |
8
|
|
|
*/ |
9
|
|
|
namespace samson\core; |
10
|
|
|
|
11
|
|
|
use Doctrine\Common\Annotations\AnnotationReader; |
12
|
|
|
use samsonframework\container\Builder; |
13
|
|
|
use samsonframework\container\metadata\ClassMetadata; |
14
|
|
|
use samsonframework\container\metadata\MethodMetadata; |
15
|
|
|
use samsonframework\container\metadata\PropertyMetadata; |
16
|
|
|
use samsonframework\containerannotation\AnnotationClassResolver; |
17
|
|
|
use samsonframework\containerannotation\AnnotationMetadataCollector; |
18
|
|
|
use samsonframework\containerannotation\AnnotationMethodResolver; |
19
|
|
|
use samsonframework\containerannotation\AnnotationPropertyResolver; |
20
|
|
|
use samsonframework\containerannotation\AnnotationResolver; |
21
|
|
|
use samsonframework\containerannotation\Inject; |
22
|
|
|
use samsonframework\containerannotation\Injectable; |
23
|
|
|
use samsonframework\containerannotation\InjectArgument; |
24
|
|
|
use samsonframework\core\PreparableInterface; |
25
|
|
|
use samsonframework\core\SystemInterface; |
26
|
|
|
use samsonframework\di\ContainerInterface; |
27
|
|
|
use samsonframework\resource\ResourceMap; |
28
|
|
|
use samsonphp\config\Scheme; |
29
|
|
|
use samsonphp\core\exception\CannotLoadModule; |
30
|
|
|
use samsonphp\core\exception\ViewPathNotFound; |
31
|
|
|
use samsonphp\core\Module; |
32
|
|
|
use samsonphp\event\Event; |
33
|
|
|
use samsonframework\container\ContainerBuilderInterface; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* SamsonPHP Core. |
37
|
|
|
* |
38
|
|
|
* @author Vitaly Iegorov <[email protected]> |
39
|
|
|
*/ |
40
|
|
|
class Core implements SystemInterface |
41
|
|
|
{ |
42
|
|
|
/** @var ContainerInterface */ |
43
|
|
|
protected $container; |
44
|
|
|
|
45
|
|
|
/** @var ClassMetadata[] */ |
46
|
|
|
protected $metadataCollection = []; |
47
|
|
|
|
48
|
|
|
/** @var ContainerBuilderInterface */ |
49
|
|
|
protected $builder; |
50
|
|
|
|
51
|
|
|
/** @var string Current system environment */ |
52
|
|
|
protected $environment; |
53
|
|
|
|
54
|
|
|
/* Rendering models */ |
55
|
|
|
/** @deprecated Standard algorithm for view rendering */ |
56
|
|
|
const RENDER_STANDART = 1; |
57
|
|
|
/** @deprecated View rendering algorithm from array of view variables */ |
58
|
|
|
const RENDER_VARIABLE = 3; |
59
|
|
|
|
60
|
|
|
/** @deprecated @var ResourceMap Current web-application resource map */ |
61
|
|
|
public $map; |
62
|
|
|
|
63
|
|
|
/** @deprecated @var string Path to current web-application */ |
64
|
|
|
public $system_path = __SAMSON_CWD__; |
65
|
|
|
/** @deprecated @var string View path loading mode */ |
66
|
|
|
public $render_mode = self::RENDER_STANDART; |
67
|
|
|
/** @var Module Pointer to current active module */ |
68
|
|
|
protected $active = null; |
69
|
|
|
/** @var bool Flag for outputting layout template, used for asynchronous requests */ |
70
|
|
|
protected $async = false; |
71
|
|
|
/** @var string Path to main system template */ |
72
|
|
|
protected $template_path = __SAMSON_DEFAULT_TEMPLATE; |
73
|
|
|
|
74
|
|
|
/** @return \Container Get system container */ |
75
|
|
|
public function getContainer() |
76
|
|
|
{ |
77
|
|
|
return $this->container; |
|
|
|
|
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Core constructor. |
82
|
|
|
* |
83
|
|
|
* @param ContainerBuilderInterface $builder Container builder |
84
|
|
|
* @param ResourceMap|null $map system resources |
85
|
|
|
*/ |
86
|
|
|
public function __construct(ContainerBuilderInterface $builder, ResourceMap $map = null) |
87
|
|
|
{ |
88
|
|
|
$this->builder = $builder; |
89
|
|
|
|
90
|
|
|
if (!isset($map)) { |
91
|
|
|
// Get correct web-application path |
92
|
|
|
$this->system_path = __SAMSON_CWD__; |
|
|
|
|
93
|
|
|
|
94
|
|
|
// Get web-application resource map |
95
|
|
|
$this->map = ResourceMap::get($this->system_path, false, array('src/')); |
|
|
|
|
96
|
|
|
} else { // Use data from passed map |
97
|
|
|
$this->map = $map; |
|
|
|
|
98
|
|
|
$this->system_path = $map->entryPoint; |
|
|
|
|
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
// Temporary add template worker |
102
|
|
|
$this->subscribe('core.rendered', array($this, 'generateTemplate')); |
103
|
|
|
|
104
|
|
|
// Fire core creation event |
105
|
|
|
Event::fire('core.created', array(&$this)); |
106
|
|
|
|
107
|
|
|
// Signal core configure event |
108
|
|
|
Event::signal('core.configure', array($this->system_path . __SAMSON_CONFIG_PATH)); |
|
|
|
|
109
|
|
|
} |
110
|
1 |
|
|
111
|
|
|
/** |
112
|
|
|
* Generic wrap for Event system subscription. |
113
|
|
|
* @see \samson\core\\samsonphp\event\Event::subscribe() |
114
|
|
|
* |
115
|
|
|
* @param string $key Event identifier |
116
|
|
|
* @param callable $handler Event handler |
117
|
|
|
* @param array $params Event parameters |
118
|
|
|
* |
119
|
|
|
* @return $this Chaining |
120
|
|
|
*/ |
121
|
1 |
|
public function subscribe($key, $handler, $params = array()) |
122
|
|
|
{ |
123
|
|
|
Event::subscribe($key, $handler, $params); |
124
|
|
|
|
125
|
|
|
return $this; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Change current system working environment or receive |
130
|
|
|
* current system enviroment if no arguments are passed. |
131
|
|
|
* |
132
|
|
|
* @param string $environment Environment identifier |
133
|
|
|
* |
134
|
|
|
* TODO: Function has two different logics - needs to be changed! |
135
|
|
|
* @return $this|string Chaining or current system environment |
136
|
|
|
*/ |
137
|
|
|
public function environment($environment = Scheme::BASE) |
138
|
|
|
{ |
139
|
|
|
if (func_num_args() !== 0) { |
140
|
|
|
$this->environment = $environment; |
141
|
|
|
|
142
|
|
|
// Signal core environment change |
143
|
|
|
Event::signal('core.environment.change', array($environment, &$this)); |
144
|
|
|
return $this; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
return $this->environment; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Generate special response header triggering caching mechanisms |
152
|
|
|
* @param int $cacheLife Amount of seconds for cache(default 3600 - 1 hour) |
153
|
|
|
* @param string $accessibility Cache-control accessibility value(default public) |
154
|
|
|
*/ |
155
|
|
|
public function cached($cacheLife = 3600, $accessibility = 'public') |
156
|
|
|
{ |
157
|
|
|
static $cached; |
158
|
|
|
// Protect sending cached headers once |
159
|
|
|
if (!isset($cached) or $cached !== true) { |
160
|
|
|
header('Expires: ' . gmdate('D, d M Y H:i:s T', time() + $cacheLife)); |
161
|
|
|
header('Cache-Control: ' . $accessibility . ', max-age=' . $cacheLife); |
162
|
|
|
header('Pragma: cache'); |
163
|
|
|
|
164
|
|
|
$cached = true; |
165
|
|
|
} |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Set asynchronous mode. |
170
|
|
|
* This mode will not output template and will just path everything that |
171
|
|
|
* was outputted to client. |
172
|
|
|
* |
173
|
|
|
* @param bool $async True to switch to asynchronous output mode |
174
|
|
|
* |
175
|
|
|
* @return $this Chaining |
176
|
|
|
*/ |
177
|
|
|
public function async($async) |
178
|
|
|
{ |
179
|
|
|
$this->async = $async; |
180
|
|
|
|
181
|
|
|
return $this; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** @see iCore::path() */ |
185
|
|
|
public function path($path = null) |
186
|
|
|
{ |
187
|
|
|
// Если передан аргумент |
188
|
|
|
if (func_num_args()) { |
189
|
|
|
// Сформируем новый относительный путь к главному шаблону системы |
190
|
|
|
$this->template_path = $path . $this->template_path; |
191
|
|
|
|
192
|
|
|
// Сохраним относительный путь к Веб-приложению |
193
|
|
|
$this->system_path = $path; |
|
|
|
|
194
|
|
|
|
195
|
|
|
// Продолжил цепирование |
196
|
|
|
return $this; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
// Вернем текущее значение |
200
|
|
|
return $this->system_path; |
|
|
|
|
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** @see iModule::active() */ |
204
|
|
|
public function &active(&$module = null) |
205
|
|
|
{ |
206
|
|
|
// Сохраним старый текущий модуль |
207
|
|
|
$old = &$this->active; |
208
|
|
|
|
209
|
|
|
// Если передано значение модуля для установки как текущий - проверим и установим его |
210
|
|
|
if (isset($module)) { |
211
|
|
|
$this->active = &$module; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
// Вернем значение текущего модуля |
215
|
|
|
return $old; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Retrieve module instance by identifier. |
220
|
|
|
* |
221
|
|
|
* @param string|null $module Module identifier |
222
|
|
|
* |
223
|
|
|
* @return null|Module Found or active module |
224
|
|
|
*/ |
225
|
|
|
public function &module($module = null) |
226
|
|
|
{ |
227
|
|
|
$return = null; |
228
|
|
|
|
229
|
|
|
// Ничего не передано - вернем текущуй модуль системы |
230
|
|
|
if (!isset($module) && isset($this->active)) { |
231
|
|
|
$return = &$this->active; |
232
|
|
|
} elseif (is_object($module)) { |
233
|
|
|
$return = &$module; |
234
|
|
|
} elseif (is_string($module)) { |
235
|
|
|
$return = $this->container->get($module); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
// Ничего не получилось вернем ошибку |
239
|
|
|
if ($return === null) { |
240
|
|
|
e('Не возможно получить модуль(##) системы', E_SAMSON_CORE_ERROR, array($module)); |
|
|
|
|
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
return $return; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Unload module from core. |
248
|
|
|
* |
249
|
|
|
* @param string $moduleID Module identifier |
250
|
|
|
*/ |
251
|
|
|
public function unload($moduleID) |
252
|
|
|
{ |
253
|
|
|
if (isset($this->module_stack[$moduleID])) { |
|
|
|
|
254
|
|
|
unset($this->module_stack[$moduleID]); |
255
|
|
|
} |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* Insert generic html template tags and data |
260
|
|
|
* |
261
|
|
|
* @param string $templateHtml Generated HTML |
262
|
|
|
* |
263
|
|
|
* @deprecated Must be moved to a new HTML output object |
264
|
2 |
|
* @return mixed Changed HTML template |
265
|
|
|
*/ |
266
|
|
|
public function generateTemplate(&$templateHtml) |
267
|
|
|
{ |
268
|
|
|
// Добавим путь к ресурсам для браузера |
269
|
|
|
$headHtml = "\n" . '<base href="' . url()->base() . '">'; |
270
|
|
|
// Добавим отметку времени для JavaScript |
271
|
2 |
|
$headHtml .= "\n" . '<script type="text/javascript">var __SAMSONPHP_STARTED = new Date().getTime();</script>'; |
272
|
|
|
|
273
|
|
|
// Добавим поддержку HTML для старых IE |
274
|
2 |
|
$headHtml .= "\n" . '<!--[if lt IE 9]>'; |
275
|
|
|
$headHtml .= "\n" . '<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>'; |
276
|
|
|
$headHtml .= "\n" . '<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>'; |
277
|
|
|
$headHtml .= "\n" . '<![endif]-->'; |
278
|
2 |
|
|
279
|
|
|
// Выполним вставку главного тега <base> от которого зависят все ссылки документа |
280
|
2 |
|
// также подставим МЕТА-теги для текущего модуля и сгенерированный минифицированный CSS |
281
|
|
|
$templateHtml = str_ireplace('<head>', '<head>' . $headHtml, $templateHtml); |
282
|
2 |
|
|
283
|
2 |
|
// Вставим указатель JavaScript ресурсы в конец HTML документа |
284
|
|
|
$templateHtml = str_ireplace('</html>', '</html>' . __SAMSON_COPYRIGHT, $templateHtml); |
285
|
|
|
|
286
|
2 |
|
return $templateHtml; |
287
|
|
|
} |
288
|
2 |
|
|
289
|
|
|
/** |
290
|
2 |
|
* Start SamsonPHP framework. |
291
|
|
|
* |
292
|
|
|
* @param string $default Default module identifier |
293
|
|
|
* |
294
|
|
|
* @throws ViewPathNotFound |
295
|
2 |
|
*/ |
296
|
|
|
public function start($default) |
297
|
|
|
{ |
298
|
|
|
// TODO: Change ExternalModule::init() signature |
299
|
|
|
// Fire core started event |
300
|
|
|
Event::fire('core.started'); |
301
|
|
|
|
302
|
|
|
// TODO: Does not see why it should be here |
303
|
|
|
// Set main template path |
304
|
|
|
$this->template($this->template_path); |
305
|
|
|
|
306
|
|
|
// Security layer |
307
|
|
|
$securityResult = true; |
308
|
|
|
// Fire core security event |
309
|
|
|
Event::fire('core.security', array(&$this, &$securityResult)); |
310
|
|
|
|
311
|
|
|
/** @var mixed $result External route controller action result */ |
312
|
|
|
$result = false; |
313
|
|
|
|
314
|
|
|
// If we have passed security application layer |
315
|
|
|
if ($securityResult) { |
316
|
|
|
// Fire core routing event - go to routing application layer |
317
|
|
|
Event::signal('core.routing', array(&$this, &$result, $default)); |
318
|
|
|
} |
319
|
|
|
|
320
|
2 |
|
// If no one has passed back routing callback |
321
|
|
|
if (!isset($result) || $result === false) { |
322
|
|
|
// Fire core e404 - routing failed event |
323
|
2 |
|
$result = Event::signal('core.e404', array(url()->module, url()->method)); |
324
|
|
|
} |
325
|
|
|
|
326
|
2 |
|
// Response |
327
|
|
|
$output = ''; |
328
|
|
|
|
329
|
2 |
|
// If this is not asynchronous response and controller has been executed |
330
|
|
|
if (!$this->async && ($result !== false)) { |
331
|
|
|
// Store module data |
332
|
2 |
|
$data = $this->active->toView(); |
333
|
|
|
|
334
|
|
|
// Render main template |
335
|
2 |
|
$output = $this->render($this->template_path, $data); |
336
|
|
|
|
337
|
|
|
// Fire after render event |
338
|
2 |
|
Event::fire('core.rendered', array(&$output)); |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
// Output results to client |
342
|
|
|
echo $output; |
343
|
|
|
|
344
|
|
|
// Fire ended event |
345
|
|
|
Event::fire('core.ended', array(&$output)); |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** @see iCore::template() */ |
349
|
|
|
public function template( $template = NULL, $absolutePath = false ) |
|
|
|
|
350
|
|
|
{ |
351
|
|
|
// Если передан аргумент |
352
|
7 |
|
if( func_num_args() ){ |
|
|
|
|
353
|
|
|
$this->template_path = ($absolutePath)?$template:$this->active->path().$template; |
354
|
7 |
|
} |
355
|
|
|
|
356
|
7 |
|
// Аргументы не переданы - вернем текущий путь к шаблону системы |
357
|
|
|
return $this->template_path; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* Render file to a buffer. |
362
|
|
|
* |
363
|
|
|
* @param string $view Path to file |
364
|
|
|
* @param array $data Collection of variables to path to file |
365
|
|
|
* |
366
|
|
|
* @return string Rendered file contents |
367
|
|
|
* @throws ViewPathNotFound |
368
|
|
|
*/ |
369
|
|
|
public function render($view, $data = array()) |
370
|
|
|
{ |
371
|
|
|
// TODO: Make rendering as external system, to split up these 3 rendering options |
372
|
|
|
|
373
|
|
|
// Объявить ассоциативный массив переменных в данном контексте |
374
|
|
|
if (is_array($data)) { |
375
|
|
|
extract($data); |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
// Начать вывод в буффер |
379
|
|
|
ob_start(); |
380
|
|
|
|
381
|
|
|
// Path to another template view, by default we are using default template folder path, |
382
|
|
|
// for meeting first condition |
383
|
|
|
$templateView = $view; |
384
|
|
|
|
385
|
|
|
if (locale() != SamsonLocale::DEF) { |
386
|
|
|
// Modify standard view path with another template |
387
|
|
|
$templateView = str_replace(__SAMSON_VIEW_PATH, __SAMSON_VIEW_PATH . locale() . '/', $templateView); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
// Depending on core view rendering model |
391
|
|
|
switch ($this->render_mode) { |
|
|
|
|
392
|
|
|
// Standard algorithm for view rendering |
393
|
|
|
case self::RENDER_STANDART: |
|
|
|
|
394
|
|
|
// Trying to find another template path, by default it's an default template path |
395
|
|
|
if (file_exists($templateView)) { |
396
|
|
|
include($templateView); |
397
|
|
|
} elseif (file_exists($view)) { |
398
|
|
|
// If another template wasn't found - we will use default template path |
399
|
|
|
include($view); |
400
|
|
|
} else { // Error no template view was found |
401
|
|
|
throw(new ViewPathNotFound($view)); |
402
|
|
|
} |
403
|
2 |
|
break; |
404
|
|
|
|
405
|
|
|
// View rendering algorithm from array of view variables |
406
|
2 |
|
case self::RENDER_VARIABLE: |
|
|
|
|
407
|
|
|
// Collection of views |
408
|
|
|
$views = &$GLOBALS['__compressor_files']; |
409
|
2 |
|
// Trying to find another template path, by default it's an default template path |
410
|
2 |
|
if (isset($views[$templateView])) { |
411
|
2 |
|
eval(' ?>' . $views[$templateView] . '<?php '); |
412
|
|
|
} elseif (isset($views[$view])) { |
413
|
|
|
// If another template wasn't found - we will use default template path |
414
|
2 |
|
eval(' ?>' . $views[$view] . '<?php '); |
415
|
|
|
} else { // Error no template view was found |
416
|
|
|
throw(new ViewPathNotFound($view)); |
417
|
|
|
} |
418
|
|
|
break; |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
// Получим данные из буффера вывода |
422
|
|
|
$html = ob_get_contents(); |
423
|
|
|
|
424
|
|
|
// Очистим буффер |
425
|
|
|
ob_end_clean(); |
426
|
|
|
|
427
|
|
|
// Fire core render event |
428
|
|
|
Event::fire('core.render', array(&$html, &$data, &$this->active)); |
429
|
|
|
|
430
|
|
|
////elapsed('End rendering '.$__view); |
431
|
|
|
return $html; |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
//[PHPCOMPRESSOR(remove,start)] |
435
|
|
|
|
436
|
|
|
/** |
437
|
|
|
* Load system from composer.json |
438
|
|
|
* @param string $dependencyFilePath Path to dependencies file |
439
|
|
|
* @return $this Chaining |
440
|
|
|
*/ |
441
|
|
|
public function composer($dependencyFilePath = null) |
442
|
|
|
{ |
443
|
|
|
$composerModules = array(); |
444
|
|
|
|
445
|
|
|
Event::fire( |
446
|
|
|
'core.composer.create', |
447
|
|
|
array( |
448
|
|
|
&$composerModules, |
449
|
|
|
isset($dependencyFilePath) ? $dependencyFilePath : $this->system_path, |
|
|
|
|
450
|
|
|
array( |
451
|
|
|
'vendorsList' => array('samsonphp/', 'samsonos/', 'samsoncms/', 'samsonjavascript/'), |
452
|
|
|
'ignoreKey' => 'samson_module_ignore', |
453
|
|
|
'includeKey' => 'samson_module_include' |
454
|
|
|
) |
455
|
|
|
) |
456
|
|
|
); |
457
|
|
|
|
458
|
|
|
$modulesToLoad = []; |
459
|
|
|
|
460
|
|
|
// Iterate requirements |
461
|
|
|
foreach ($composerModules as $requirement => $parameters) { |
462
|
|
|
$moduleName = $this->load(__SAMSON_CWD__ . __SAMSON_VENDOR_PATH . $requirement, |
463
|
|
|
array_merge( |
464
|
|
|
is_array($parameters) ? $parameters : array($parameters), |
465
|
|
|
array('module_id' => $requirement) |
466
|
|
|
)); |
467
|
|
|
|
468
|
|
|
$modulesToLoad[$moduleName] = $parameters; |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
$localModulesPath = '../src'; |
472
|
|
|
ResourceMap::get('cache'); |
473
|
|
|
// TODO: Nested modules relation |
474
|
|
|
for ($i = 0; $i < 2; $i++) { |
475
|
|
|
$resourceMap = ResourceMap::get($localModulesPath); |
476
|
|
|
|
477
|
|
|
foreach ($resourceMap->modules as $moduleFile) { |
478
|
|
|
$modulePath = str_replace(realpath($localModulesPath), '', $moduleFile[1]); |
479
|
|
|
$modulePath = explode('/', $modulePath); |
480
|
|
|
$modulePath = $localModulesPath . '/' . $modulePath[1]; |
481
|
|
|
$moduleName = $this->load($modulePath, $parameters); |
|
|
|
|
482
|
|
|
$modulesToLoad[$moduleName] = $parameters; |
483
|
|
|
} |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
//$this->active = new VirtualModule($this->system_path, $this->map, $this, 'local'); |
487
|
|
|
|
488
|
|
|
// Create local module and set it as active |
489
|
|
|
$this->createMetadata(VirtualModule::class, 'local', $this->system_path); |
|
|
|
|
490
|
|
|
|
491
|
|
|
// TODO: This should be changed to one single logic |
492
|
|
|
// Require all local module model files |
493
|
|
|
foreach ($this->map->models as $model) { |
|
|
|
|
494
|
|
|
// TODO: Why have to require once? |
495
|
|
|
require_once($model); |
496
|
|
|
} |
497
|
|
|
|
498
|
|
|
// Create all local modules |
499
|
|
|
foreach ($this->map->controllers as $controller) { |
|
|
|
|
500
|
|
|
// Require class into PHP |
501
|
|
|
require($controller); |
502
|
|
|
|
503
|
|
|
//new VirtualModule($this->system_path, $this->map, $this, basename($controller, '.php')); |
504
|
|
|
|
505
|
|
|
$this->createMetadata(VirtualModule::class, basename($controller, '.php'), $this->system_path); |
|
|
|
|
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
$metadata = new ClassMetadata(); |
509
|
|
|
$metadata->className = get_class($this); |
510
|
|
|
$metadata->name = 'core'; |
511
|
|
|
$metadata->scopes[] = Builder::SCOPE_SERVICES; |
512
|
|
|
$metadata->methodsMetadata['__construct'] = new MethodMetadata($metadata); |
513
|
|
|
$metadata->methodsMetadata['__construct']->dependencies['map'] = ResourceMap::class; |
514
|
|
|
|
515
|
|
|
$this->metadataCollection[$metadata->name] = $metadata; |
516
|
|
|
|
517
|
|
|
$metadata = new ClassMetadata(); |
518
|
|
|
$metadata->className = ResourceMap::class; |
519
|
|
|
$metadata->name = 'resource_map'; |
520
|
|
|
$metadata->scopes[] = Builder::SCOPE_SERVICES; |
521
|
|
|
|
522
|
|
|
$this->metadataCollection[$metadata->name] = $metadata; |
523
|
|
|
|
524
|
|
|
// Load annotations |
525
|
|
|
$classes = []; |
526
|
|
|
foreach ($this->metadataCollection as $alias => $metadata) { |
527
|
|
|
$classes[$alias] = $metadata->className; |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
// Load annotation and parse classes |
531
|
|
|
new Injectable(); |
532
|
|
|
new InjectArgument(['var' => 'type']); |
533
|
|
|
|
534
|
|
|
$reader = new AnnotationReader(); |
535
|
|
|
$resolver = new AnnotationResolver( |
536
|
|
|
new AnnotationClassResolver($reader), |
537
|
|
|
new AnnotationPropertyResolver($reader), |
538
|
|
|
new AnnotationMethodResolver($reader) |
539
|
|
|
); |
540
|
|
|
$annotationCollector = new AnnotationMetadataCollector($resolver); |
541
|
|
|
$this->metadataCollection = $annotationCollector->collect($classes, $this->metadataCollection); |
542
|
|
|
|
543
|
|
|
// Gather all interface implementations |
544
|
|
|
$implementsByAlias = []; |
545
|
|
|
foreach (get_declared_interfaces() as $interface) { |
546
|
|
|
foreach (get_declared_classes() as $class) { |
547
|
|
|
if (in_array($interface, class_implements($class), true)) { |
548
|
|
|
// TODO: We should already have metadata by classname collection |
549
|
|
|
foreach ($this->metadataCollection as $alias => $metadata) { |
550
|
|
|
if ($metadata->className === $class) { |
551
|
|
|
$implementsByAlias['\\'.$interface][] = $alias; |
552
|
|
|
break; |
553
|
|
|
} |
554
|
|
|
} |
555
|
|
|
} |
556
|
|
|
} |
557
|
|
|
} |
558
|
7 |
|
|
559
|
|
|
/** |
560
|
|
|
* TODO: now we need to implement not forcing to load fixed dependencies into modules |
561
|
7 |
|
* to give ability to change constructors and inject old variable into properties |
562
|
|
|
* and them after refactoring remove them. With this we can only specify needed dependencies |
563
|
|
|
* in new modules, and still have old ones working. |
564
|
7 |
|
*/ |
565
|
|
|
|
566
|
|
|
foreach ($this->metadataCollection as $alias => $metadata) { |
567
|
7 |
|
foreach ($metadata->propertiesMetadata as $property => $propertyMetadata) { |
568
|
|
|
if (array_key_exists($propertyMetadata->dependency, $implementsByAlias)) { |
569
|
|
|
$propertyMetadata->dependency = $implementsByAlias[$propertyMetadata->dependency][0]; |
570
|
7 |
|
} |
571
|
|
|
} |
572
|
|
|
foreach ($metadata->methodsMetadata as $method => $methodMetadata) { |
573
|
7 |
|
foreach ($methodMetadata->dependencies as $argument => $dependency) { |
574
|
|
|
if (array_key_exists($dependency, $implementsByAlias)) { |
575
|
|
|
$methodMetadata->dependencies[$argument] = $implementsByAlias[$dependency][0]; |
576
|
7 |
|
} |
577
|
7 |
|
} |
578
|
|
|
} |
579
|
|
|
} |
580
|
|
|
|
581
|
|
|
// Load container class |
582
|
|
|
$containerPath = $this->path().'www/cache/Container.php'; |
583
|
|
|
file_put_contents($containerPath, $this->builder->build($this->metadataCollection)); |
584
|
|
|
require_once($containerPath); |
585
|
|
|
|
586
|
|
|
// Inject current core into container |
587
|
|
|
$this->container = new \Container(); |
|
|
|
|
588
|
|
|
$containerReflection = new \ReflectionClass(get_class($this->container)); |
589
|
|
|
$serviceProperty = $containerReflection->getProperty(Builder::DI_FUNCTION_SERVICES); |
590
|
|
|
$serviceProperty->setAccessible(true); |
591
|
|
|
$containerServices = $serviceProperty->getValue($this->container); |
592
|
|
|
$containerServices['core'] = $this; |
593
|
|
|
$serviceProperty->setValue($this->container, $containerServices); |
594
|
|
|
$serviceProperty->setAccessible(false); |
595
|
|
|
|
596
|
|
|
foreach ($modulesToLoad as $identifier => $parameters) { |
597
|
|
|
$instance = $this->container->get($identifier); |
598
|
|
|
|
599
|
|
|
// Set composer parameters |
600
|
|
|
$instance->composerParameters = $parameters; |
601
|
|
|
|
602
|
|
|
// TODO: Change event signature to single approach |
603
|
|
|
// Fire core module load event |
604
|
|
|
Event::fire('core.module_loaded', [$identifier, &$instance]); |
605
|
|
|
|
606
|
|
|
// Signal core module configure event |
607
|
|
|
Event::signal('core.module.configure', [&$instance, $identifier]); |
608
|
|
|
|
609
|
|
|
if ($instance instanceof PreparableInterface) { |
610
|
|
|
// Call module preparation handler |
611
|
|
|
if (!$instance->prepare()) { |
612
|
|
|
//throw new \Exception($identifier.' - Module preparation stage failed'); |
613
|
|
|
} |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
// Try to set module parent module |
617
|
|
|
$instance->parent = $this->getClassParentModule(get_parent_class($instance)); |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
$this->active = $this->container->getLocal(); |
621
|
|
|
|
622
|
|
|
return $this; |
623
|
|
|
} |
624
|
|
|
|
625
|
|
|
/** |
626
|
|
|
* Find parent module by OOP class inheritance. |
627
|
|
|
* |
628
|
|
|
* @param string $className Class name for searching parent modules |
629
|
|
|
* @param array $ignoredClasses Collection of ignored classes |
630
|
|
|
* |
631
|
|
|
* @return null|mixed Parent service instance if present |
632
|
|
|
*/ |
633
|
|
|
protected function getClassParentModule( |
634
|
|
|
$className, |
635
|
|
|
array $ignoredClasses = [ExternalModule::class, CompressableExternalModule::class, Service::class, CompressableService::class] |
636
|
|
|
) { |
637
|
|
|
// Skip ignored class names |
638
|
|
|
if (!in_array($className, $ignoredClasses, true)) { |
639
|
|
|
// Iterate loaded services |
640
|
|
|
foreach ($this->getContainer()->getServices('module') as $service) { |
641
|
|
|
if (get_class($service) === $className) { |
642
|
|
|
return $service; |
643
|
|
|
} |
644
|
|
|
} |
645
|
|
|
} |
646
|
|
|
|
647
|
|
|
return null; |
648
|
|
|
} |
649
|
|
|
|
650
|
|
|
/** |
651
|
|
|
* Load module from path to core. |
652
|
|
|
* |
653
|
|
|
* @param string $path Path for module loading |
654
|
|
|
* @param array $parameters Collection of loading parameters |
655
|
|
|
* |
656
|
|
|
* @return string module name |
657
|
|
|
* @throws \samsonphp\core\exception\CannotLoadModule |
658
|
|
|
*/ |
659
|
|
|
public function load($path, $parameters = array()) |
660
|
|
|
{ |
661
|
|
|
$name = ''; |
662
|
|
|
// Check path |
663
|
|
|
if (file_exists($path)) { |
664
|
|
|
/** @var ResourceMap $resourceMap Gather all resources from path */ |
665
|
|
|
$resourceMap = ResourceMap::get($path); |
666
|
|
|
if (isset($resourceMap->module[0])) { |
667
|
|
|
|
668
|
|
|
/** @var string $controllerPath Path to module controller file */ |
669
|
|
|
$controllerPath = $resourceMap->module[1]; |
670
|
|
|
|
671
|
|
|
/** @var string $moduleClass Name of module controller class to load */ |
672
|
|
|
$moduleClass = $resourceMap->module[0]; |
673
|
|
|
|
674
|
|
|
// Require module controller class into PHP |
675
|
|
|
if (file_exists($controllerPath)) { |
676
|
|
|
require_once($controllerPath); |
677
|
|
|
} |
678
|
|
|
|
679
|
|
|
// TODO: this should be done via composer autoload file field |
680
|
|
|
// Iterate all function-style controllers and require them |
681
|
|
|
foreach ($resourceMap->controllers as $controller) { |
682
|
|
|
require_once($controller); |
683
|
|
|
} |
684
|
|
|
|
685
|
|
|
$reflection = new \ReflectionClass($moduleClass); |
686
|
|
|
$name = $reflection->getDefaultProperties(); |
687
|
|
|
$name = $this->createMetadata($moduleClass, $name['id'] ?? $moduleClass, $path); |
688
|
|
|
|
689
|
|
|
/*$this->initModule( |
690
|
|
|
new $moduleClass($path, $resourceMap, $this), |
691
|
|
|
$parameters |
692
|
|
|
);*/ |
693
|
|
|
} elseif (is_array($parameters) && isset($parameters['samsonphp_package_compressable']) && ($parameters['samsonphp_package_compressable'] == 1)) { |
694
|
|
|
$name = $this->createMetadata(VirtualModule::class, $parameters['module_id'], $path); |
695
|
|
|
|
696
|
|
|
/*$this->initModule( |
697
|
|
|
new VirtualModule($path, $resourceMap, $this, str_replace('/', '', $parameters['module_id'])), |
698
|
|
|
$parameters |
699
|
|
|
);*/ |
700
|
|
|
} |
701
|
|
|
// elseif (count($resourceMap->classes)) { |
702
|
|
|
// /** Update for future version: Search classes that implement LoadableInterface */ |
703
|
|
|
// foreach ($resourceMap->classes as $classPath => $class) { |
704
|
|
|
// // This class implements LoadableInterface LoadableInterface::class |
705
|
|
|
// if (in_array('\samsonframework\core\LoadableInterface', $resourceMap->classData[$classPath]['implements'])) { |
706
|
|
|
// |
707
|
|
|
// $name = str_replace('/', '', $parameters['module_id']); |
708
|
|
|
// |
709
|
|
|
// $this->createMetadata(VirtualModule::class, str_replace('/', '', $parameters['module_id']), $path); |
710
|
|
|
// |
711
|
|
|
// /*$this->initModule( |
712
|
|
|
// new VirtualModule( |
713
|
|
|
// $path, |
714
|
|
|
// $resourceMap, |
715
|
|
|
// $this, |
716
|
|
|
// str_replace('/', '', $resourceMap->classData[$classPath]['className']) |
717
|
|
|
// ), |
718
|
|
|
// $parameters |
719
|
|
|
// );*/ |
720
|
|
|
// } |
721
|
|
|
// } |
722
|
|
|
// } |
723
|
|
|
|
|
|
|
|
724
|
|
|
} else { |
725
|
|
|
throw new CannotLoadModule($path); |
726
|
|
|
} |
727
|
|
|
|
728
|
|
|
return $name; |
729
|
|
|
} |
730
|
|
|
//[PHPCOMPRESSOR(remove,end)] |
731
|
|
|
|
732
|
|
|
/** Магический метод для десериализации объекта */ |
733
|
|
|
public function __wakeup() |
734
|
|
|
{ |
735
|
|
|
$this->active = &$this->module_stack['local']; |
736
|
|
|
} |
737
|
|
|
|
738
|
|
|
/** Магический метод для сериализации объекта */ |
739
|
|
|
public function __sleep() |
740
|
|
|
{ |
741
|
|
|
return array('module_stack', 'render_mode'); |
742
|
|
|
} |
743
|
|
|
|
744
|
|
|
protected function createMetadata($class, $name, $path, $scope = 'module') |
745
|
|
|
{ |
746
|
|
|
$metadata = new ClassMetadata(); |
747
|
|
|
$class = ltrim($class, '\\'); |
748
|
|
|
$name = strtolower(ltrim($name, '\\')); |
749
|
|
|
$metadata->className = $class; |
750
|
|
|
$metadata->name = str_replace(['\\', '/'], '_', $name ?? $class); |
751
|
|
|
$metadata->scopes[] = Builder::SCOPE_SERVICES; |
752
|
|
|
$metadata->scopes[] = $scope; |
753
|
|
|
$metadata->propertiesMetadata['system'] = new PropertyMetadata($metadata); |
754
|
|
|
$metadata->propertiesMetadata['system']->dependency = 'core'; |
755
|
|
|
$metadata->propertiesMetadata['system']->isPublic = false; |
756
|
|
|
$metadata->propertiesMetadata['resourceMap'] = new PropertyMetadata($metadata); |
757
|
|
|
$metadata->propertiesMetadata['resourceMap']->dependency = 'resource_map'; |
758
|
|
|
$metadata->propertiesMetadata['resourceMap']->isPublic = false; |
759
|
|
|
|
760
|
|
|
// TODO: Now we need to remove and change constructors |
761
|
|
|
$metadata->methodsMetadata['__construct'] = new MethodMetadata($metadata); |
762
|
|
|
$metadata->methodsMetadata['__construct']->dependencies['path'] = $path; |
763
|
|
|
$metadata->methodsMetadata['__construct']->dependencies['resources'] = 'resource_map'; |
764
|
|
|
$metadata->methodsMetadata['__construct']->dependencies['system'] = 'core'; |
765
|
|
|
|
766
|
|
|
$this->metadataCollection[$metadata->name] = $metadata; |
767
|
|
|
|
768
|
|
|
return $metadata->name; |
769
|
|
|
} |
770
|
|
|
} |
771
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.