| Total Complexity | 57 | 
| Total Lines | 296 | 
| Duplicated Lines | 0 % | 
| Changes | 14 | ||
| Bugs | 0 | Features | 0 | 
Complex classes like ExtensionBuilder 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.
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 ExtensionBuilder, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 31 | class ExtensionBuilder  | 
            ||
| 32 | { | 
            ||
| 33 | protected AbstractContainer $container;  | 
            ||
| 34 | private ?ConfigBuilderGeneratorInterface $configBuilder = null;  | 
            ||
| 35 | |||
| 36 | /** @var array<string,mixed> */  | 
            ||
| 37 | private array $configuration;  | 
            ||
| 38 | |||
| 39 | /** @var array<string,string> */  | 
            ||
| 40 | private array $aliases = [];  | 
            ||
| 41 | |||
| 42 | /** @var array<string,ExtensionInterface|null> */  | 
            ||
| 43 | private array $extensions = [];  | 
            ||
| 44 | |||
| 45 | /**  | 
            ||
| 46 | * @param array<string,mixed> $config the default configuration for all extensions  | 
            ||
| 47 | */  | 
            ||
| 48 | public function __construct(AbstractContainer $container, array $config = [])  | 
            ||
| 49 |     { | 
            ||
| 50 |         if (\array_key_exists('parameters', $config)) { | 
            ||
| 51 | $container->parameters += $config['parameters'];  | 
            ||
| 52 | unset($config['parameters']);  | 
            ||
| 53 | }  | 
            ||
| 54 | |||
| 55 | $this->container = $container;  | 
            ||
| 56 | $this->configuration = $config;  | 
            ||
| 57 | }  | 
            ||
| 58 | |||
| 59 | /**  | 
            ||
| 60 | * Enable Generating ConfigBuilders to help create valid config.  | 
            ||
| 61 | */  | 
            ||
| 62 | public function setConfigBuilderGenerator(string $outputDir): void  | 
            ||
| 63 |     { | 
            ||
| 64 | $this->configBuilder = new ConfigBuilderGenerator($outputDir);  | 
            ||
| 65 | }  | 
            ||
| 66 | |||
| 67 | /**  | 
            ||
| 68 | * Get a registered extension instance for extending purpose or etc.  | 
            ||
| 69 | */  | 
            ||
| 70 | public function get(string $extensionName): ?ExtensionInterface  | 
            ||
| 71 |     { | 
            ||
| 72 | return $this->extensions[$this->aliases[$extensionName] ?? $extensionName] ?? null;  | 
            ||
| 73 | }  | 
            ||
| 74 | |||
| 75 | /**  | 
            ||
| 76 | * Checks if extension exists.  | 
            ||
| 77 | */  | 
            ||
| 78 | public function has(string $extensionName): bool  | 
            ||
| 79 |     { | 
            ||
| 80 | return \array_key_exists($this->aliases[$extensionName] ?? $extensionName, $this->extensions);  | 
            ||
| 81 | }  | 
            ||
| 82 | |||
| 83 | /**  | 
            ||
| 84 | * Get all loaded extensions.  | 
            ||
| 85 | *  | 
            ||
| 86 | * @return array<string,ExtensionInterface>  | 
            ||
| 87 | */  | 
            ||
| 88 | public function getExtensions(): array  | 
            ||
| 91 | }  | 
            ||
| 92 | |||
| 93 | /**  | 
            ||
| 94 | * Get all loaded extension configs.  | 
            ||
| 95 | *  | 
            ||
| 96 | * @return array<int|string,mixed>  | 
            ||
| 97 | */  | 
            ||
| 98 | public function getConfigs(): array  | 
            ||
| 99 |     { | 
            ||
| 100 | return $this->configuration;  | 
            ||
| 101 | }  | 
            ||
| 102 | |||
| 103 | /**  | 
            ||
| 104 | * Get all extensions configuration.  | 
            ||
| 105 | *  | 
            ||
| 106 | * @return array<string,mixed>  | 
            ||
| 107 | */  | 
            ||
| 108 | public function getConfig(string $extensionName, string $parent = null)  | 
            ||
| 133 | }  | 
            ||
| 134 | |||
| 135 | /**  | 
            ||
| 136 | * Modify the default configuration for an extension.  | 
            ||
| 137 | *  | 
            ||
| 138 | * @param array<string,mixed> $configuration  | 
            ||
| 139 | * @param bool $replace If true, integer keys values will be replaceable  | 
            ||
| 140 | */  | 
            ||
| 141 | public function modifyConfig(string $extensionName, array $configuration, string $parent = null, bool $replace = false): void  | 
            ||
