| Total Complexity | 57 |
| Total Lines | 296 |
| Duplicated Lines | 0 % |
| Changes | 13 | ||
| 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 name 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 |