Total Complexity | 80 |
Total Lines | 571 |
Duplicated Lines | 0 % |
Changes | 2 | ||
Bugs | 0 | Features | 0 |
Complex classes like ThemeService 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 ThemeService, and based on these observations, apply Extract Interface, too.
1 | <?php declare(strict_types=1); |
||
17 | class ThemeService |
||
18 | { |
||
19 | /** |
||
20 | * @var StorefrontPluginRegistryInterface |
||
21 | */ |
||
22 | private $pluginRegistry; |
||
23 | |||
24 | /** |
||
25 | * @var EntityRepositoryInterface |
||
26 | */ |
||
27 | private $themeRepository; |
||
28 | |||
29 | /** |
||
30 | * @var EntityRepositoryInterface |
||
31 | */ |
||
32 | private $themeSalesChannelRepository; |
||
33 | |||
34 | /** |
||
35 | * @var EntityRepositoryInterface |
||
36 | */ |
||
37 | private $mediaRepository; |
||
38 | |||
39 | /** |
||
40 | * @var ThemeCompilerInterface |
||
41 | */ |
||
42 | private $themeCompiler; |
||
43 | |||
44 | public function __construct( |
||
56 | } |
||
57 | |||
58 | public function compileTheme( |
||
73 | ); |
||
74 | } |
||
75 | |||
76 | public function updateTheme(string $themeId, ?array $config, ?string $parentThemeId, Context $context): void |
||
77 | { |
||
78 | $criteria = new Criteria([$themeId]); |
||
79 | $criteria->addAssociation('salesChannels'); |
||
80 | $theme = $this->themeRepository->search($criteria, $context)->get($themeId); |
||
81 | |||
82 | if (!$theme) { |
||
83 | throw new InvalidThemeException($themeId); |
||
84 | } |
||
85 | |||
86 | $data = ['id' => $themeId]; |
||
87 | if ($config) { |
||
|
|||
88 | foreach ($config as $key => $value) { |
||
89 | $data['configValues'][$key] = $value; |
||
90 | } |
||
91 | } |
||
92 | |||
93 | if ($parentThemeId) { |
||
94 | $data['parentThemeId'] = $parentThemeId; |
||
95 | } |
||
96 | |||
97 | if (array_key_exists('configValues', $data) && $theme->getConfigValues()) { |
||
98 | $data['configValues'] = array_replace_recursive($theme->getConfigValues(), $data['configValues']); |
||
99 | } |
||
100 | |||
101 | $this->themeRepository->update([$data], $context); |
||
102 | |||
103 | foreach ($theme->getSalesChannels() as $salesChannel) { |
||
104 | $this->compileTheme($salesChannel->getId(), $themeId, $context, null, false); |
||
105 | } |
||
106 | } |
||
107 | |||
108 | public function assignTheme(string $themeId, string $salesChannelId, Context $context): bool |
||
109 | { |
||
110 | $this->compileTheme($salesChannelId, $themeId, $context); |
||
111 | |||
112 | $this->themeSalesChannelRepository->upsert([[ |
||
113 | 'themeId' => $themeId, |
||
114 | 'salesChannelId' => $salesChannelId, |
||
115 | ]], $context); |
||
116 | |||
117 | return true; |
||
118 | } |
||
119 | |||
120 | public function resetTheme(string $themeId, Context $context): void |
||
121 | { |
||
122 | $criteria = new Criteria([$themeId]); |
||
123 | $theme = $this->themeRepository->search($criteria, $context)->get($themeId); |
||
124 | |||
125 | if (!$theme) { |
||
126 | throw new InvalidThemeException($themeId); |
||
127 | } |
||
128 | |||
129 | $data = ['id' => $themeId]; |
||
130 | $data['configValues'] = null; |
||
131 | |||
132 | $this->themeRepository->update([$data], $context); |
||
133 | } |
||
134 | |||
135 | /** |
||
136 | * @throws InvalidThemeConfigException |
||
137 | * @throws InvalidThemeException |
||
138 | * @throws InconsistentCriteriaIdsException |
||
139 | */ |
||
140 | public function getThemeConfiguration(string $themeId, bool $translate, Context $context): array |
||
141 | { |
||
142 | $criteria = new Criteria(); |
||
143 | $criteria->setTitle('theme-service::load-config'); |
||
144 | |||
145 | $criteria->addFilter(new MultiFilter( |
||
146 | MultiFilter::CONNECTION_OR, |
||
147 | [ |
||
148 | new EqualsFilter('technicalName', StorefrontPluginRegistry::BASE_THEME_NAME), |
||
149 | new EqualsFilter('id', $themeId), |
||
150 | ] |
||
151 | )); |
||
152 | |||
153 | $themes = $this->themeRepository->search($criteria, $context); |
||
154 | |||
155 | $theme = $themes->get($themeId); |
||
156 | |||
157 | /** @var ThemeEntity|null $theme */ |
||
158 | if (!$theme) { |
||
159 | throw new InvalidThemeException($themeId); |
||
160 | } |
||
161 | |||
162 | /** @var ThemeEntity $baseTheme */ |
||
163 | $baseTheme = $themes->filter(function (ThemeEntity $theme) { |
||
164 | return $theme->getTechnicalName() === StorefrontPluginRegistry::BASE_THEME_NAME; |
||
165 | })->first(); |
||
166 | |||
167 | $baseThemeConfig = $this->mergeStaticConfig($baseTheme); |
||
168 | |||
169 | $themeConfigFieldFactory = new ThemeConfigFieldFactory(); |
||
170 | $configFields = []; |
||
171 | |||
172 | $configuredTheme = $this->mergeStaticConfig($theme); |
||
173 | $themeConfig = array_replace_recursive($baseThemeConfig, $configuredTheme); |
||
174 | |||
175 | foreach ($themeConfig['fields'] as $name => $item) { |
||
176 | $configFields[$name] = $themeConfigFieldFactory->create($name, $item); |
||
177 | } |
||
178 | |||
179 | $configFields = json_decode((string) json_encode($configFields), true); |
||
180 | |||
181 | $labels = array_replace_recursive($baseTheme->getLabels() ?? [], $theme->getLabels() ?? []); |
||
182 | if ($translate && !empty($labels)) { |
||
183 | $configFields = $this->translateLabels($configFields, $labels); |
||
184 | } |
||
185 | |||
186 | $helpTexts = array_replace_recursive($baseTheme->getHelpTexts() ?? [], $theme->getHelpTexts() ?? []); |
||
187 | if ($translate && !empty($helpTexts)) { |
||
188 | $configFields = $this->translateHelpTexts($configFields, $helpTexts); |
||
189 | } |
||
190 | |||
191 | $themeConfig['fields'] = $configFields; |
||
192 | |||
193 | return $themeConfig; |
||
194 | } |
||
195 | |||
196 | public function getResolvedThemeConfiguration(string $themeId, Context $context): array |
||
197 | { |
||
198 | $config = $this->getThemeConfiguration($themeId, false, $context); |
||
199 | $resolvedConfig = []; |
||
200 | $mediaItems = []; |
||
201 | if (!array_key_exists('fields', $config)) { |
||
202 | return []; |
||
203 | } |
||
204 | |||
205 | foreach ($config['fields'] as $key => $data) { |
||
206 | if ($data['type'] === 'media' && $data['value'] && Uuid::isValid($data['value'])) { |
||
207 | $mediaItems[$data['value']][] = $key; |
||
208 | } |
||
209 | $resolvedConfig[$key] = $data['value']; |
||
210 | } |
||
211 | |||
212 | /** @var string[] $mediaIds */ |
||
213 | $mediaIds = array_keys($mediaItems); |
||
214 | $criteria = new Criteria($mediaIds); |
||
215 | $criteria->setTitle('theme-service::resolve-media'); |
||
216 | $result = $this->mediaRepository->search($criteria, $context); |
||
217 | |||
218 | foreach ($result as $media) { |
||
219 | if (!\array_key_exists($media->getId(), $mediaItems)) { |
||
220 | continue; |
||
221 | } |
||
222 | |||
223 | foreach ($mediaItems[$media->getId()] as $key) { |
||
224 | $resolvedConfig[$key] = $media->getUrl(); |
||
225 | } |
||
226 | } |
||
227 | |||
228 | return $resolvedConfig; |
||
229 | } |
||
230 | |||
231 | public function getThemeConfigurationStructuredFields(string $themeId, bool $translate, Context $context): array |
||
232 | { |
||
233 | $mergedConfig = $this->getThemeConfiguration($themeId, $translate, $context)['fields']; |
||
234 | |||
235 | $translations = []; |
||
236 | if ($translate) { |
||
237 | $translations = $this->getTranslations($themeId, $context); |
||
238 | $mergedConfig = $this->translateLabels($mergedConfig, $translations); |
||
239 | } |
||
240 | |||
241 | $outputStructure = []; |
||
242 | |||
243 | foreach ($mergedConfig as $fieldName => $fieldConfig) { |
||
244 | $tab = $this->getTab($fieldConfig); |
||
245 | $tabLabel = $this->getTabLabel($tab, $translations); |
||
246 | $block = $this->getBlock($fieldConfig); |
||
247 | $blockLabel = $this->getBlockLabel($block, $translations); |
||
248 | $section = $this->getSection($fieldConfig); |
||
249 | $sectionLabel = $this->getSectionLabel($section, $translations); |
||
250 | |||
251 | // set default tab |
||
252 | $outputStructure['tabs']['default']['label'] = ''; |
||
253 | |||
254 | // set labels |
||
255 | $outputStructure['tabs'][$tab]['label'] = $tabLabel; |
||
256 | $outputStructure['tabs'][$tab]['blocks'][$block]['label'] = $blockLabel; |
||
257 | $outputStructure['tabs'][$tab]['blocks'][$block]['sections'][$section]['label'] = $sectionLabel; |
||
258 | |||
259 | // add fields to sections |
||
260 | $outputStructure['tabs'][$tab]['blocks'][$block]['sections'][$section]['fields'][$fieldName] = [ |
||
261 | 'label' => $fieldConfig['label'], |
||
262 | 'helpText' => $fieldConfig['helpText'] ?? null, |
||
263 | 'type' => $fieldConfig['type'], |
||
264 | 'custom' => $fieldConfig['custom'], |
||
265 | 'fullWidth' => $fieldConfig['fullWidth'], |
||
266 | ]; |
||
267 | } |
||
268 | |||
269 | return $outputStructure; |
||
270 | } |
||
271 | |||
272 | /** |
||
273 | * @deprecated tag:v6.4.0 use getThemeConfigurationStructuredFields instead |
||
274 | * |
||
275 | * @throws InconsistentCriteriaIdsException |
||
276 | * @throws InvalidThemeConfigException |
||
277 | * @throws InvalidThemeException |
||
278 | */ |
||
279 | public function getThemeConfigurationFields(string $themeId, bool $translate, Context $context): array |
||
280 | { |
||
281 | $mergedConfig = $this->getThemeConfiguration($themeId, $translate, $context)['fields']; |
||
282 | |||
283 | $translations = []; |
||
284 | if ($translate) { |
||
285 | $translations = $this->getTranslations($themeId, $context); |
||
286 | $mergedConfig = $this->translateLabels($mergedConfig, $translations); |
||
287 | } |
||
288 | |||
289 | $blocks = []; |
||
290 | $noblocks = [ |
||
291 | 'label' => $this->getBlockLabel('unordered', $translations), |
||
292 | 'sections' => [], |
||
293 | ]; |
||
294 | |||
295 | foreach ($mergedConfig as $fieldName => $fieldConfig) { |
||
296 | $section = $this->getSection($fieldConfig); |
||
297 | |||
298 | if (!isset($fieldConfig['block'])) { |
||
299 | $noblocks['sections'][$section] = [ |
||
300 | 'label' => $this->getSectionLabel($section, $translations), |
||
301 | $fieldName => [ |
||
302 | 'label' => $fieldConfig['label'], |
||
303 | 'helpText' => $fieldConfig['helpText'] ?? null, |
||
304 | 'type' => $fieldConfig['type'], |
||
305 | 'custom' => $fieldConfig['custom'], |
||
306 | ], |
||
307 | ]; |
||
308 | } elseif (!isset($blocks[$fieldConfig['block']])) { |
||
309 | $blocks[$fieldConfig['block']] = [ |
||
310 | 'label' => $this->getBlockLabel($fieldConfig['block'], $translations), |
||
311 | 'sections' => [ |
||
312 | $section => [ |
||
313 | 'label' => $this->getSectionLabel($section, $translations), |
||
314 | $fieldName => [ |
||
315 | 'label' => $fieldConfig['label'], |
||
316 | 'helpText' => $fieldConfig['helpText'] ?? null, |
||
317 | 'type' => $fieldConfig['type'], |
||
318 | 'custom' => $fieldConfig['custom'], |
||
319 | ], |
||
320 | ], |
||
321 | ], |
||
322 | ]; |
||
323 | } elseif (isset($blocks[$fieldConfig['block']]['sections'][$section])) { |
||
324 | $blocks[$fieldConfig['block']]['sections'][$section][$fieldName] = [ |
||
325 | 'label' => $fieldConfig['label'], |
||
326 | 'helpText' => $fieldConfig['helpText'] ?? null, |
||
327 | 'type' => $fieldConfig['type'], |
||
328 | 'custom' => $fieldConfig['custom'], |
||
329 | ]; |
||
330 | } else { |
||
331 | $blocks[$fieldConfig['block']]['sections'][$section] = [ |
||
332 | 'label' => $this->getSectionLabel($section, $translations), |
||
333 | $fieldName => [ |
||
334 | 'label' => $fieldConfig['label'], |
||
335 | 'helpText' => $fieldConfig['helpText'] ?? null, |
||
336 | 'type' => $fieldConfig['type'], |
||
337 | 'custom' => $fieldConfig['custom'], |
||
338 | ], |
||
339 | ]; |
||
340 | } |
||
341 | } |
||
342 | |||
343 | $blocks['unordered'] = $noblocks; |
||
344 | |||
345 | return $blocks; |
||
346 | } |
||
347 | |||
348 | private function loadCompileConfig(string $themeId, Context $context): array |
||
362 | } |
||
363 | |||
364 | private function loadRecursiveConfig(string $themeId, Context $context): array |
||
365 | { |
||
366 | $criteria = new Criteria([$themeId]); |
||
367 | |||
368 | $theme = $this->themeRepository |
||
369 | ->search($criteria, $context) |
||
370 | ->first(); |
||
371 | |||
372 | if (!$theme instanceof ThemeEntity) { |
||
373 | throw new InvalidThemeException($themeId); |
||
374 | } |
||
375 | |||
376 | $config = $this->mergeStaticConfig($theme); |
||
377 | |||
378 | $parentId = $theme->getParentThemeId(); |
||
379 | if ($parentId) { |
||
380 | $parent = $this->loadRecursiveConfig($parentId, $context); |
||
381 | |||
382 | return array_replace_recursive($parent, $config); |
||
383 | } |
||
384 | |||
385 | $criteria = new Criteria(); |
||
386 | $criteria->addFilter(new EqualsFilter('technicalName', StorefrontPluginRegistry::BASE_THEME_NAME)); |
||
387 | |||
388 | $theme = $this->themeRepository |
||
389 | ->search($criteria, $context) |
||
390 | ->first(); |
||
391 | |||
392 | if (!$theme instanceof ThemeEntity) { |
||
393 | throw new InvalidThemeException(StorefrontPluginRegistry::BASE_THEME_NAME); |
||
394 | } |
||
395 | |||
396 | $base = $this->mergeStaticConfig($theme); |
||
397 | |||
398 | return array_replace_recursive($base, $config); |
||
399 | } |
||
400 | |||
401 | private function getPluginConfiguration(string $themeId, Context $context): StorefrontPluginConfiguration |
||
402 | { |
||
403 | $pluginConfig = $this->loadConfigByName($themeId, $context); |
||
404 | |||
405 | if (!$pluginConfig) { |
||
406 | throw new InvalidThemeException($themeId); |
||
407 | } |
||
408 | |||
409 | $pluginConfig = clone $pluginConfig; |
||
410 | |||
411 | $config = $this->loadCompileConfig($themeId, $context); |
||
412 | |||
413 | $pluginConfig->setThemeConfig($config); |
||
414 | |||
415 | return $pluginConfig; |
||
416 | } |
||
417 | |||
418 | private function loadConfigByName(string $themeId, Context $context): ?StorefrontPluginConfiguration |
||
419 | { |
||
420 | /** @var ThemeEntity|null $theme */ |
||
421 | $theme = $this->themeRepository |
||
422 | ->search(new Criteria([$themeId]), $context) |
||
423 | ->get($themeId); |
||
424 | |||
425 | if ($theme === null) { |
||
426 | return $this->pluginRegistry |
||
427 | ->getConfigurations() |
||
428 | ->getByTechnicalName(StorefrontPluginRegistry::BASE_THEME_NAME); |
||
429 | } |
||
430 | |||
431 | $pluginConfig = null; |
||
432 | if ($theme->getTechnicalName() !== null) { |
||
433 | $pluginConfig = $this->pluginRegistry |
||
434 | ->getConfigurations() |
||
435 | ->getByTechnicalName($theme->getTechnicalName()); |
||
436 | } |
||
437 | |||
438 | if ($pluginConfig !== null) { |
||
439 | return $pluginConfig; |
||
440 | } |
||
441 | |||
442 | if ($theme->getParentThemeId() !== null) { |
||
443 | $criteria = new Criteria(); |
||
444 | $criteria->addFilter(new EqualsFilter('id', $theme->getParentThemeId())); |
||
445 | |||
446 | /** @var ThemeEntity $parentTheme */ |
||
447 | $parentTheme = $this->themeRepository |
||
448 | ->search($criteria, $context) |
||
449 | ->first(); |
||
450 | |||
451 | return $this->pluginRegistry |
||
452 | ->getConfigurations() |
||
453 | ->getByTechnicalName($parentTheme->getTechnicalName()); |
||
454 | } |
||
455 | |||
456 | return $this->pluginRegistry |
||
457 | ->getConfigurations() |
||
458 | ->getByTechnicalName(StorefrontPluginRegistry::BASE_THEME_NAME); |
||
459 | } |
||
460 | |||
461 | private function mergeStaticConfig(ThemeEntity $theme): array |
||
462 | { |
||
463 | $configuredTheme = []; |
||
464 | |||
465 | $pluginConfig = null; |
||
466 | if ($theme->getTechnicalName()) { |
||
467 | $pluginConfig = $this->pluginRegistry->getConfigurations()->getByTechnicalName($theme->getTechnicalName()); |
||
468 | } |
||
469 | |||
470 | if ($pluginConfig !== null) { |
||
471 | $configuredTheme = $pluginConfig->getThemeConfig(); |
||
472 | } |
||
473 | |||
474 | if ($theme !== null && $theme->getBaseConfig() !== null) { |
||
475 | $configuredTheme = array_replace_recursive($configuredTheme, $theme->getBaseConfig()); |
||
476 | } |
||
477 | |||
478 | if ($theme !== null && $theme->getConfigValues() !== null) { |
||
479 | $configuredThemeFields = []; |
||
480 | if (array_key_exists('fields', $configuredTheme)) { |
||
481 | $configuredThemeFields = $configuredTheme['fields']; |
||
482 | } |
||
483 | $configuredTheme['fields'] = array_replace_recursive($configuredThemeFields, $theme->getConfigValues()); |
||
484 | } |
||
485 | |||
486 | return $configuredTheme; |
||
487 | } |
||
488 | |||
489 | private function getTab($fieldConfig): string |
||
490 | { |
||
491 | $tab = 'default'; |
||
492 | |||
493 | if (isset($fieldConfig['tab'])) { |
||
494 | $tab = $fieldConfig['tab']; |
||
495 | } |
||
496 | |||
497 | return $tab; |
||
498 | } |
||
499 | |||
500 | private function getBlock($fieldConfig): string |
||
501 | { |
||
502 | $block = 'default'; |
||
503 | |||
504 | if (isset($fieldConfig['block'])) { |
||
505 | $block = $fieldConfig['block']; |
||
506 | } |
||
507 | |||
508 | return $block; |
||
509 | } |
||
510 | |||
511 | private function getSection($fieldConfig): string |
||
520 | } |
||
521 | |||
522 | private function getTabLabel(string $tabName, array $translations) |
||
523 | { |
||
524 | if ($tabName === 'default') { |
||
525 | return ''; |
||
526 | } |
||
527 | |||
528 | return $translations['tabs.' . $tabName] ?? $tabName; |
||
529 | } |
||
530 | |||
531 | private function getBlockLabel(string $blockName, array $translations) |
||
538 | } |
||
539 | |||
540 | private function getSectionLabel(string $sectionName, array $translations) |
||
541 | { |
||
542 | if ($sectionName === 'default') { |
||
543 | return ''; |
||
544 | } |
||
545 | |||
546 | return $translations['sections.' . $sectionName] ?? $sectionName; |
||
547 | } |
||
548 | |||
549 | private function translateLabels(array $themeConfiguration, array $translations): array |
||
550 | { |
||
551 | foreach ($themeConfiguration as $key => &$value) { |
||
552 | $value['label'] = $translations['fields.' . $key] ?? $key; |
||
553 | } |
||
554 | |||
555 | return $themeConfiguration; |
||
556 | } |
||
557 | |||
558 | private function translateHelpTexts(array $themeConfiguration, array $translations): array |
||
559 | { |
||
560 | foreach ($themeConfiguration as $key => &$value) { |
||
561 | $value['helpText'] = $translations['fields.' . $key] ?? null; |
||
562 | } |
||
563 | |||
564 | return $themeConfiguration; |
||
565 | } |
||
566 | |||
567 | /** |
||
568 | * @throws InconsistentCriteriaIdsException |
||
569 | */ |
||
570 | private function getTranslations(string $themeId, Context $context): array |
||
588 | } |
||
589 | } |
||
590 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.