Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Core often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Core, and based on these observations, apply Extract Interface, too.
| 1 | <?php declare(strict_types=1); |
||
| 41 | class Core implements SystemInterface |
||
| 42 | { |
||
| 43 | /** @var ContainerInterface */ |
||
| 44 | protected $container; |
||
| 45 | |||
| 46 | /** @var ClassMetadata[] */ |
||
| 47 | protected $metadataCollection = []; |
||
| 48 | |||
| 49 | /** @var ContainerBuilderInterface */ |
||
| 50 | protected $builder; |
||
| 51 | |||
| 52 | /** @var string Current system environment */ |
||
| 53 | protected $environment; |
||
| 54 | |||
| 55 | /* Rendering models */ |
||
| 56 | /** @deprecated Standard algorithm for view rendering */ |
||
| 57 | const RENDER_STANDART = 1; |
||
| 58 | /** @deprecated View rendering algorithm from array of view variables */ |
||
| 59 | const RENDER_VARIABLE = 3; |
||
| 60 | |||
| 61 | /** @deprecated @var ResourceMap Current web-application resource map */ |
||
| 62 | public $map; |
||
| 63 | |||
| 64 | protected $classes = []; |
||
| 65 | |||
| 66 | /** @deprecated @var string Path to current web-application */ |
||
| 67 | public $system_path = __SAMSON_CWD__; |
||
| 68 | /** @deprecated @var string View path loading mode */ |
||
| 69 | public $render_mode = self::RENDER_STANDART; |
||
| 70 | /** @var Module Pointer to current active module */ |
||
| 71 | protected $active = null; |
||
| 72 | /** @var bool Flag for outputting layout template, used for asynchronous requests */ |
||
| 73 | protected $async = false; |
||
| 74 | /** @var string Path to main system template */ |
||
| 75 | protected $template_path = __SAMSON_DEFAULT_TEMPLATE; |
||
| 76 | |||
| 77 | /** @return \Container Get system container */ |
||
| 78 | public function getContainer() |
||
| 79 | { |
||
| 80 | return $this->container; |
||
| 81 | } |
||
| 82 | |||
| 83 | /** |
||
| 84 | * Core constructor. |
||
| 85 | * |
||
| 86 | * @param ContainerBuilderInterface $builder Container builder |
||
| 87 | * @param ResourceMap|null $map system resources |
||
| 88 | * @InjectArgument(builder="\samsonframework\container\ContainerBuilderInterface") |
||
| 89 | * @InjectArgument(builder="\samsonframework\core\ResourceInterface") |
||
| 90 | */ |
||
| 91 | public function __construct(ContainerBuilderInterface $builder, ResourceMap $map) |
||
| 92 | { |
||
| 93 | $this->builder = $builder; |
||
| 94 | // Get correct web-application path |
||
| 95 | $this->system_path = __SAMSON_CWD__; |
||
| 96 | |||
| 97 | // Get web-application resource map |
||
| 98 | $this->map = ResourceMap::get($this->system_path, false, array('src/')); |
||
| 99 | |||
| 100 | // Temporary add template worker |
||
| 101 | $this->subscribe('core.rendered', array($this, 'generateTemplate')); |
||
| 102 | |||
| 103 | // Fire core creation event |
||
| 104 | Event::fire('core.created', array(&$this)); |
||
| 105 | |||
| 106 | // Signal core configure event |
||
| 107 | Event::signal('core.configure', array($this->system_path . __SAMSON_CONFIG_PATH)); |
||
| 108 | } |
||
| 109 | |||
| 110 | 1 | /** |
|
| 111 | * Generic wrap for Event system subscription. |
||
| 112 | * @see \samson\core\\samsonphp\event\Event::subscribe() |
||
| 113 | * |
||
| 114 | * @param string $key Event identifier |
||
| 115 | * @param callable $handler Event handler |
||
| 116 | * @param array $params Event parameters |
||
| 117 | * |
||
| 118 | * @return $this Chaining |
||
| 119 | */ |
||
| 120 | public function subscribe($key, $handler, $params = array()) |
||
| 121 | 1 | { |
|
| 122 | Event::subscribe($key, $handler, $params); |
||
| 123 | |||
| 124 | return $this; |
||
| 125 | } |
||
| 126 | |||
| 127 | /** |
||
| 128 | * Change current system working environment or receive |
||
| 129 | * current system enviroment if no arguments are passed. |
||
| 130 | * |
||
| 131 | * @param string $environment Environment identifier |
||
| 132 | * |
||
| 133 | * TODO: Function has two different logics - needs to be changed! |
||
| 134 | * @return $this|string Chaining or current system environment |
||
| 135 | */ |
||
| 136 | public function environment($environment = Scheme::BASE) |
||
| 137 | { |
||
| 138 | if (func_num_args() !== 0) { |
||
| 139 | $this->environment = $environment; |
||
| 140 | |||
| 141 | // Signal core environment change |
||
| 142 | Event::signal('core.environment.change', array($environment, &$this)); |
||
| 143 | return $this; |
||
| 144 | } |
||
| 145 | |||
| 146 | return $this->environment; |
||
| 147 | } |
||
| 148 | |||
| 149 | /** |
||
| 150 | * Generate special response header triggering caching mechanisms |
||
| 151 | * @param int $cacheLife Amount of seconds for cache(default 3600 - 1 hour) |
||
| 152 | * @param string $accessibility Cache-control accessibility value(default public) |
||
| 153 | */ |
||
| 154 | public function cached($cacheLife = 3600, $accessibility = 'public') |
||
| 155 | { |
||
| 156 | static $cached; |
||
| 157 | // Protect sending cached headers once |
||
| 158 | if (!isset($cached) or $cached !== true) { |
||
| 159 | header('Expires: ' . gmdate('D, d M Y H:i:s T', time() + $cacheLife)); |
||
| 160 | header('Cache-Control: ' . $accessibility . ', max-age=' . $cacheLife); |
||
| 161 | header('Pragma: cache'); |
||
| 162 | |||
| 163 | $cached = true; |
||
| 164 | } |
||
| 165 | } |
||
| 166 | |||
| 167 | /** |
||
| 168 | * Set asynchronous mode. |
||
| 169 | * This mode will not output template and will just path everything that |
||
| 170 | * was outputted to client. |
||
| 171 | * |
||
| 172 | * @param bool $async True to switch to asynchronous output mode |
||
| 173 | * |
||
| 174 | * @return $this Chaining |
||
| 175 | */ |
||
| 176 | public function async($async) |
||
| 177 | { |
||
| 178 | $this->async = $async; |
||
| 179 | |||
| 180 | return $this; |
||
| 181 | } |
||
| 182 | |||
| 183 | /** @see iCore::path() */ |
||
| 184 | public function path($path = null) |
||
| 185 | { |
||
| 186 | // Если передан аргумент |
||
| 187 | if (func_num_args()) { |
||
| 188 | // Сформируем новый относительный путь к главному шаблону системы |
||
| 189 | $this->template_path = $path . $this->template_path; |
||
| 190 | |||
| 191 | // Сохраним относительный путь к Веб-приложению |
||
| 192 | $this->system_path = $path; |
||
| 193 | |||
| 194 | // Продолжил цепирование |
||
| 195 | return $this; |
||
| 196 | } |
||
| 197 | |||
| 198 | // Вернем текущее значение |
||
| 199 | return $this->system_path; |
||
| 200 | } |
||
| 201 | |||
| 202 | /** @see iModule::active() */ |
||
| 203 | public function &active(&$module = null) |
||
| 204 | { |
||
| 205 | // Сохраним старый текущий модуль |
||
| 206 | $old = &$this->active; |
||
| 207 | |||
| 208 | // Если передано значение модуля для установки как текущий - проверим и установим его |
||
| 209 | if (isset($module)) { |
||
| 210 | $this->active = &$module; |
||
| 211 | } |
||
| 212 | |||
| 213 | // Вернем значение текущего модуля |
||
| 214 | return $old; |
||
| 215 | } |
||
| 216 | |||
| 217 | /** |
||
| 218 | * Retrieve module instance by identifier. |
||
| 219 | * |
||
| 220 | * @param string|null $module Module identifier |
||
| 221 | * |
||
| 222 | * @return null|Module Found or active module |
||
| 223 | */ |
||
| 224 | public function &module($module = null) |
||
| 225 | { |
||
| 226 | $return = null; |
||
| 227 | |||
| 228 | // Ничего не передано - вернем текущуй модуль системы |
||
| 229 | if (!isset($module) && isset($this->active)) { |
||
| 230 | $return = &$this->active; |
||
| 231 | } elseif (is_object($module)) { |
||
| 232 | $return = &$module; |
||
| 233 | } elseif (is_string($module)) { |
||
| 234 | $return = $this->container->get($module); |
||
| 235 | } |
||
| 236 | |||
| 237 | // Ничего не получилось вернем ошибку |
||
| 238 | if ($return === null) { |
||
| 239 | e('Не возможно получить модуль(##) системы', E_SAMSON_CORE_ERROR, array($module)); |
||
| 240 | } |
||
| 241 | |||
| 242 | return $return; |
||
| 243 | } |
||
| 244 | |||
| 245 | /** |
||
| 246 | * Unload module from core. |
||
| 247 | * |
||
| 248 | * @param string $moduleID Module identifier |
||
| 249 | */ |
||
| 250 | public function unload($moduleID) |
||
| 251 | { |
||
| 252 | if (isset($this->module_stack[$moduleID])) { |
||
| 253 | unset($this->module_stack[$moduleID]); |
||
| 254 | } |
||
| 255 | } |
||
| 256 | |||
| 257 | /** |
||
| 258 | * Insert generic html template tags and data |
||
| 259 | * |
||
| 260 | * @param string $templateHtml Generated HTML |
||
| 261 | * |
||
| 262 | * @deprecated Must be moved to a new HTML output object |
||
| 263 | * @return mixed Changed HTML template |
||
| 264 | 2 | */ |
|
| 265 | public function generateTemplate(&$templateHtml) |
||
| 266 | { |
||
| 267 | // Добавим путь к ресурсам для браузера |
||
| 268 | $headHtml = "\n" . '<base href="' . url()->base() . '">'; |
||
| 269 | // Добавим отметку времени для JavaScript |
||
| 270 | $headHtml .= "\n" . '<script type="text/javascript">var __SAMSONPHP_STARTED = new Date().getTime();</script>'; |
||
| 271 | 2 | ||
| 272 | // Добавим поддержку HTML для старых IE |
||
| 273 | $headHtml .= "\n" . '<!--[if lt IE 9]>'; |
||
| 274 | 2 | $headHtml .= "\n" . '<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>'; |
|
| 275 | $headHtml .= "\n" . '<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>'; |
||
| 276 | $headHtml .= "\n" . '<![endif]-->'; |
||
| 277 | |||
| 278 | 2 | // Выполним вставку главного тега <base> от которого зависят все ссылки документа |
|
| 279 | // также подставим МЕТА-теги для текущего модуля и сгенерированный минифицированный CSS |
||
| 280 | 2 | $templateHtml = str_ireplace('<head>', '<head>' . $headHtml, $templateHtml); |
|
| 281 | |||
| 282 | 2 | // Вставим указатель JavaScript ресурсы в конец HTML документа |
|
| 283 | 2 | $templateHtml = str_ireplace('</html>', '</html>' . __SAMSON_COPYRIGHT, $templateHtml); |
|
| 284 | |||
| 285 | return $templateHtml; |
||
| 286 | 2 | } |
|
| 287 | |||
| 288 | 2 | /** |
|
| 289 | * Start SamsonPHP framework. |
||
| 290 | 2 | * |
|
| 291 | * @param string $default Default module identifier |
||
| 292 | * |
||
| 293 | * @throws ViewPathNotFound |
||
| 294 | */ |
||
| 295 | 2 | public function start($default) |
|
| 296 | { |
||
| 297 | // TODO: Change ExternalModule::init() signature |
||
| 298 | // Fire core started event |
||
| 299 | Event::fire('core.started'); |
||
| 300 | |||
| 301 | // TODO: Does not see why it should be here |
||
| 302 | // Set main template path |
||
| 303 | $this->template($this->template_path); |
||
| 304 | |||
| 305 | // Security layer |
||
| 306 | $securityResult = true; |
||
| 307 | // Fire core security event |
||
| 308 | Event::fire('core.security', array(&$this, &$securityResult)); |
||
| 309 | |||
| 310 | /** @var mixed $result External route controller action result */ |
||
| 311 | $result = false; |
||
| 312 | |||
| 313 | // If we have passed security application layer |
||
| 314 | if ($securityResult) { |
||
| 315 | // Fire core routing event - go to routing application layer |
||
| 316 | Event::signal('core.routing', array(&$this, &$result, $default)); |
||
| 317 | } |
||
| 318 | |||
| 319 | // If no one has passed back routing callback |
||
| 320 | 2 | if (!isset($result) || $result === false) { |
|
| 321 | // Fire core e404 - routing failed event |
||
| 322 | $result = Event::signal('core.e404', array(url()->module, url()->method)); |
||
| 323 | 2 | } |
|
| 324 | |||
| 325 | // Response |
||
| 326 | 2 | $output = ''; |
|
| 327 | |||
| 328 | // If this is not asynchronous response and controller has been executed |
||
| 329 | 2 | if (!$this->async && ($result !== false)) { |
|
| 330 | // Store module data |
||
| 331 | $data = $this->active->toView(); |
||
| 332 | 2 | ||
| 333 | // Render main template |
||
| 334 | $output = $this->render($this->template_path, $data); |
||
| 335 | 2 | ||
| 336 | // Fire after render event |
||
| 337 | Event::fire('core.rendered', array(&$output)); |
||
| 338 | 2 | } |
|
| 339 | |||
| 340 | // Output results to client |
||
| 341 | echo $output; |
||
| 342 | |||
| 343 | // Fire ended event |
||
| 344 | Event::fire('core.ended', array(&$output)); |
||
| 345 | } |
||
| 346 | |||
| 347 | /** @see iCore::template() */ |
||
| 348 | public function template( $template = NULL, $absolutePath = false ) |
||
| 349 | { |
||
| 350 | // Если передан аргумент |
||
| 351 | if( func_num_args() ){ |
||
| 352 | 7 | $this->template_path = ($absolutePath)?$template:$this->active->path().$template; |
|
| 353 | } |
||
| 354 | 7 | ||
| 355 | // Аргументы не переданы - вернем текущий путь к шаблону системы |
||
| 356 | 7 | return $this->template_path; |
|
| 357 | } |
||
| 358 | |||
| 359 | /** |
||
| 360 | * Render file to a buffer. |
||
| 361 | * |
||
| 362 | * @param string $view Path to file |
||
| 363 | * @param array $data Collection of variables to path to file |
||
| 364 | * |
||
| 365 | * @return string Rendered file contents |
||
| 366 | * @throws ViewPathNotFound |
||
| 367 | */ |
||
| 368 | public function render($view, $data = array()) |
||
| 369 | { |
||
| 370 | // TODO: Make rendering as external system, to split up these 3 rendering options |
||
| 371 | |||
| 372 | // Объявить ассоциативный массив переменных в данном контексте |
||
| 373 | if (is_array($data)) { |
||
| 374 | extract($data); |
||
| 375 | } |
||
| 376 | |||
| 377 | // Начать вывод в буффер |
||
| 378 | ob_start(); |
||
| 379 | |||
| 380 | // Path to another template view, by default we are using default template folder path, |
||
| 381 | // for meeting first condition |
||
| 382 | $templateView = $view; |
||
| 383 | |||
| 384 | if (locale() != SamsonLocale::DEF) { |
||
| 385 | // Modify standard view path with another template |
||
| 386 | $templateView = str_replace(__SAMSON_VIEW_PATH, __SAMSON_VIEW_PATH . locale() . '/', $templateView); |
||
| 387 | } |
||
| 388 | |||
| 389 | // Depending on core view rendering model |
||
| 390 | switch ($this->render_mode) { |
||
| 391 | // Standard algorithm for view rendering |
||
| 392 | case self::RENDER_STANDART: |
||
| 393 | // Trying to find another template path, by default it's an default template path |
||
| 394 | if (file_exists($templateView)) { |
||
| 395 | include($templateView); |
||
| 396 | } elseif (file_exists($view)) { |
||
| 397 | // If another template wasn't found - we will use default template path |
||
| 398 | include($view); |
||
| 399 | } else { // Error no template view was found |
||
| 400 | throw(new ViewPathNotFound($view)); |
||
| 401 | } |
||
| 402 | break; |
||
| 403 | 2 | ||
| 404 | // View rendering algorithm from array of view variables |
||
| 405 | case self::RENDER_VARIABLE: |
||
| 406 | 2 | // Collection of views |
|
| 407 | $views = &$GLOBALS['__compressor_files']; |
||
| 408 | // Trying to find another template path, by default it's an default template path |
||
| 409 | 2 | if (isset($views[$templateView])) { |
|
| 410 | 2 | eval(' ?>' . $views[$templateView] . '<?php '); |
|
| 411 | 2 | } elseif (isset($views[$view])) { |
|
| 412 | // If another template wasn't found - we will use default template path |
||
| 413 | eval(' ?>' . $views[$view] . '<?php '); |
||
| 414 | 2 | } else { // Error no template view was found |
|
| 415 | throw(new ViewPathNotFound($view)); |
||
| 416 | } |
||
| 417 | break; |
||
| 418 | } |
||
| 419 | |||
| 420 | // Получим данные из буффера вывода |
||
| 421 | $html = ob_get_contents(); |
||
| 422 | |||
| 423 | // Очистим буффер |
||
| 424 | ob_end_clean(); |
||
| 425 | |||
| 426 | // Fire core render event |
||
| 427 | Event::fire('core.render', array(&$html, &$data, &$this->active)); |
||
| 428 | |||
| 429 | ////elapsed('End rendering '.$__view); |
||
| 430 | return $html; |
||
| 431 | } |
||
| 432 | |||
| 433 | //[PHPCOMPRESSOR(remove,start)] |
||
| 434 | |||
| 435 | /** |
||
| 436 | * Load system from composer.json |
||
| 437 | * @param string $dependencyFilePath Path to dependencies file |
||
| 438 | * @return $this Chaining |
||
| 439 | */ |
||
| 440 | public function composer($dependencyFilePath = null) |
||
| 441 | { |
||
| 442 | $composerModules = array(); |
||
| 443 | |||
| 444 | Event::fire( |
||
| 445 | 'core.composer.create', |
||
| 446 | array( |
||
| 447 | &$composerModules, |
||
| 448 | isset($dependencyFilePath) ? $dependencyFilePath : $this->system_path, |
||
| 449 | array( |
||
| 450 | 'vendorsList' => array('samsonphp/', 'samsonos/', 'samsoncms/', 'samsonjavascript/'), |
||
| 451 | 'ignoreKey' => 'samson_module_ignore', |
||
| 452 | 'includeKey' => 'samson_module_include' |
||
| 453 | ) |
||
| 454 | ) |
||
| 455 | ); |
||
| 456 | |||
| 457 | $modulesToLoad = []; |
||
| 458 | $preClasses = [ |
||
| 459 | str_replace(['\\', '/'], '_', __CLASS__) => __CLASS__, |
||
| 460 | str_replace(['\\', '/'], '_', ResourceMap::class) => ResourceMap::class |
||
| 461 | ]; |
||
| 462 | // $this->classes = array_merge($this->classes, $preClasses); |
||
| 463 | $preModules = []; |
||
| 464 | |||
| 465 | // Iterate requirements |
||
| 466 | foreach ($composerModules as $requirement => $parameters) { |
||
| 467 | $modulePath = __SAMSON_CWD__ . __SAMSON_VENDOR_PATH . $requirement; |
||
| 468 | $moduleName = $this->load($modulePath, |
||
| 469 | array_merge( |
||
| 470 | is_array($parameters) ? $parameters : array($parameters), |
||
| 471 | array('module_id' => $requirement) |
||
| 472 | )); |
||
| 473 | if (array_key_exists('samsonframework_precontainer', $parameters)) { |
||
| 474 | $preModules[$moduleName] = $parameters; |
||
| 475 | foreach (ResourceMap::get($modulePath)->classes as $className) { |
||
| 476 | //$preClasses[str_replace(['\\', '/'], '_', $className)] = $className; |
||
| 477 | } |
||
| 478 | } |
||
| 479 | $modulesToLoad[$moduleName] = $parameters; |
||
| 480 | } |
||
| 481 | |||
| 482 | // $metadata = new ClassMetadata(); |
||
| 483 | // $metadata->className = get_class($this); |
||
| 484 | // $metadata->name = 'core'; |
||
| 485 | // $metadata->scopes[] = Builder::SCOPE_SERVICES; |
||
| 486 | // $metadata->methodsMetadata['__construct'] = new MethodMetadata($metadata); |
||
| 487 | // $metadata->methodsMetadata['__construct']->dependencies['map'] = 'resource_map'; |
||
| 488 | // $this->metadataCollection[$metadata->name] = $metadata; |
||
| 489 | |||
| 490 | // $metadata = new ClassMetadata(); |
||
| 491 | // $metadata->className = ResourceMap::class; |
||
| 492 | // $metadata->name = 'resource_map'; |
||
| 493 | // $metadata->scopes[] = Builder::SCOPE_SERVICES; |
||
| 494 | // $this->metadataCollection[$metadata->name] = $metadata; |
||
| 495 | |||
| 496 | // Create local module and set it as active |
||
| 497 | $this->createMetadata(VirtualModule::class, 'local', $this->system_path); |
||
| 498 | |||
| 499 | // TODO: This should be changed to one single logic |
||
| 500 | // Require all local module model files |
||
| 501 | foreach ($this->map->models as $model) { |
||
| 502 | // TODO: Why have to require once? |
||
| 503 | require_once($model); |
||
| 504 | } |
||
| 505 | |||
| 506 | // Create all local modules |
||
| 507 | foreach ($this->map->controllers as $controller) { |
||
| 508 | // Require class into PHP |
||
| 509 | require($controller); |
||
| 510 | |||
| 511 | //new VirtualModule($this->system_path, $this->map, $this, basename($controller, '.php')); |
||
| 512 | |||
| 513 | $this->createMetadata(VirtualModule::class, basename($controller, '.php'), $this->system_path); |
||
| 514 | } |
||
| 515 | |||
| 516 | $localModulesPath = '../src'; |
||
| 517 | ResourceMap::get('cache'); |
||
| 518 | // TODO: Nested modules relation |
||
| 519 | for ($i = 0; $i < 2; $i++) { |
||
| 520 | $resourceMap = ResourceMap::get($localModulesPath); |
||
| 521 | |||
| 522 | foreach ($resourceMap->modules as $moduleFile) { |
||
| 523 | $modulePath = str_replace(realpath($localModulesPath), '', $moduleFile[1]); |
||
| 524 | $modulePath = explode('/', $modulePath); |
||
| 525 | $modulePath = $localModulesPath . '/' . $modulePath[1]; |
||
| 526 | $moduleName = $this->load($modulePath, $parameters); |
||
| 527 | $modulesToLoad[$moduleName] = $parameters; |
||
| 528 | } |
||
| 529 | } |
||
| 530 | |||
| 531 | $preMetadataCollection = []; |
||
| 532 | foreach ($preModules as $moduleName => $parameters) { |
||
| 533 | $preMetadataCollection[$moduleName] = $this->metadataCollection[$moduleName]; |
||
| 534 | } |
||
| 535 | //$preMetadataCollection['core'] = $this->metadataCollection['core']; |
||
| 536 | //$preMetadataCollection['resource_map'] = $this->metadataCollection['resource_map']; |
||
| 537 | |||
| 538 | $preContainer = $this->loadMetadata($preMetadataCollection, $preModules, $preClasses, 'ContainerPreLoad'); |
||
| 539 | /** @var ContainerInterface container */ |
||
| 540 | $this->container = $this->loadMetadata($this->metadataCollection, $modulesToLoad, $this->classes, 'Container', $preContainer); |
||
| 541 | |||
| 542 | $this->active = $this->container->getLocal(); |
||
| 543 | |||
| 544 | return $this; |
||
| 545 | } |
||
| 546 | |||
| 547 | /** |
||
| 548 | * @param ClassMetadata[] $metadataCollection |
||
| 549 | * @param array $modulesToLoad |
||
| 550 | * @param array $classes |
||
| 551 | * @param string $containerName |
||
| 552 | * @param ContainerInterface $parentContainer |
||
| 553 | * @return ContainerInterface |
||
| 554 | */ |
||
| 555 | protected function loadMetadata(array $metadataCollection, array $modulesToLoad, array $classes, $containerName = 'Container', ContainerInterface $parentContainer = null) : ContainerInterface |
||
| 556 | { |
||
| 557 | static $implementsByAlias; |
||
| 558 | 7 | static $serviceAliasesByClass; |
|
| 559 | |||
| 560 | // Load annotation and parse classes |
||
| 561 | 7 | new Injectable(); |
|
| 562 | new InjectArgument(['var' => 'type']); |
||
| 563 | new Service(['value' => '']); |
||
| 564 | 7 | ||
| 565 | $reader = new AnnotationReader(); |
||
| 566 | $resolver = new AnnotationResolver( |
||
| 567 | 7 | new AnnotationClassResolver($reader), |
|
| 568 | new AnnotationPropertyResolver($reader), |
||
| 569 | new AnnotationMethodResolver($reader) |
||
| 570 | 7 | ); |
|
| 571 | $annotationCollector = new AnnotationMetadataCollector($resolver); |
||
| 572 | $metadataCollection = $annotationCollector->collect($classes, $metadataCollection); |
||
| 573 | 7 | ||
| 574 | // Regroup classes metadata by class name instead of alias |
||
| 575 | $classesMetadata = []; |
||
| 576 | 7 | foreach ($metadataCollection as $alias => $classMetadata) { |
|
| 577 | 7 | $classesMetadata[$classMetadata->className] = $classMetadata; |
|
| 578 | } |
||
| 579 | |||
| 580 | // Gather all interface implementations |
||
| 581 | $implementsByAlias = $implementsByAlias ?? []; |
||
| 582 | foreach (get_declared_classes() as $class) { |
||
| 583 | $classImplements = class_implements($class); |
||
| 584 | foreach (get_declared_interfaces() as $interface) { |
||
| 585 | if (in_array($interface, $classImplements, true)) { |
||
| 586 | if (array_key_exists($class, $classesMetadata)) { |
||
| 587 | $implementsByAlias[strtolower('\\'.$interface)][] = $classesMetadata[$class]->name; |
||
| 588 | } |
||
| 589 | } |
||
| 590 | } |
||
| 591 | } |
||
| 592 | |||
| 593 | // Gather all class implementations |
||
| 594 | $serviceAliasesByClass = $serviceAliasesByClass ?? []; |
||
| 595 | foreach (get_declared_classes() as $class) { |
||
| 596 | if (array_key_exists($class, $classesMetadata)) { |
||
| 597 | $serviceAliasesByClass[strtolower('\\' . $class)][] = $classesMetadata[$class]->name; |
||
| 598 | } |
||
| 599 | } |
||
| 600 | |||
| 601 | /** |
||
| 602 | * TODO: now we need to implement not forcing to load fixed dependencies into modules |
||
| 603 | * to give ability to change constructors and inject old variable into properties |
||
| 604 | * and them after refactoring remove them. With this we can only specify needed dependencies |
||
| 605 | * in new modules, and still have old ones working. |
||
| 606 | */ |
||
| 607 | |||
| 608 | foreach ($metadataCollection as $alias => $metadata) { |
||
| 609 | View Code Duplication | foreach ($metadata->propertiesMetadata as $property => $propertyMetadata) { |
|
| 610 | $dependency = strtolower($propertyMetadata->dependency); |
||
| 611 | if (array_key_exists($dependency, $implementsByAlias)) { |
||
| 612 | $propertyMetadata->dependency = $implementsByAlias[$dependency][0]; |
||
| 613 | } elseif (array_key_exists($dependency, $serviceAliasesByClass)) { |
||
| 614 | $propertyMetadata->dependency = $serviceAliasesByClass[$dependency][0]; |
||
| 615 | } else { |
||
| 616 | |||
| 617 | } |
||
| 618 | } |
||
| 619 | foreach ($metadata->methodsMetadata as $method => $methodMetadata) { |
||
| 620 | View Code Duplication | foreach ($methodMetadata->dependencies as $argument => $dependency) { |
|
| 621 | $dependency = strtolower($dependency); |
||
| 622 | if (array_key_exists($dependency, $implementsByAlias)) { |
||
| 623 | $methodMetadata->dependencies[$argument] = $implementsByAlias[$dependency][0]; |
||
| 624 | //$methodMetadata->parametersMetadata[$argument]->dependency = $implementsByAlias[$dependency][0]; |
||
| 625 | } elseif (array_key_exists($dependency, $serviceAliasesByClass)) { |
||
| 626 | $methodMetadata->dependencies[$argument] = $serviceAliasesByClass[$dependency][0]; |
||
| 627 | //$methodMetadata->parametersMetadata[$argument]->dependency = $serviceAliasesByClass[$dependency][0]; |
||
| 628 | } else { |
||
| 629 | |||
| 630 | } |
||
| 631 | } |
||
| 632 | } |
||
| 633 | } |
||
| 634 | |||
| 635 | // Generate XML configs |
||
| 636 | (new XMLBuilder())->buildXMLConfig($metadataCollection, getcwd().'/cache/config_'); |
||
| 637 | |||
| 638 | // Load container class |
||
| 639 | $containerPath = $this->path().'www/cache/' . $containerName . '.php'; |
||
| 640 | file_put_contents($containerPath, $this->builder->build($metadataCollection, $containerName, '', $parentContainer)); |
||
| 641 | require_once($containerPath); |
||
| 642 | |||
| 643 | // Inject current core into container |
||
| 644 | /** @var ContainerInterface $container */ |
||
| 645 | $this->container = new $containerName(); |
||
| 646 | $containerReflection = new \ReflectionClass(get_class($this->container)); |
||
| 647 | $serviceProperty = $containerReflection->getProperty(Builder::DI_FUNCTION_SERVICES); |
||
| 648 | $serviceProperty->setAccessible(true); |
||
| 649 | $containerServices = $serviceProperty->getValue($this->container); |
||
| 650 | $containerServices['core'] = $this; |
||
| 651 | $serviceProperty->setValue($this->container, $containerServices); |
||
| 652 | $serviceProperty->setAccessible(false); |
||
| 653 | if ($parentContainer !== null) { |
||
| 654 | $this->container->delegate($parentContainer); |
||
| 655 | } |
||
| 656 | |||
| 657 | foreach ($modulesToLoad as $identifier => $parameters) { |
||
| 658 | $instance = $this->container->get($identifier); |
||
| 659 | |||
| 660 | // Set composer parameters |
||
| 661 | $instance->composerParameters = $parameters; |
||
| 662 | |||
| 663 | // TODO: Change event signature to single approach |
||
| 664 | // Fire core module load event |
||
| 665 | Event::fire('core.module_loaded', [$identifier, &$instance]); |
||
| 666 | |||
| 667 | // Signal core module configure event |
||
| 668 | Event::signal('core.module.configure', [&$instance, $identifier]); |
||
| 669 | |||
| 670 | if ($instance instanceof PreparableInterface) { |
||
| 671 | // Call module preparation handler |
||
| 672 | if (!$instance->prepare()) { |
||
| 673 | //throw new \Exception($identifier.' - Module preparation stage failed'); |
||
| 674 | } |
||
| 675 | } |
||
| 676 | |||
| 677 | // Try to set module parent module |
||
| 678 | $instance->parent = $this->getClassParentModule(get_parent_class($instance)); |
||
| 679 | } |
||
| 680 | |||
| 681 | return $this->container; |
||
| 682 | } |
||
| 683 | |||
| 684 | /** |
||
| 685 | * Find parent module by OOP class inheritance. |
||
| 686 | * |
||
| 687 | * @param string $className Class name for searching parent modules |
||
| 688 | * @param array $ignoredClasses Collection of ignored classes |
||
| 689 | * |
||
| 690 | * @return null|mixed Parent service instance if present |
||
| 691 | */ |
||
| 692 | protected function getClassParentModule( |
||
| 708 | |||
| 709 | /** |
||
| 710 | * Load module from path to core. |
||
| 711 | * |
||
| 712 | * @param string $path Path for module loading |
||
| 713 | * @param array $parameters Collection of loading parameters |
||
| 714 | * |
||
| 715 | * @return string module name |
||
| 716 | * @throws \samsonphp\core\exception\CannotLoadModule |
||
| 717 | */ |
||
| 718 | public function load($path, $parameters = array()) |
||
| 719 | { |
||
| 720 | $name = ''; |
||
| 721 | // Check path |
||
| 722 | if (file_exists($path)) { |
||
| 723 | |||
| 724 | /** @var ResourceMap $resourceMap Gather all resources from path */ |
||
| 769 | //[PHPCOMPRESSOR(remove,end)] |
||
| 770 | |||
| 771 | /** Магический метод для десериализации объекта */ |
||
| 772 | public function __wakeup() |
||
| 776 | |||
| 777 | /** Магический метод для сериализации объекта */ |
||
| 778 | public function __sleep() |
||
| 782 | |||
| 783 | protected function createMetadata($class, $name, $path, $scope = 'module') |
||
| 820 | } |
||
| 821 |
Let’s assume that you have a directory layout like this:
. |-- OtherDir | |-- Bar.php | `-- Foo.php `-- SomeDir `-- Foo.phpand let’s assume the following content of
Bar.php:If both files
OtherDir/Foo.phpandSomeDir/Foo.phpare loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.phpHowever, as
OtherDir/Foo.phpdoes not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: