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); |
||
| 51 | class Core implements SystemInterface |
||
| 52 | { |
||
| 53 | /** @deprecated Standard algorithm for view rendering */ |
||
| 54 | const RENDER_STANDART = 1; |
||
| 55 | /** @deprecated View rendering algorithm from array of view variables */ |
||
| 56 | const RENDER_VARIABLE = 3; |
||
| 57 | /** @deprecated @var ResourceMap Current web-application resource map */ |
||
| 58 | public $map; |
||
| 59 | /** @deprecated @var string Path to current web-application */ |
||
| 60 | public $system_path = __SAMSON_CWD__; |
||
| 61 | |||
| 62 | /* Rendering models */ |
||
| 63 | /** @deprecated @var string View path loading mode */ |
||
| 64 | public $render_mode = self::RENDER_STANDART; |
||
| 65 | /** @var ContainerInterface */ |
||
| 66 | protected $container; |
||
| 67 | /** @var ClassMetadata[] */ |
||
| 68 | protected $metadataCollection = []; |
||
| 69 | /** @var ContainerBuilderInterface */ |
||
| 70 | protected $builder; |
||
| 71 | /** @var string Current system environment */ |
||
| 72 | protected $environment; |
||
| 73 | protected $classes = []; |
||
| 74 | /** @var Module Pointer to current active module */ |
||
| 75 | protected $active = null; |
||
| 76 | /** @var bool Flag for outputting layout template, used for asynchronous requests */ |
||
| 77 | protected $async = false; |
||
| 78 | /** @var string Path to main system template */ |
||
| 79 | protected $template_path = __SAMSON_DEFAULT_TEMPLATE; |
||
| 80 | |||
| 81 | /** |
||
| 82 | * Core constructor. |
||
| 83 | * |
||
| 84 | * @param ContainerBuilderInterface $builder Container builder |
||
| 85 | * @param ResourceMap|null $map system resources |
||
| 86 | * @InjectArgument(builder="\samsonframework\container\ContainerBuilderInterface") |
||
| 87 | * @InjectArgument(builder="\samsonframework\core\ResourceInterface") |
||
| 88 | */ |
||
| 89 | public function __construct(ContainerBuilderInterface $builder, ResourceMap $map) |
||
| 90 | { |
||
| 91 | $this->builder = $builder; |
||
| 92 | // Get correct web-application path |
||
| 93 | $this->system_path = __SAMSON_CWD__; |
||
| 94 | |||
| 95 | // Get web-application resource map |
||
| 96 | $this->map = ResourceMap::get($this->system_path, false, array('src/')); |
||
| 97 | |||
| 98 | // Temporary add template worker |
||
| 99 | $this->subscribe('core.rendered', array($this, 'generateTemplate')); |
||
| 100 | |||
| 101 | // Fire core creation event |
||
| 102 | Event::fire('core.created', array(&$this)); |
||
| 103 | |||
| 104 | // Signal core configure event |
||
| 105 | Event::signal('core.configure', array($this->system_path . __SAMSON_CONFIG_PATH)); |
||
| 106 | } |
||
| 107 | |||
| 108 | /** |
||
| 109 | * Generic wrap for Event system subscription. |
||
| 110 | 1 | * @see \samson\core\\samsonphp\event\Event::subscribe() |
|
| 111 | * |
||
| 112 | * @param string $key Event identifier |
||
| 113 | * @param callable $handler Event handler |
||
| 114 | * @param array $params Event parameters |
||
| 115 | * |
||
| 116 | * @return $this Chaining |
||
| 117 | */ |
||
| 118 | public function subscribe($key, $handler, $params = array()) |
||
| 124 | |||
| 125 | /** |
||
| 126 | * Change current system working environment or receive |
||
| 127 | * current system enviroment if no arguments are passed. |
||
| 128 | * |
||
| 129 | * @param string $environment Environment identifier |
||
| 130 | * |
||
| 131 | * TODO: Function has two different logics - needs to be changed! |
||
| 132 | * @return $this|string Chaining or current system environment |
||
| 133 | */ |
||
| 134 | public function environment($environment = Scheme::BASE) |
||
| 146 | |||
| 147 | /** |
||
| 148 | * Generate special response header triggering caching mechanisms |
||
| 149 | * @param int $cacheLife Amount of seconds for cache(default 3600 - 1 hour) |
||
| 150 | * @param string $accessibility Cache-control accessibility value(default public) |
||
| 151 | */ |
||
| 152 | public function cached($cacheLife = 3600, $accessibility = 'public') |
||
| 153 | { |
||
| 154 | static $cached; |
||
| 155 | // Protect sending cached headers once |
||
| 156 | if (!isset($cached) or $cached !== true) { |
||
| 157 | header('Expires: ' . gmdate('D, d M Y H:i:s T', time() + $cacheLife)); |
||
| 158 | header('Cache-Control: ' . $accessibility . ', max-age=' . $cacheLife); |
||
| 159 | header('Pragma: cache'); |
||
| 160 | |||
| 161 | $cached = true; |
||
| 162 | } |
||
| 163 | } |
||
| 164 | |||
| 165 | /** |
||
| 166 | * Set asynchronous mode. |
||
| 167 | * This mode will not output template and will just path everything that |
||
| 168 | * was outputted to client. |
||
| 169 | * |
||
| 170 | * @param bool $async True to switch to asynchronous output mode |
||
| 171 | * |
||
| 172 | * @return $this Chaining |
||
| 173 | */ |
||
| 174 | public function async($async) |
||
| 180 | |||
| 181 | /** @see iModule::active() */ |
||
| 182 | public function &active(&$module = null) |
||
| 195 | |||
| 196 | /** |
||
| 197 | * Retrieve module instance by identifier. |
||
| 198 | * |
||
| 199 | * @param string|null $module Module identifier |
||
| 200 | * |
||
| 201 | * @return null|Module Found or active module |
||
| 202 | */ |
||
| 203 | public function &module($module = null) |
||
| 223 | |||
| 224 | /** |
||
| 225 | * Unload module from core. |
||
| 226 | * |
||
| 227 | * @param string $moduleID Module identifier |
||
| 228 | */ |
||
| 229 | public function unload($moduleID) |
||
| 235 | |||
| 236 | /** |
||
| 237 | * Insert generic html template tags and data |
||
| 238 | * |
||
| 239 | * @param string $templateHtml Generated HTML |
||
| 240 | * |
||
| 241 | * @deprecated Must be moved to a new HTML output object |
||
| 242 | * @return mixed Changed HTML template |
||
| 243 | */ |
||
| 244 | public function generateTemplate(&$templateHtml) |
||
| 266 | |||
| 267 | /** |
||
| 268 | * Start SamsonPHP framework. |
||
| 269 | * |
||
| 270 | * @param string $default Default module identifier |
||
| 271 | 2 | * |
|
| 272 | * @throws ViewPathNotFound |
||
| 273 | */ |
||
| 274 | 2 | public function start($default) |
|
| 325 | |||
| 326 | 2 | /** @see iCore::template() */ |
|
| 327 | public function template( $template = NULL, $absolutePath = false ) |
||
| 337 | |||
| 338 | 2 | /** |
|
| 339 | * Render file to a buffer. |
||
| 340 | * |
||
| 341 | * @param string $view Path to file |
||
| 342 | * @param array $data Collection of variables to path to file |
||
| 343 | * |
||
| 344 | * @return string Rendered file contents |
||
| 345 | * @throws ViewPathNotFound |
||
| 346 | */ |
||
| 347 | public function render($view, $data = array()) |
||
| 411 | 2 | ||
| 412 | /** |
||
| 413 | * Load system from composer.json |
||
| 414 | 2 | * @param string $dependencyFilePath Path to dependencies file |
|
| 415 | * @return $this Chaining |
||
| 416 | */ |
||
| 417 | public function composer($dependencyFilePath = null) |
||
| 529 | |||
| 530 | /** |
||
| 531 | * Load module from path to core. |
||
| 532 | * |
||
| 533 | * @param string $path Path for module loading |
||
| 534 | * @param array $parameters Collection of loading parameters |
||
| 535 | * |
||
| 536 | * @return string module name |
||
| 537 | * @throws \samsonphp\core\exception\CannotLoadModule |
||
| 538 | */ |
||
| 539 | public function load($path, $parameters = array()) |
||
| 590 | |||
| 591 | //[PHPCOMPRESSOR(remove,start)] |
||
| 592 | |||
| 593 | protected function createMetadata($class, $name, $path, $scope = 'module') |
||
| 631 | |||
| 632 | /** @see iCore::path() */ |
||
| 633 | public function path($path = null) |
||
| 650 | |||
| 651 | /** |
||
| 652 | * @param ClassMetadata[] $metadataCollection |
||
| 653 | * @param array $modulesToLoad |
||
| 654 | * @param array $classes |
||
| 655 | * @param string $containerName |
||
| 656 | * @param ContainerInterface $parentContainer |
||
| 657 | * @return ContainerInterface |
||
| 658 | */ |
||
| 659 | protected function loadMetadata(array $metadataCollection, array $modulesToLoad, array $classes, $containerName = 'Container', ContainerInterface $parentContainer = null) : ContainerInterface |
||
| 660 | { |
||
| 661 | static $implementsByAlias; |
||
| 662 | static $serviceAliasesByClass; |
||
| 663 | |||
| 664 | $containerPath = $this->path().'www/cache/'.$containerName.'.php'; |
||
| 665 | if (!file_exists($containerPath)) { |
||
| 666 | |||
| 667 | |||
| 668 | // Load annotation and parse classes |
||
| 669 | new Injectable(); |
||
| 670 | new InjectArgument(['var' => 'type']); |
||
| 671 | new Service(['value' => '']); |
||
| 672 | |||
| 673 | $reader = new AnnotationReader(); |
||
| 674 | $resolver = new AnnotationResolver( |
||
| 675 | new AnnotationClassResolver($reader), |
||
| 676 | new AnnotationPropertyResolver($reader), |
||
| 677 | new AnnotationMethodResolver($reader) |
||
| 678 | ); |
||
| 679 | |||
| 680 | $annotationCollector = new AnnotationMetadataCollector($resolver); |
||
| 681 | $metadataCollection = $annotationCollector->collect($classes, $metadataCollection); |
||
| 682 | |||
| 683 | // Regroup classes metadata by class name instead of alias |
||
| 684 | $classesMetadata = []; |
||
| 685 | foreach ($metadataCollection as $alias => $classMetadata) { |
||
| 686 | $classesMetadata[$classMetadata->className] = $classMetadata; |
||
| 687 | } |
||
| 688 | |||
| 689 | // Gather all interface implementations |
||
| 690 | $implementsByAlias = $implementsByAlias ?? []; |
||
| 691 | foreach (get_declared_classes() as $class) { |
||
| 692 | $classImplements = class_implements($class); |
||
| 693 | foreach (get_declared_interfaces() as $interface) { |
||
| 694 | if (in_array($interface, $classImplements, true)) { |
||
| 695 | if (array_key_exists($class, $classesMetadata)) { |
||
| 696 | $implementsByAlias[strtolower('\\' . $interface)][] = $classesMetadata[$class]->name; |
||
| 697 | } |
||
| 698 | } |
||
| 699 | } |
||
| 700 | } |
||
| 701 | |||
| 702 | // Gather all class implementations |
||
| 703 | $serviceAliasesByClass = $serviceAliasesByClass ?? []; |
||
| 704 | foreach (get_declared_classes() as $class) { |
||
| 705 | if (array_key_exists($class, $classesMetadata)) { |
||
| 706 | $serviceAliasesByClass[strtolower('\\' . $class)][] = $classesMetadata[$class]->name; |
||
| 707 | } |
||
| 708 | } |
||
| 709 | |||
| 710 | /** |
||
| 711 | * TODO: now we need to implement not forcing to load fixed dependencies into modules |
||
| 712 | * to give ability to change constructors and inject old variable into properties |
||
| 713 | * and them after refactoring remove them. With this we can only specify needed dependencies |
||
| 714 | * in new modules, and still have old ones working. |
||
| 715 | */ |
||
| 716 | |||
| 717 | foreach ($metadataCollection as $alias => $metadata) { |
||
| 718 | foreach ($metadata->propertiesMetadata as $property => $propertyMetadata) { |
||
| 719 | if (is_string($propertyMetadata->dependency)) { |
||
| 720 | $dependency = strtolower($propertyMetadata->dependency); |
||
| 721 | if (array_key_exists($dependency, $implementsByAlias)) { |
||
| 722 | $propertyMetadata->dependency = $implementsByAlias[$dependency][0]; |
||
| 723 | } elseif (array_key_exists($dependency, $serviceAliasesByClass)) { |
||
| 724 | $propertyMetadata->dependency = $serviceAliasesByClass[$dependency][0]; |
||
| 725 | } else { |
||
| 726 | |||
| 727 | } |
||
| 728 | } |
||
| 729 | } |
||
| 730 | foreach ($metadata->methodsMetadata as $method => $methodMetadata) { |
||
| 731 | foreach ($methodMetadata->dependencies as $argument => $dependency) { |
||
| 732 | if (is_string($dependency)) { |
||
| 733 | $dependency = strtolower($dependency); |
||
| 734 | if (array_key_exists($dependency, $implementsByAlias)) { |
||
| 735 | $methodMetadata->dependencies[$argument] = $implementsByAlias[$dependency][0]; |
||
| 736 | //$methodMetadata->parametersMetadata[$argument]->dependency = $implementsByAlias[$dependency][0]; |
||
| 737 | } elseif (array_key_exists($dependency, $serviceAliasesByClass)) { |
||
| 738 | $methodMetadata->dependencies[$argument] = $serviceAliasesByClass[$dependency][0]; |
||
| 739 | //$methodMetadata->parametersMetadata[$argument]->dependency = $serviceAliasesByClass[$dependency][0]; |
||
| 740 | } else { |
||
| 741 | |||
| 742 | } |
||
| 743 | } |
||
| 744 | } |
||
| 745 | } |
||
| 746 | } |
||
| 747 | |||
| 748 | // Generate XML configs |
||
| 749 | //(new XMLBuilder())->buildXMLConfig($metadataCollection, getcwd().'/cache/config_'); |
||
| 750 | |||
| 751 | // Load container class |
||
| 752 | file_put_contents($containerPath, |
||
| 753 | $this->builder->build($metadataCollection, $containerName, '', $parentContainer)); |
||
| 754 | |||
| 755 | } |
||
| 756 | |||
| 757 | require_once($containerPath); |
||
| 758 | |||
| 759 | |||
| 760 | // Inject current core into container |
||
| 761 | /** @var ContainerInterface $container */ |
||
| 762 | $this->container = new $containerName(); |
||
| 763 | $containerReflection = new \ReflectionClass(get_class($this->container)); |
||
| 764 | $serviceProperty = $containerReflection->getProperty(Builder::DI_FUNCTION_SERVICES); |
||
| 765 | $serviceProperty->setAccessible(true); |
||
| 766 | $containerServices = $serviceProperty->getValue($this->container); |
||
| 767 | $containerServices['core'] = $this; |
||
| 768 | $serviceProperty->setValue($this->container, $containerServices); |
||
| 769 | $serviceProperty->setAccessible(false); |
||
| 770 | if ($parentContainer !== null) { |
||
| 771 | $this->container->delegate($parentContainer); |
||
| 772 | } |
||
| 773 | |||
| 774 | foreach ($modulesToLoad as $identifier => $parameters) { |
||
| 775 | $instance = $this->container->get($identifier); |
||
| 776 | |||
| 777 | // Set composer parameters |
||
| 778 | $instance->composerParameters = $parameters; |
||
| 779 | |||
| 780 | // TODO: Change event signature to single approach |
||
| 781 | // Fire core module load event |
||
| 782 | Event::fire('core.module_loaded', [$identifier, &$instance]); |
||
| 783 | |||
| 784 | // Signal core module configure event |
||
| 785 | Event::signal('core.module.configure', [&$instance, $identifier]); |
||
| 786 | |||
| 787 | if ($instance instanceof PreparableInterface) { |
||
| 788 | // Call module preparation handler |
||
| 789 | if (!$instance->prepare()) { |
||
| 790 | //throw new \Exception($identifier.' - Module preparation stage failed'); |
||
| 791 | } |
||
| 792 | } |
||
| 793 | |||
| 794 | // Try to set module parent module |
||
| 795 | $instance->parent = $this->getClassParentModule(get_parent_class($instance)); |
||
| 796 | } |
||
| 797 | |||
| 798 | return $this->container; |
||
| 799 | } |
||
| 800 | |||
| 801 | /** |
||
| 802 | * Find parent module by OOP class inheritance. |
||
| 803 | * |
||
| 804 | * @param string $className Class name for searching parent modules |
||
| 805 | * @param array $ignoredClasses Collection of ignored classes |
||
| 806 | * |
||
| 807 | * @return null|mixed Parent service instance if present |
||
| 808 | */ |
||
| 809 | protected function getClassParentModule( |
||
| 825 | //[PHPCOMPRESSOR(remove,end)] |
||
| 826 | |||
| 827 | /** @return \Container Get system container */ |
||
| 828 | public function getContainer() |
||
| 832 | |||
| 833 | /** Магический метод для десериализации объекта */ |
||
| 834 | public function __wakeup() |
||
| 838 | |||
| 839 | /** Магический метод для сериализации объекта */ |
||
| 840 | public function __sleep() |
||
| 844 | } |
||
| 845 |
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: