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