| 142 |     { | 
            ||
| 143 |         if (!\array_key_exists($extensionName, $this->extensions)) { | 
            ||
| 144 |             throw new \InvalidArgumentException(\sprintf('The extension "%s" provided in not valid, must be an extension\'s class name.', $extensionName)); | 
            ||
| 145 | }  | 
            ||
| 146 | |||
| 147 |         if (!empty($defaults = $this->getConfig($extensionName, $parent))) { | 
            ||
| 148 | $values = $this->mergeConfig($defaults, $configuration, $replace);  | 
            ||
| 149 | |||
| 150 |             if (isset($parent, $this->configuration[$parent])) { | 
            ||
| 151 | $this->configuration[$parent][$extensionName] = $values;  | 
            ||
| 152 |             } else { | 
            ||
| 153 | $this->configuration[$extensionName] = $values;  | 
            ||
| 154 | }  | 
            ||
| 155 |         } else { | 
            ||
| 156 | $this->configuration[$extensionName] = $configuration;  | 
            ||
| 157 | }  | 
            ||
| 158 | }  | 
            ||
| 159 | |||
| 160 | /**  | 
            ||
| 161 | * Loads a set of container extensions.  | 
            ||
| 162 | *  | 
            ||
| 163 | * You can map an extension class name to a priority index else if  | 
            ||
| 164 | * declared as an array with arguments the third index is termed as priority value.  | 
            ||
| 165 | *  | 
            ||
| 166 | * Example:  | 
            ||
| 167 | * [  | 
            ||
| 168 | * PhpExtension::class,  | 
            ||
| 169 | * CoreExtension::class => -1,  | 
            ||
| 170 | * [ProjectExtension::class, ['%project.dir%']],  | 
            ||
| 171 | * ]  | 
            ||
| 172 | *  | 
            ||
| 173 | * @param array<int,mixed> $extensions  | 
            ||
| 174 | */  | 
            ||
| 175 | public function load(array $extensions): void  | 
            ||
| 176 |     { | 
            ||
| 177 |         $this->container->runScope([ExtensionInterface::BUILDER => $this], function () use ($extensions): void { | 
            ||
| 178 | /** @var array<int,BootExtensionInterface> */  | 
            ||
| 179 | $afterLoading = [];  | 
            ||
| 180 | |||
| 181 | $this->bootExtensions($extensions, $afterLoading);  | 
            ||
| 182 | |||
| 183 |             foreach (\array_reverse($afterLoading) as $bootable) { | 
            ||
| 184 | $bootable->boot($this->container);  | 
            ||
| 185 | }  | 
            ||
| 186 | });  | 
            ||
| 187 | }  | 
            ||
| 188 | |||
| 189 | /**  | 
            ||
| 190 | * Resolve extensions and register them.  | 
            ||
| 191 | *  | 
            ||
| 192 | * @param mixed[] $extensions  | 
            ||
| 193 | * @param array<int,BootExtensionInterface> $afterLoading  | 
            ||
| 194 | */  | 
            ||
| 195 | private function bootExtensions(array $extensions, array &$afterLoading, string $extraKey = null): void  | 
            ||
| 196 |     { | 
            ||
| 197 | $container = $this->container;  | 
            ||
| 198 | |||
| 199 |         foreach ($this->sortExtensions($extensions) as $resolved) { | 
            ||
| 200 |             if ($resolved instanceof DebugExtensionInterface && $resolved->inDevelopment() !== $container->parameters['debug']) { | 
            ||
| 201 | continue;  | 
            ||
| 202 | }  | 
            ||
| 203 | |||
| 204 |             if ($container instanceof ContainerBuilder) { | 
            ||
| 205 | $container->addResource(new ClassExistenceResource(($ref = new \ReflectionClass($resolved))->getName(), false));  | 
            ||
| 206 | $container->addResource(new FileExistenceResource($rPath = $ref->getFileName()));  | 
            ||
| 207 | $container->addResource(new FileResource($rPath));  | 
            ||
| 208 | }  | 
            ||
| 209 | |||
| 210 |             if ($resolved instanceof RequiredPackagesInterface) { | 
            ||
| 211 | $this->ensureRequiredPackagesAvailable($resolved);  | 
            ||
| 212 | }  | 
            ||
| 213 | |||
| 214 |             if ($resolved instanceof DependenciesInterface) { | 
            ||
| 215 | $this->bootExtensions($resolved->dependencies(), $afterLoading, \method_exists($resolved, 'dependOnConfigKey') ? $resolved->dependOnConfigKey() : $extraKey);  | 
            ||
| 216 | }  | 
            ||
| 217 | $configuration = $this->getConfig($id = \get_class($resolved), $extraKey);  | 
            ||
| 218 | |||
| 219 |             if ($resolved instanceof ConfigurationInterface) { | 
            ||
| 220 |                 if (null !== $this->configBuilder && \is_string($configuration)) { | 
            ||
| 221 | $configLoader = $this->configBuilder->build($resolved)();  | 
            ||
| 222 | |||
| 223 |                     if (\file_exists($configuration = $container->parameter($configuration))) { | 
            ||
| 224 | (include $configuration)($configLoader);  | 
            ||
| 225 | }  | 
            ||
| 226 | $configuration = $configLoader->toArray();  | 
            ||
| 227 | |||
| 228 |                     if (isset($extraKey, $this->configuration[$extraKey][$id])) { | 
            ||
| 229 | $this->configuration[$extraKey][$id] = $configuration;  | 
            ||
| 230 |                     } else { | 
            ||
| 231 | $this->configuration[$id] = $configuration;  | 
            ||
| 232 | }  | 
            ||
| 233 |                 } else { | 
            ||
| 234 | $treeBuilder = $resolved->getConfigTreeBuilder()->buildTree();  | 
            ||
| 235 | $configuration = (new Processor())->process($treeBuilder, [$treeBuilder->getName() => $configuration]);  | 
            ||
| 236 | }  | 
            ||
| 237 | }  | 
            ||
| 238 | $resolved->register($container, $configuration ?? []);  | 
            ||
| 239 | |||
| 240 |             if ($resolved instanceof BootExtensionInterface) { | 
            ||
| 241 | $afterLoading[] = $resolved;  | 
            ||
| 242 | }  | 
            ||
| 243 | }  | 
            ||
| 244 | }  | 
            ||
