| 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 |