Total Complexity | 53 |
Total Lines | 385 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Complex classes like CartLineItemController 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 CartLineItemController, and based on these observations, apply Extract Interface, too.
1 | <?php declare(strict_types=1); |
||
33 | #[Route(defaults: ['_routeScope' => ['storefront']])] |
||
34 | #[Package('storefront')] |
||
35 | class CartLineItemController extends StorefrontController |
||
36 | { |
||
37 | /** |
||
38 | * @internal |
||
39 | */ |
||
40 | public function __construct( |
||
41 | private readonly CartService $cartService, |
||
42 | private readonly PromotionItemBuilder $promotionItemBuilder, |
||
43 | private readonly ProductLineItemFactory $productLineItemFactory, |
||
44 | private readonly HtmlSanitizer $htmlSanitizer, |
||
45 | private readonly AbstractProductListRoute $productListRoute, |
||
46 | private readonly LineItemFactoryRegistry $lineItemFactoryRegistry |
||
47 | ) { |
||
48 | } |
||
49 | |||
50 | #[Route(path: '/checkout/line-item/delete/{id}', name: 'frontend.checkout.line-item.delete', defaults: ['XmlHttpRequest' => true], methods: ['POST', 'DELETE'])] |
||
69 | }); |
||
70 | } |
||
71 | |||
72 | /** |
||
73 | * requires the provided items in the following form |
||
74 | * 'ids' => [ |
||
75 | * 'firstLineItemId', |
||
76 | * 'secondLineItemId', |
||
77 | * 'thirdLineItemId', |
||
78 | * ] |
||
79 | */ |
||
80 | #[Route(path: '/checkout/line-item/delete', name: 'frontend.checkout.line-items.delete', defaults: ['XmlHttpRequest' => true], methods: ['POST', 'DELETE'])] |
||
109 | }); |
||
110 | } |
||
111 | |||
112 | /** |
||
113 | * It has some individual code for the storefront layouts, like visual |
||
114 | * error and success messages. |
||
115 | */ |
||
116 | #[Route(path: '/checkout/promotion/add', name: 'frontend.checkout.promotion.add', defaults: ['XmlHttpRequest' => true], methods: ['POST'])] |
||
117 | public function addPromotion(Cart $cart, Request $request, SalesChannelContext $context): Response |
||
118 | { |
||
119 | return Profiler::trace('cart::add-promotion', function () use ($cart, $request, $context) { |
||
120 | try { |
||
121 | $code = (string) $request->request->get('code'); |
||
122 | |||
123 | if ($code === '') { |
||
124 | throw RoutingException::missingRequestParameter('code'); |
||
125 | } |
||
126 | |||
127 | $lineItem = $this->promotionItemBuilder->buildPlaceholderItem($code); |
||
128 | |||
129 | $cart = $this->cartService->add($cart, $lineItem, $context); |
||
130 | |||
131 | // we basically show all cart errors or notices |
||
132 | // at the moments its not possible to show success messages with "green" color |
||
133 | // from the cart...thus it has to be done in the storefront level |
||
134 | // so if we have an promotion added notice, we simply convert this to |
||
135 | // a success flash message |
||
136 | $addedEvents = $cart->getErrors()->filterInstance(PromotionCartAddedInformationError::class); |
||
137 | if ($addedEvents->count() > 0) { |
||
138 | $this->addFlash(self::SUCCESS, $this->trans('checkout.codeAddedSuccessful')); |
||
139 | |||
140 | return $this->createActionResponse($request); |
||
141 | } |
||
142 | |||
143 | // if we have no custom error message above |
||
144 | // then simply continue with the default display |
||
145 | // of the cart errors and notices |
||
146 | $this->traceErrors($cart); |
||
147 | } catch (\Exception) { |
||
148 | $this->addFlash(self::DANGER, $this->trans('error.message-default')); |
||
149 | } |
||
150 | |||
151 | return $this->createActionResponse($request); |
||
152 | }); |
||
153 | } |
||
154 | |||
155 | #[Route(path: '/checkout/line-item/change-quantity/{id}', name: 'frontend.checkout.line-item.change-quantity', defaults: ['XmlHttpRequest' => true], methods: ['POST'])] |
||
180 | }); |
||
181 | } |
||
182 | |||
183 | /** |
||
184 | * requires the provided items in the following form |
||
185 | * 'lineItems' => [ |
||
186 | * 'anyKey' => [ |
||
187 | * 'id' => 'someKey' |
||
188 | * 'quantity' => 2, |
||
189 | * ], |
||
190 | * 'randomKey' => [ |
||
191 | * 'id' => 'otherKey' |
||
192 | * 'quantity' => 2, |
||
193 | * ] |
||
194 | * ] |
||
195 | */ |
||
196 | #[Route(path: '/checkout/line-item/update', name: 'frontend.checkout.line-items.update', defaults: ['XmlHttpRequest' => true], methods: ['POST', 'PATCH'])] |
||
221 | }); |
||
222 | } |
||
223 | |||
224 | #[Route(path: '/checkout/product/add-by-number', name: 'frontend.checkout.product.add-by-number', methods: ['POST'])] |
||
263 | }); |
||
264 | } |
||
265 | |||
266 | /** |
||
267 | * requires the provided items in the following form |
||
268 | * 'lineItems' => [ |
||
269 | * 'anyKey' => [ |
||
270 | * 'id' => 'someKey' |
||
271 | * 'quantity' => 2, |
||
272 | * 'type' => 'someType' |
||
273 | * ], |
||
274 | * 'randomKey' => [ |
||
275 | * 'id' => 'otherKey' |
||
276 | * 'quantity' => 2, |
||
277 | * 'type' => 'otherType' |
||
278 | * ] |
||
279 | * ] |
||
280 | */ |
||
281 | #[Route(path: '/checkout/line-item/add', name: 'frontend.checkout.line-item.add', defaults: ['XmlHttpRequest' => true], methods: ['POST'])] |
||
282 | public function addLineItems(Cart $cart, RequestDataBag $requestDataBag, Request $request, SalesChannelContext $context): Response |
||
283 | { |
||
284 | return Profiler::trace('cart::add-line-item', function () use ($cart, $requestDataBag, $request, $context) { |
||
285 | /** @var RequestDataBag|null $lineItems */ |
||
286 | $lineItems = $requestDataBag->get('lineItems'); |
||
287 | if (!$lineItems) { |
||
288 | throw RoutingException::missingRequestParameter('lineItems'); |
||
289 | } |
||
290 | |||
291 | $count = 0; |
||
292 | |||
293 | try { |
||
294 | $items = []; |
||
295 | /** @var RequestDataBag $lineItemData */ |
||
296 | foreach ($lineItems as $lineItemData) { |
||
297 | try { |
||
298 | $item = $this->lineItemFactoryRegistry->create($this->getLineItemArray($lineItemData, [ |
||
299 | 'quantity' => 1, |
||
300 | 'stackable' => true, |
||
301 | 'removable' => true, |
||
302 | ]), $context); |
||
303 | $count += $item->getQuantity(); |
||
304 | |||
305 | $items[] = $item; |
||
306 | } catch (CartException $e) { |
||
307 | if ($e->getErrorCode() === CartException::CART_INVALID_LINE_ITEM_QUANTITY_CODE) { |
||
308 | $this->addFlash( |
||
309 | self::DANGER, |
||
310 | $this->trans( |
||
311 | 'error.CHECKOUT__CART_INVALID_LINE_ITEM_QUANTITY', |
||
312 | [ |
||
313 | '%quantity%' => $e->getParameter('quantity'), |
||
314 | ] |
||
315 | ) |
||
316 | ); |
||
317 | |||
318 | return $this->createActionResponse($request); |
||
319 | } |
||
320 | |||
321 | if ($e->getErrorCode() !== CartException::CART_LINE_ITEM_TYPE_NOT_SUPPORTED_CODE) { |
||
322 | throw $e; |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * @deprecated tag:v6.6.0 - remove complete catch below and just leave the try content |
||
327 | */ |
||
328 | Feature::triggerDeprecationOrThrow( |
||
329 | 'v6.6.0.0', |
||
330 | 'With Shopware 6.6.0.0, you will only be able to create line items only with registered LineItemFactories', |
||
331 | ); |
||
332 | |||
333 | $lineItem = new LineItem( |
||
334 | $lineItemData->getAlnum('id'), |
||
335 | $lineItemData->getAlnum('type'), |
||
336 | $lineItemData->get('referencedId'), |
||
337 | $lineItemData->getInt('quantity', 1) |
||
338 | ); |
||
339 | |||
340 | $lineItem->setStackable($lineItemData->getBoolean('stackable', true)); |
||
341 | $lineItem->setRemovable($lineItemData->getBoolean('removable', true)); |
||
342 | |||
343 | $count += $lineItem->getQuantity(); |
||
344 | |||
345 | $items[] = $lineItem; |
||
346 | } |
||
347 | } |
||
348 | |||
349 | $cart = $this->cartService->add($cart, $items, $context); |
||
350 | |||
351 | if (!$this->traceErrors($cart)) { |
||
352 | $this->addFlash(self::SUCCESS, $this->trans('checkout.addToCartSuccess', ['%count%' => $count])); |
||
353 | } |
||
354 | } catch (ProductNotFoundException|RoutingException) { |
||
355 | $this->addFlash(self::DANGER, $this->trans('error.addToCartError')); |
||
356 | } |
||
357 | |||
358 | return $this->createActionResponse($request); |
||
359 | }); |
||
360 | } |
||
361 | |||
362 | private function traceErrors(Cart $cart): bool |
||
363 | { |
||
364 | if ($cart->getErrors()->count() <= 0) { |
||
365 | return false; |
||
366 | } |
||
367 | |||
368 | $this->addCartErrors($cart, fn (Error $error) => $error->isPersistent()); |
||
369 | |||
370 | return true; |
||
371 | } |
||
372 | |||
373 | /** |
||
374 | * @param array{quantity?: int, stackable?: bool, removable?: bool} $defaultValues |
||
375 | * |
||
376 | * @return array<string|int, mixed> |
||
377 | */ |
||
378 | private function getLineItemArray(RequestDataBag $lineItemData, ?array $defaultValues): array |
||
418 | } |
||
419 | } |
||
420 |