| 245 | |||
| 246 | /**  | 
            ||
| 247 | * Sort extensions by priority.  | 
            ||
| 248 | *  | 
            ||
| 249 | * @param mixed[] $extensions container extensions with their priority as key  | 
            ||
| 250 | *  | 
            ||
| 251 | * @return array<string,ExtensionInterface>  | 
            ||
| 252 | */  | 
            ||
| 253 | private function sortExtensions(array $extensions): array  | 
            ||
| 254 |     { | 
            ||
| 255 |         if (0 === \count($extensions)) { | 
            ||
| 256 | return [];  | 
            ||
| 257 | }  | 
            ||
| 258 | $passes = [];  | 
            ||
| 259 | |||
| 260 |         foreach ($extensions as $offset => $extension) { | 
            ||
| 261 | $index = 0;  | 
            ||
| 262 | |||
| 263 |             if (\is_int($extension)) { | 
            ||
| 264 | [$index, $extension] = [$extension, $offset];  | 
            ||
| 265 |             } elseif (\is_array($extension) && isset($extension[2])) { | 
            ||
| 266 | $index = $extension[2];  | 
            ||
| 267 | }  | 
            ||
| 268 | [$extension, $args] = \is_array($extension) ? $extension : [$extension, []];  | 
            ||
| 269 | |||
| 270 |             if ($this->container instanceof ContainerBuilder) { | 
            ||
| 271 | $resolved = (new \ReflectionClass($extension))->newInstanceArgs(\array_map(fn ($v) => \is_string($v) ? $this->container->parameter($v) : $v, $args));  | 
            ||
| 272 |             } else { | 
            ||
| 273 | $resolved = $this->container->getResolver()->resolveClass($extension, $args);  | 
            ||
| 274 | }  | 
            ||
| 275 | |||
| 276 |             if ($resolved instanceof AliasedInterface) { | 
            ||
| 277 | $aliasedId = $resolved->getAlias();  | 
            ||
| 278 | |||
| 279 |                 if (isset($this->aliases[$aliasedId])) { | 
            ||
| 280 |                     throw new \RuntimeException(\sprintf('The aliased id "%s" for %s extension class must be unqiue.', $aliasedId, $extension)); | 
            ||
| 281 | }  | 
            ||
| 282 | $this->aliases[$aliasedId] = $extension;  | 
            ||
| 283 | }  | 
            ||
| 284 | $passes[$index][] = $this->extensions[$extension] = $resolved;  | 
            ||
| 285 | }  | 
            ||
| 286 | \krsort($passes);  | 
            ||
| 287 | |||
| 288 | return \array_merge(...$passes); // Flatten the array  | 
            ||
| 289 | }  | 
            ||
| 290 | |||
| 291 | private function ensureRequiredPackagesAvailable(RequiredPackagesInterface $extension): void  | 
            ||
| 306 | }  | 
            ||
| 307 | |||
| 308 | /**  | 
            ||
| 309 | * Merges $b into $a.  | 
            ||
| 310 | */  | 
            ||
| 311 | private function mergeConfig(array $a, array $b, bool $replace): array  | 
            ||
| 327 | }  | 
            ||
| 328 | }  | 
            ||
| 329 |