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:
and let’s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are 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.php
However, as
OtherDir/Foo.php
does 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: