Passed
Push — trunk ( 7ae7db...d8d8b1 )
by Christian
12:59 queued 14s
created

CachedLandingPageRoute::load()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 12
nc 3
nop 3
dl 0
loc 23
rs 9.8666
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\Content\LandingPage\SalesChannel;
4
5
use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
0 ignored issues
show
Bug introduced by
The type Shopware\Core\Content\Cm...e\CmsSlot\CmsSlotEntity was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use Shopware\Core\Content\Cms\SalesChannel\Struct\ProductBoxStruct;
7
use Shopware\Core\Content\Cms\SalesChannel\Struct\ProductSliderStruct;
8
use Shopware\Core\Content\LandingPage\Event\LandingPageRouteCacheKeyEvent;
9
use Shopware\Core\Content\LandingPage\Event\LandingPageRouteCacheTagsEvent;
10
use Shopware\Core\Framework\Adapter\Cache\AbstractCacheTracer;
11
use Shopware\Core\Framework\Adapter\Cache\CacheValueCompressor;
12
use Shopware\Core\Framework\DataAbstractionLayer\Cache\EntityCacheKeyGenerator;
13
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\RuleAreas;
14
use Shopware\Core\Framework\Log\Package;
15
use Shopware\Core\Framework\Util\Json;
16
use Shopware\Core\System\SalesChannel\SalesChannelContext;
17
use Symfony\Component\HttpFoundation\Request;
18
use Symfony\Component\Routing\Annotation\Route;
19
use Symfony\Contracts\Cache\CacheInterface;
20
use Symfony\Contracts\Cache\ItemInterface;
21
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
22
23
#[Route(defaults: ['_routeScope' => ['store-api']])]
24
#[Package('content')]
25
class CachedLandingPageRoute extends AbstractLandingPageRoute
26
{
27
    /**
28
     * @internal
29
     *
30
     * @param AbstractCacheTracer<LandingPageRouteResponse> $tracer
31
     * @param array<string> $states
32
     */
33
    public function __construct(
34
        private readonly AbstractLandingPageRoute $decorated,
35
        private readonly CacheInterface $cache,
36
        private readonly EntityCacheKeyGenerator $generator,
37
        private readonly AbstractCacheTracer $tracer,
38
        private readonly EventDispatcherInterface $dispatcher,
39
        private readonly array $states
40
    ) {
41
    }
42
43
    public static function buildName(string $id): string
44
    {
45
        return 'landing-page-route-' . $id;
46
    }
47
48
    public function getDecorated(): AbstractLandingPageRoute
49
    {
50
        return $this->decorated;
51
    }
52
53
    #[Route(path: '/store-api/landing-page/{landingPageId}', name: 'store-api.landing-page.detail', methods: ['POST'])]
54
    public function load(string $landingPageId, Request $request, SalesChannelContext $context): LandingPageRouteResponse
55
    {
56
        if ($context->hasState(...$this->states)) {
57
            return $this->getDecorated()->load($landingPageId, $request, $context);
58
        }
59
60
        $key = $this->generateKey($landingPageId, $request, $context);
61
62
        if ($key === null) {
63
            return $this->getDecorated()->load($landingPageId, $request, $context);
64
        }
65
66
        $value = $this->cache->get($key, function (ItemInterface $item) use ($request, $context, $landingPageId) {
67
            $name = self::buildName($landingPageId);
68
            $response = $this->tracer->trace($name, fn () => $this->getDecorated()->load($landingPageId, $request, $context));
69
70
            $item->tag($this->generateTags($landingPageId, $response, $request, $context));
71
72
            return CacheValueCompressor::compress($response);
73
        });
74
75
        return CacheValueCompressor::uncompress($value);
76
    }
77
78
    private function generateKey(string $landingPageId, Request $request, SalesChannelContext $context): ?string
79
    {
80
        $parts = [...$request->query->all(), ...$request->request->all(), ...[$this->generator->getSalesChannelContextHash($context, [RuleAreas::LANDING_PAGE_AREA, RuleAreas::PRODUCT_AREA, RuleAreas::CATEGORY_AREA])]];
81
82
        $event = new LandingPageRouteCacheKeyEvent($landingPageId, $parts, $request, $context, null);
83
        $this->dispatcher->dispatch($event);
84
85
        if (!$event->shouldCache()) {
86
            return null;
87
        }
88
89
        return self::buildName($landingPageId) . '-' . md5(Json::encode($event->getParts()));
90
    }
91
92
    /**
93
     * @return array<string>
94
     */
95
    private function generateTags(string $landingPageId, LandingPageRouteResponse $response, Request $request, SalesChannelContext $context): array
96
    {
97
        $tags = array_merge(
98
            $this->tracer->get(self::buildName($landingPageId)),
99
            $this->extractIds($response),
100
            [self::buildName($landingPageId)]
101
        );
102
103
        $event = new LandingPageRouteCacheTagsEvent($landingPageId, $tags, $request, $response, $context, null);
104
        $this->dispatcher->dispatch($event);
105
106
        return array_unique(array_filter($event->getTags()));
107
    }
108
109
    /**
110
     * @return array<string>
111
     */
112
    private function extractIds(LandingPageRouteResponse $response): array
113
    {
114
        $page = $response->getLandingPage()->getCmsPage();
115
116
        if ($page === null) {
117
            return [];
118
        }
119
120
        $ids = [];
121
        $streamIds = [];
122
123
        $slots = $page->getElementsOfType('product-slider');
124
        /** @var CmsSlotEntity $slot */
125
        foreach ($slots as $slot) {
126
            $slider = $slot->getData();
127
128
            if (!$slider instanceof ProductSliderStruct) {
129
                continue;
130
            }
131
132
            if ($slider->getStreamId() !== null) {
133
                $streamIds[] = $slider->getStreamId();
134
            }
135
136
            if ($slider->getProducts() === null) {
137
                continue;
138
            }
139
            foreach ($slider->getProducts() as $product) {
140
                $ids[] = $product->getId();
141
                $ids[] = $product->getParentId();
142
            }
143
        }
144
145
        $slots = $page->getElementsOfType('product-box');
146
        /** @var CmsSlotEntity $slot */
147
        foreach ($slots as $slot) {
148
            $box = $slot->getData();
149
150
            if (!$box instanceof ProductBoxStruct) {
151
                continue;
152
            }
153
            if ($box->getProduct() === null) {
154
                continue;
155
            }
156
157
            $ids[] = $box->getProduct()->getId();
158
            $ids[] = $box->getProduct()->getParentId();
159
        }
160
161
        $ids = array_values(array_unique(array_filter($ids)));
162
163
        return [...array_map(EntityCacheKeyGenerator::buildProductTag(...), $ids), ...array_map(EntityCacheKeyGenerator::buildStreamTag(...), $streamIds), ...[EntityCacheKeyGenerator::buildCmsTag($page->getId())]];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array(array_map(S...msTag($page->getId()))) returns the type array<integer,array|array<integer,string>> which is incompatible with the documented return type string[].
Loading history...
164
    }
165
}
166