| Total Complexity | 53 |
| Total Lines | 384 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
Complex classes like ThemeCompiler 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 ThemeCompiler, and based on these observations, apply Extract Interface, too.
| 1 | <?php declare(strict_types=1); |
||
| 31 | class ThemeCompiler implements ThemeCompilerInterface |
||
| 32 | { |
||
| 33 | /** |
||
| 34 | * @internal |
||
| 35 | * |
||
| 36 | * @param Package[] $packages |
||
| 37 | */ |
||
| 38 | public function __construct( |
||
| 39 | private readonly FilesystemOperator $filesystem, |
||
| 40 | private readonly FilesystemOperator $tempFilesystem, |
||
| 41 | private readonly ThemeFileResolver $themeFileResolver, |
||
| 42 | private readonly bool $debug, |
||
| 43 | private readonly EventDispatcherInterface $eventDispatcher, |
||
| 44 | private readonly ThemeFileImporterInterface $themeFileImporter, |
||
| 45 | private readonly iterable $packages, |
||
| 46 | private readonly CacheInvalidator $logger, |
||
| 47 | private readonly AbstractThemePathBuilder $themePathBuilder, |
||
| 48 | private readonly string $projectDir, |
||
| 49 | private readonly AbstractScssCompiler $scssCompiler, |
||
| 50 | private readonly MessageBusInterface $messageBus |
||
| 51 | ) { |
||
| 52 | } |
||
| 53 | |||
| 54 | public function compileTheme( |
||
| 55 | string $salesChannelId, |
||
| 56 | string $themeId, |
||
| 57 | StorefrontPluginConfiguration $themeConfig, |
||
| 58 | StorefrontPluginConfigurationCollection $configurationCollection, |
||
| 59 | bool $withAssets, |
||
| 60 | Context $context |
||
| 61 | ): void { |
||
| 62 | $resolvedFiles = $this->themeFileResolver->resolveFiles($themeConfig, $configurationCollection, false); |
||
| 63 | |||
| 64 | $styleFiles = $resolvedFiles[ThemeFileResolver::STYLE_FILES]; |
||
| 65 | |||
| 66 | $concatenatedStyles = $this->concatenateStyles( |
||
| 67 | $styleFiles, |
||
| 68 | $themeConfig, |
||
| 69 | $salesChannelId |
||
| 70 | ); |
||
| 71 | |||
| 72 | $compiled = $this->compileStyles( |
||
| 73 | $concatenatedStyles, |
||
| 74 | $themeConfig, |
||
| 75 | $styleFiles->getResolveMappings(), |
||
| 76 | $salesChannelId, |
||
| 77 | $context |
||
| 78 | ); |
||
| 79 | |||
| 80 | $concatenatedScripts = $this->getConcatenatedScripts($resolvedFiles[ThemeFileResolver::SCRIPT_FILES], $themeConfig, $salesChannelId); |
||
| 81 | |||
| 82 | $newThemeHash = Uuid::randomHex(); |
||
| 83 | $themePrefix = $this->themePathBuilder->generateNewPath($salesChannelId, $themeId, $newThemeHash); |
||
|
|
|||
| 84 | $oldThemePrefix = $this->themePathBuilder->assemblePath($salesChannelId, $themeId); |
||
| 85 | |||
| 86 | try { |
||
| 87 | $this->writeCompiledFiles($themePrefix, $compiled, $concatenatedScripts, $withAssets, $themeConfig, $configurationCollection); |
||
| 88 | } catch (\Throwable $e) { |
||
| 89 | // delete folder in case of error and rethrow exception |
||
| 90 | if ($themePrefix !== $oldThemePrefix) { |
||
| 91 | $this->filesystem->deleteDirectory($themePrefix); |
||
| 92 | } |
||
| 93 | |||
| 94 | throw $e; |
||
| 95 | } |
||
| 96 | |||
| 97 | $this->themePathBuilder->saveSeed($salesChannelId, $themeId, $newThemeHash); |
||
| 98 | |||
| 99 | if ($themePrefix !== $oldThemePrefix) { |
||
| 100 | // only delete the old directory if the `themePathBuilder` actually returned a new path and supports seeding |
||
| 101 | // also delete with a delay of one our, so that the old theme is still available for a while in case some CDN delivers stale content |
||
| 102 | $this->messageBus->dispatch( |
||
| 103 | new Envelope( |
||
| 104 | new DeleteThemeFilesMessage($oldThemePrefix, $salesChannelId, $themeId), |
||
| 105 | // one hour in milliseconds |
||
| 106 | [new DelayStamp(3600 * 1000)] |
||
| 107 | ) |
||
| 108 | ); |
||
| 109 | } |
||
| 110 | |||
| 111 | // Reset cache buster state for improving performance in getMetadata |
||
| 112 | $this->logger->invalidate(['theme-metaData'], true); |
||
| 113 | } |
||
| 114 | |||
| 115 | /** |
||
| 116 | * @param array<string, string> $resolveMappings |
||
| 117 | */ |
||
| 118 | public function getResolveImportPathsCallback(array $resolveMappings): \Closure |
||
| 119 | { |
||
| 120 | return function ($originalPath) use ($resolveMappings) { |
||
| 121 | foreach ($resolveMappings as $resolve => $resolvePath) { |
||
| 122 | $resolve = '~' . $resolve; |
||
| 123 | if (mb_strpos($originalPath, $resolve) === 0) { |
||
| 124 | $dirname = $resolvePath . \dirname(mb_substr($originalPath, mb_strlen($resolve))); |
||
| 125 | |||
| 126 | $filename = basename($originalPath); |
||
| 127 | $extension = $this->getImportFileExtension(pathinfo($filename, \PATHINFO_EXTENSION)); |
||
| 128 | $path = $dirname . \DIRECTORY_SEPARATOR . $filename . $extension; |
||
| 129 | if (file_exists($path)) { |
||
| 130 | return $path; |
||
| 131 | } |
||
| 132 | |||
| 133 | $path = $dirname . \DIRECTORY_SEPARATOR . '_' . $filename . $extension; |
||
| 134 | if (file_exists($path)) { |
||
| 135 | return $path; |
||
| 136 | } |
||
| 137 | } |
||
| 138 | } |
||
| 139 | |||
| 140 | return null; |
||
| 141 | }; |
||
| 142 | } |
||
| 143 | |||
| 144 | private function copyAssets( |
||
| 174 | } |
||
| 175 | } |
||
| 176 | |||
| 177 | /** |
||
| 178 | * @param array<string, string> $resolveMappings |
||
| 179 | */ |
||
| 180 | private function compileStyles( |
||
| 230 | } |
||
| 231 | |||
| 232 | private function getImportFileExtension(string $extension): string |
||
| 246 | } |
||
| 247 | |||
| 248 | /** |
||
| 249 | * Converts the feature config array to a SCSS map syntax. |
||
| 250 | * This allows reading of the feature flag config inside SCSS via `map.get` function. |
||
| 251 | * |
||
| 252 | * Output example: |
||
| 253 | * $sw-features: ("FEATURE_NEXT_1234": false, "FEATURE_NEXT_1235": true); |
||
| 254 | * |
||
| 255 | * @see https://sass-lang.com/documentation/values/maps |
||
| 256 | */ |
||
| 257 | private function getFeatureConfigScssMap(): string |
||
| 258 | { |
||
| 259 | $allFeatures = Feature::getAll(); |
||
| 260 | |||
| 261 | $featuresScss = implode(',', array_map(fn ($value, $key) => sprintf('"%s": %s', $key, json_encode($value, \JSON_THROW_ON_ERROR)), $allFeatures, array_keys($allFeatures))); |
||
| 262 | |||
| 263 | return sprintf('$sw-features: (%s);', $featuresScss); |
||
| 264 | } |
||
| 265 | |||
| 266 | /** |
||
| 267 | * @param array<string, string|int> $variables |
||
| 268 | * |
||
| 269 | * @return array<string> |
||
| 270 | */ |
||
| 271 | private function formatVariables(array $variables): array |
||
| 274 | } |
||
| 275 | |||
| 276 | /** |
||
| 277 | * @param array{fields?: array{value: null|string|array<mixed>, scss?: bool, type: string}[]} $config |
||
| 278 | */ |
||
| 279 | private function dumpVariables(array $config, string $salesChannelId, Context $context): string |
||
| 280 | { |
||
| 281 | $variables = []; |
||
| 282 | foreach ($config['fields'] ?? [] as $key => $data) { |
||
| 283 | if (!\is_array($data) || !$this->isDumpable($data)) { |
||
| 284 | continue; |
||
| 285 | } |
||
| 286 | |||
| 287 | if (\in_array($data['type'], ['media', 'textarea'], true) && \is_string($data['value'])) { |
||
| 288 | $variables[$key] = '\'' . $data['value'] . '\''; |
||
| 289 | } elseif ($data['type'] === 'switch' || $data['type'] === 'checkbox') { |
||
| 290 | $variables[$key] = (int) ($data['value']); |
||
| 291 | } elseif (!\is_array($data['value'])) { |
||
| 292 | $variables[$key] = (string) $data['value']; |
||
| 293 | } |
||
| 294 | } |
||
| 295 | |||
| 296 | foreach ($this->packages as $key => $package) { |
||
| 297 | $variables[sprintf('sw-asset-%s-url', $key)] = sprintf('\'%s\'', $package->getUrl('')); |
||
| 298 | } |
||
| 299 | |||
| 300 | $themeVariablesEvent = new ThemeCompilerEnrichScssVariablesEvent( |
||
| 301 | $variables, |
||
| 302 | $salesChannelId, |
||
| 303 | $context |
||
| 304 | ); |
||
| 305 | |||
| 306 | $this->eventDispatcher->dispatch($themeVariablesEvent); |
||
| 307 | |||
| 308 | $dump = str_replace( |
||
| 309 | ['#class#', '#variables#'], |
||
| 310 | [self::class, implode(\PHP_EOL, $this->formatVariables($themeVariablesEvent->getVariables()))], |
||
| 311 | $this->getVariableDumpTemplate() |
||
| 312 | ); |
||
| 313 | |||
| 314 | $this->tempFilesystem->write('theme-variables.scss', $dump); |
||
| 315 | |||
| 316 | return $dump; |
||
| 317 | } |
||
| 318 | |||
| 319 | /** |
||
| 320 | * @param array{value: string|array<mixed>|null, scss?: bool, type: string} $data |
||
| 321 | */ |
||
| 322 | private function isDumpable(array $data): bool |
||
| 349 | } |
||
| 350 | |||
| 351 | private function getVariableDumpTemplate(): string |
||
| 352 | { |
||
| 353 | return <<<PHP_EOL |
||
| 354 | // ATTENTION! This file is auto generated by the #class# and should not be edited. |
||
| 355 | |||
| 356 | #variables# |
||
| 357 | |||
| 358 | PHP_EOL; |
||
| 359 | } |
||
| 360 | |||
| 361 | private function concatenateStyles( |
||
| 362 | FileCollection $styleFiles, |
||
| 363 | StorefrontPluginConfiguration $themeConfig, |
||
| 364 | string $salesChannelId |
||
| 365 | ): string { |
||
| 366 | $concatenatedStyles = ''; |
||
| 367 | foreach ($styleFiles as $file) { |
||
| 368 | $concatenatedStyles .= $this->themeFileImporter->getConcatenableStylePath($file, $themeConfig); |
||
| 369 | } |
||
| 370 | $concatenatedStylesEvent = new ThemeCompilerConcatenatedStylesEvent($concatenatedStyles, $salesChannelId); |
||
| 371 | $this->eventDispatcher->dispatch($concatenatedStylesEvent); |
||
| 372 | |||
| 373 | return $concatenatedStylesEvent->getConcatenatedStyles(); |
||
| 374 | } |
||
| 375 | |||
| 376 | private function getConcatenatedScripts( |
||
| 377 | FileCollection $scriptFiles, |
||
| 378 | StorefrontPluginConfiguration $themeConfig, |
||
| 379 | string $salesChannelId |
||
| 380 | ): string { |
||
| 381 | $concatenatedScripts = ''; |
||
| 382 | foreach ($scriptFiles as $file) { |
||
| 383 | $concatenatedScripts .= $this->themeFileImporter->getConcatenableScriptPath($file, $themeConfig); |
||
| 384 | } |
||
| 385 | |||
| 386 | $concatenatedScriptsEvent = new ThemeCompilerConcatenatedScriptsEvent($concatenatedScripts, $salesChannelId); |
||
| 387 | $this->eventDispatcher->dispatch($concatenatedScriptsEvent); |
||
| 388 | |||
| 389 | return $concatenatedScriptsEvent->getConcatenatedScripts(); |
||
| 390 | } |
||
| 391 | |||
| 392 | private function writeCompiledFiles( |
||
| 415 | } |
||
| 416 | } |
||
| 417 | } |
||
| 418 |
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.