77use Shopware \Core \Framework \DataAbstractionLayer \EntityRepository ;
88use Shopware \Core \Framework \DataAbstractionLayer \Search \Criteria ;
99use Shopware \Core \Framework \DataAbstractionLayer \Search \Filter \ContainsFilter ;
10+ use Shopware \Core \Framework \DataAbstractionLayer \Search \Filter \EqualsAnyFilter ;
1011use Shopware \Core \Framework \DataAbstractionLayer \Search \Filter \EqualsFilter ;
11- use Shopware \Core \Framework \DataAbstractionLayer \Search \Filter \NotFilter ;
12- use Shopware \Core \Framework \DataAbstractionLayer \Search \Grouping \FieldGrouping ;
13- use Shopware \Core \Framework \DataAbstractionLayer \Search \Sorting \FieldSorting ;
14- use Shopware \Core \Framework \Struct \ArrayEntity ;
1512use Shopware \Core \System \SalesChannel \Entity \SalesChannelRepository ;
1613use Shopware \Core \System \SalesChannel \SalesChannelContext ;
17- use Shopware \Core \Framework \DataAbstractionLayer \ Search \ Filter \ EqualsAnyFilter ;
14+ use Shopware \Core \Framework \Struct \ ArrayEntity ;
1815use Sidworks \ProductLabels \Core \Content \ProductLabels \ProductLabelsEntity ;
1916
2017class LabelStreamService
2118{
2219 private const SIDWORKS_PRODUCT_LABELS_EXTENSION = 'sidworksProductLabels ' ;
2320
2421 private array $ labelCache = [];
22+ private array $ variantMappingCache = [];
23+ private array $ streamMatchCache = [];
2524
2625 public function __construct (
2726 private readonly EntityRepository $ productLabelsRepository ,
@@ -31,151 +30,210 @@ public function __construct(
3130
3231 public function getProductLabelStreamProducts (array $ productIds , SalesChannelContext $ context ): array
3332 {
34- $ cacheKey = md5 (implode (', ' , $ productIds ) . $ context ->getSalesChannelId ());
35-
33+ $ cacheKey = $ this ->getCacheKey ($ productIds , $ context ->getSalesChannelId ());
3634 if (isset ($ this ->labelCache [$ cacheKey ])) {
3735 return $ this ->labelCache [$ cacheKey ];
3836 }
3937
4038 $ productLabels = $ this ->fetchActiveProductLabels ($ context ->getContext ());
41-
4239 $ productLabelStreamItems = [];
4340
41+ if (!$ productLabels ) {
42+ return [];
43+ }
44+
45+ // Pre-fetch variant mapping once for all labels
46+ $ variantToParent = $ this ->getVariantToParentMapping ($ productIds , $ context );
47+
4448 foreach ($ productLabels as $ productLabel ) {
4549 if (!$ this ->shouldShowProductLabel ($ productLabel )) {
4650 continue ;
4751 }
4852
49- $ matchedIds = $ this ->getMatchedProductIds ($ productLabel , $ productIds , $ context );
50-
53+ $ matchedIds = $ this ->getMatchedProductIds ($ productLabel , $ productIds , $ context , $ variantToParent );
5154 if (empty ($ matchedIds )) {
5255 continue ;
5356 }
5457
5558 $ productLabelStreamItems [] = [
5659 'label ' => $ productLabel ,
57- 'matchedProductIds ' => array_values ( array_unique ( $ matchedIds)) ,
60+ 'matchedProductIds ' => $ matchedIds ,
5861 ];
5962 }
6063
6164 $ this ->labelCache [$ cacheKey ] = $ productLabelStreamItems ;
62-
6365 return $ productLabelStreamItems ;
6466 }
6567
6668 public function applyLabelsToProduct ($ product , array $ productLabelsStreamProducts ): void
6769 {
68- foreach ($ productLabelsStreamProducts as $ productLabelsStreamProduct ) {
69- $ matchedIds = $ productLabelsStreamProduct ['matchedProductIds ' ] ?? [];
70- $ label = $ productLabelsStreamProduct ['label ' ];
71- $ labelId = $ label ->getId ();
70+ $ productId = $ product ->getId ();
71+ $ sidworksProductLabels = $ product ->getExtension (self ::SIDWORKS_PRODUCT_LABELS_EXTENSION ) ?? new ArrayEntity ();
7272
73- $ matchedMap = array_flip ( $ matchedIds );
74- if (!isset ( $ matchedMap [ $ product -> getId ()] )) {
73+ foreach ( $ productLabelsStreamProducts as $ productLabelsStreamProduct ) {
74+ if (!in_array ( $ productId , $ productLabelsStreamProduct [ ' matchedProductIds ' ], true )) {
7575 continue ;
7676 }
7777
78- /** @var ArrayEntity $sidworksProductLabels */
79- $ sidworksProductLabels = $ product -> getExtension ( self :: SIDWORKS_PRODUCT_LABELS_EXTENSION ) ?? new ArrayEntity ( );
80- $ sidworksProductLabels -> set ( $ labelId , $ label );
78+ $ label = $ productLabelsStreamProduct [ ' label ' ];
79+ $ sidworksProductLabels-> set ( $ label -> getId (), $ label );
80+ }
8181
82+ if ($ sidworksProductLabels ->all ()) {
8283 $ product ->addExtension (self ::SIDWORKS_PRODUCT_LABELS_EXTENSION , $ sidworksProductLabels );
8384 }
8485 }
8586
8687 private function fetchActiveProductLabels (Context $ context ): iterable
8788 {
89+ $ salesChannelId = $ context ->getSource ()->getSalesChannelId ();
90+ $ cacheKey = "active_labels_ {$ salesChannelId }" ;
91+
92+ if (isset ($ this ->labelCache [$ cacheKey ])) {
93+ return $ this ->labelCache [$ cacheKey ];
94+ }
95+
8896 $ criteria = new Criteria ();
8997 $ criteria ->addFilter (new EqualsFilter ('active ' , 1 ));
90- $ criteria ->addFilter (new ContainsFilter ('salesChannelIds ' , $ context -> getSource ()-> getSalesChannelId () ));
98+ $ criteria ->addFilter (new ContainsFilter ('salesChannelIds ' , $ salesChannelId ));
9199
92- return $ this ->productLabelsRepository ->search ($ criteria , $ context )->getEntities ();
100+ $ result = $ this ->productLabelsRepository ->search ($ criteria , $ context )->getEntities ();
101+ $ this ->labelCache [$ cacheKey ] = $ result ;
102+
103+ return $ result ;
93104 }
94105
95- private function getMatchedProductIds (ProductLabelsEntity $ productLabel , array $ productIds , SalesChannelContext $ context ): array
96- {
106+ private function getMatchedProductIds (
107+ ProductLabelsEntity $ productLabel ,
108+ array $ productIds ,
109+ SalesChannelContext $ context ,
110+ array $ variantToParent
111+ ): array {
97112 $ matchedIds = [];
98113
99- $ selectedProducts = $ productLabel ->getSelectedProducts () ?? [];
100- $ selectedMatches = array_intersect ($ productIds , $ selectedProducts );
101- if (!empty ($ selectedMatches )) {
102- $ matchedIds = array_flip ($ selectedMatches );
114+ // Handle selected products
115+ if ($ selectedProducts = $ productLabel ->getSelectedProducts ()) {
116+ $ matchedIds = array_flip (array_intersect ($ productIds , $ selectedProducts ));
103117 }
104118
105- $ filters = null ;
106- if ($ productLabel ->getProductStreamId ()) {
107- $ filters = $ this ->productStreamBuilder ->buildFilters (
108- $ productLabel ->getProductStreamId (),
109- $ context ->getContext ()
110- );
119+ // Handle product stream
120+ if ($ streamId = $ productLabel ->getProductStreamId ()) {
121+ $ streamMatches = $ this ->matchProductsByStream ($ streamId , $ productIds , $ context );
122+ $ matchedIds = array_merge ($ matchedIds , $ streamMatches );
123+ }
111124
112- $ productIds [] = '18e3f68e6de24264b6e837098d16ac1c ' ;
113- $ criteria = new Criteria ($ productIds );
114- $ criteria ->addFilter (...$ filters );
115- $ criteria ->addFields (['id ' ]);
125+ // Handle variants
126+ if ($ this ->shouldProcessVariants ($ selectedProducts , $ productLabel ->getProductStreamId ())) {
127+ $ variantMatches = $ this ->processVariantMatches ($ selectedProducts , $ streamId , $ variantToParent , $ context );
128+ $ matchedIds = array_merge ($ matchedIds , $ variantMatches );
129+ }
130+
131+ return array_keys ($ matchedIds );
132+ }
116133
117- $ streamProducts = $ this ->productRepository ->search ($ criteria , $ context );
118- $ streamProductIds = $ streamProducts ->getEntities ()->getIds ();
134+ private function processVariantMatches (
135+ ?array $ selectedProducts ,
136+ ?string $ streamId ,
137+ array $ variantToParent ,
138+ SalesChannelContext $ context
139+ ): array {
140+ $ matchedIds = [];
119141
120- foreach ($ streamProductIds as $ id ) {
121- $ matchedIds [$ id ] = true ;
142+ if (!empty ($ selectedProducts )) {
143+ foreach (array_intersect (array_keys ($ variantToParent ), $ selectedProducts ) as $ variantId ) {
144+ $ matchedIds [$ variantToParent [$ variantId ]] = true ;
122145 }
123146 }
124147
125- if ($ this ->shouldProcessVariants ($ selectedProducts , $ productLabel ->getProductStreamId ())) {
126- $ variantToParent = $ this ->getVariantToParentMapping ($ productIds , $ context );
127-
128- if (!empty ($ variantToParent )) {
129- if (!empty ($ selectedProducts )) {
130- $ variantMatches = array_intersect (array_keys ($ variantToParent ), $ selectedProducts );
131- foreach ($ variantMatches as $ variantId ) {
132- $ matchedIds [$ variantToParent [$ variantId ]] = true ;
133- }
134- }
135-
136- if ($ filters !== null ) {
137- $ variantIds = array_keys ($ variantToParent );
138- $ variantStreamCriteria = new Criteria ($ variantIds );
139- $ variantStreamCriteria ->addFilter (...$ filters );
140- $ variantStreamCriteria ->addFields (['id ' ]);
141-
142- $ variantStreamProducts = $ this ->productRepository ->search ($ variantStreamCriteria , $ context );
143-
144- foreach ($ variantStreamProducts ->getIds () as $ variantId ) {
145- $ matchedIds [$ variantToParent [$ variantId ]] = true ;
146- }
147- }
148+ if ($ streamId && !empty ($ variantToParent )) {
149+ $ streamVariantMatches = $ this ->matchVariantsByStream ($ streamId , array_keys ($ variantToParent ), $ variantToParent , $ context );
150+ $ matchedIds = array_merge ($ matchedIds , $ streamVariantMatches );
151+ }
152+
153+ return $ matchedIds ;
154+ }
155+
156+ private function matchProductsByStream (string $ streamId , array $ productIds , SalesChannelContext $ context ): array
157+ {
158+ $ cacheKey = $ this ->getStreamCacheKey ($ streamId , $ productIds , $ context ->getSalesChannelId ());
159+
160+ if (isset ($ this ->streamMatchCache [$ cacheKey ])) {
161+ return $ this ->streamMatchCache [$ cacheKey ];
162+ }
163+
164+ $ filters = $ this ->productStreamBuilder ->buildFilters ($ streamId , $ context ->getContext ());
165+ $ criteria = new Criteria ($ this ->ensureMinProductIds ($ productIds ));
166+ $ criteria ->addFilter (...$ filters )->addFields (['id ' ]);
167+
168+ $ matchedIds = [];
169+ foreach ($ this ->productRepository ->search ($ criteria , $ context )->getIds () as $ id ) {
170+ $ matchedIds [$ id ] = true ;
171+ }
172+
173+ $ this ->streamMatchCache [$ cacheKey ] = $ matchedIds ;
174+ return $ matchedIds ;
175+ }
176+
177+ private function matchVariantsByStream (string $ streamId , array $ variantIds , array $ variantToParent , SalesChannelContext $ context ): array
178+ {
179+ $ cacheKey = $ this ->getStreamCacheKey ($ streamId , $ variantIds , $ context ->getSalesChannelId ()) . '_variants ' ;
180+
181+ if (isset ($ this ->streamMatchCache [$ cacheKey ])) {
182+ return $ this ->streamMatchCache [$ cacheKey ];
183+ }
184+
185+ $ filters = $ this ->productStreamBuilder ->buildFilters ($ streamId , $ context ->getContext ());
186+ $ criteria = new Criteria ($ variantIds );
187+ $ criteria ->addFilter (...$ filters )->addFields (['id ' ]);
188+
189+ $ matchedIds = [];
190+ foreach ($ this ->productRepository ->search ($ criteria , $ context )->getIds () as $ variantId ) {
191+ if (isset ($ variantToParent [$ variantId ])) {
192+ $ matchedIds [$ variantToParent [$ variantId ]] = true ;
148193 }
149194 }
150195
151- return array_keys ($ matchedIds );
196+ $ this ->streamMatchCache [$ cacheKey ] = $ matchedIds ;
197+ return $ matchedIds ;
152198 }
153199
154- private function shouldProcessVariants (array $ selectedProducts , ?string $ productStreamId ): bool
200+ private function shouldProcessVariants (? array $ selectedProducts , ?string $ productStreamId ): bool
155201 {
156202 return !empty ($ selectedProducts ) || $ productStreamId !== null ;
157203 }
158204
159205 private function getVariantToParentMapping (array $ productIds , SalesChannelContext $ context ): array
160206 {
161- $ variantCriteria = new Criteria ();
162- $ variantCriteria -> addFilter ( new EqualsAnyFilter ( ' parentId ' , $ productIds )) ;
163- $ variantCriteria -> addFields ([ ' id ' , ' parentId ' ]);
207+ if ( empty ( $ productIds )) {
208+ return [] ;
209+ }
164210
165- $ variantProducts = $ this ->productRepository -> search ( $ variantCriteria , $ context) ;
211+ $ cacheKey = $ this ->getCacheKey ( $ productIds , $ context-> getSalesChannelId ()) . ' _variants ' ;
166212
167- if ($ variantProducts -> count () === 0 ) {
168- return [ ];
213+ if (isset ( $ this -> variantMappingCache [ $ cacheKey ]) ) {
214+ return $ this -> variantMappingCache [ $ cacheKey ];
169215 }
170216
171- $ variantToParent = [];
217+ $ criteria = new Criteria ();
218+ $ criteria ->addFilter (new EqualsAnyFilter ('parentId ' , $ productIds ));
219+ $ criteria ->addFields (['id ' , 'parentId ' ]);
220+
221+ $ variantProducts = $ this ->productRepository ->search ($ criteria , $ context );
222+
223+ $ mapping = [];
172224 foreach ($ variantProducts ->getEntities () as $ variant ) {
173- $ variantId = $ variant ->get ('id ' );
174- $ parentId = $ variant ->get ('parentId ' );
175- $ variantToParent [$ variantId ] = $ parentId ;
225+ $ mapping [$ variant ->get ('id ' )] = $ variant ->get ('parentId ' );
176226 }
177227
178- return $ variantToParent ;
228+ $ this ->variantMappingCache [$ cacheKey ] = $ mapping ;
229+ return $ mapping ;
230+ }
231+
232+ private function ensureMinProductIds (array $ productIds ): array
233+ {
234+ return count ($ productIds ) === 1
235+ ? array_merge ($ productIds , ['00000000000000000000000000000000 ' ])
236+ : $ productIds ;
179237 }
180238
181239 public function shouldShowProductLabel (ProductLabelsEntity $ productLabel ): bool
@@ -191,8 +249,19 @@ public function shouldShowProductLabel(ProductLabelsEntity $productLabel): bool
191249 return match (true ) {
192250 $ from && $ from > $ now => false ,
193251 $ to && $ to < $ now => false ,
194- $ from && $ from <= $ now && (!$ to || $ to >= $ now ) => true ,
195- default => $ productLabel ->getActive ()
252+ default => true ,
196253 };
197254 }
255+
256+ private function getCacheKey (array $ productIds , string $ salesChannelId ): string
257+ {
258+ sort ($ productIds ); // Ensure consistent ordering for cache keys
259+ return md5 (implode (', ' , $ productIds ) . $ salesChannelId );
260+ }
261+
262+ private function getStreamCacheKey (string $ streamId , array $ productIds , string $ salesChannelId ): string
263+ {
264+ sort ($ productIds );
265+ return md5 ($ streamId . implode (', ' , $ productIds ) . $ salesChannelId );
266+ }
198267}
0 commit comments