vendor/shopware/core/Checkout/Cart/LineItem/Group/LineItemGroupBuilder.php line 15

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Checkout\Cart\LineItem\Group;
  3. use Shopware\Core\Checkout\Cart\Cart;
  4. use Shopware\Core\Checkout\Cart\Exception\InvalidQuantityException;
  5. use Shopware\Core\Checkout\Cart\Exception\LineItemNotStackableException;
  6. use Shopware\Core\Checkout\Cart\LineItem\LineItemCollection;
  7. use Shopware\Core\Checkout\Cart\LineItem\LineItemFlatCollection;
  8. use Shopware\Core\Checkout\Cart\LineItem\LineItemQuantitySplitter;
  9. use Shopware\Core\Framework\Log\Package;
  10. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  11. #[Package('checkout')]
  12. class LineItemGroupBuilder
  13. {
  14.     private LineItemGroupServiceRegistry $registry;
  15.     private LineItemGroupRuleMatcherInterface $ruleMatcher;
  16.     private LineItemQuantitySplitter $quantitySplitter;
  17.     private AbstractProductLineItemProvider $lineItemProvider;
  18.     /**
  19.      * @internal
  20.      */
  21.     public function __construct(
  22.         LineItemGroupServiceRegistry $registry,
  23.         LineItemGroupRuleMatcherInterface $ruleMatcher,
  24.         LineItemQuantitySplitter $lineItemQuantitySplitter,
  25.         AbstractProductLineItemProvider $lineItemProvider
  26.     ) {
  27.         $this->registry $registry;
  28.         $this->ruleMatcher $ruleMatcher;
  29.         $this->quantitySplitter $lineItemQuantitySplitter;
  30.         $this->lineItemProvider $lineItemProvider;
  31.     }
  32.     /**
  33.      * Searches for all packages that can be built from the provided list of groups.
  34.      * Every line item will be taken from the cart and only the ones that are left will
  35.      * be checked for upcoming groups.
  36.      *
  37.      * @param LineItemGroupDefinition[] $groupDefinitions
  38.      */
  39.     public function findGroupPackages(array $groupDefinitionsCart $cartSalesChannelContext $context): LineItemGroupBuilderResult
  40.     {
  41.         $result = new LineItemGroupBuilderResult();
  42.         // filter out all promotion items
  43.         $cartProducts $this->lineItemProvider->getProducts($cart);
  44.         // split quantities into separate line items
  45.         // so we have a real list of products like we would have
  46.         // them when holding it in our actual hands.
  47.         $restOfCart $this->splitQuantities($cartProducts$context);
  48.         foreach ($groupDefinitions as $groupDefinition) {
  49.             $sorter $this->registry->getSorter($groupDefinition->getSorterKey());
  50.             $packager $this->registry->getPackager($groupDefinition->getPackagerKey());
  51.             // we have to sort our items first
  52.             // otherwise it would be a "random" order when
  53.             // adjusting the rest of our cart...
  54.             $restOfCart $sorter->sort($restOfCart);
  55.             // try as long as groups can be
  56.             // found for the current definition
  57.             while (true) {
  58.                 $itemsToConsider $this->ruleMatcher->getMatchingItems($groupDefinition$restOfCart$context);
  59.                 // now build a package with our packager
  60.                 $group $packager->buildGroupPackage($groupDefinition->getValue(), $itemsToConsider$context);
  61.                 // if we have no found items in our group, quit
  62.                 if (!$group->hasItems()) {
  63.                     break;
  64.                 }
  65.                 // append the currently found group of items
  66.                 // to our group definition inside our result object
  67.                 $result->addGroup($groupDefinition$group);
  68.                 // decrease rest of cart items for next search
  69.                 $restOfCart $this->adjustRestOfCart($group->getItems(), $restOfCart);
  70.             }
  71.         }
  72.         return $result;
  73.     }
  74.     /**
  75.      * This is a very important function.
  76.      * It removes our line items that are found in the group and returns the rest of the cart items.
  77.      * So if we have 4 line items of 2 products with each quantity 1, and want to remove a product with qt 2,
  78.      * then 2 line items will be removed and the new rest of the cart is being returned.
  79.      *
  80.      * @param LineItemQuantity[] $foundItems
  81.      */
  82.     private function adjustRestOfCart(array $foundItemsLineItemFlatCollection $restOfCart): LineItemFlatCollection
  83.     {
  84.         // a holder for all foundItems indexed by lineItemId
  85.         /** @var LineItemQuantity[] $lineItemsToRemove */
  86.         $lineItemsToRemove = [];
  87.         // we prepare the removeLineItemIds array with all LineItemQuantity objects indexed by lineItemId
  88.         foreach ($foundItems as $itemToRemove) {
  89.             if (isset($lineItemsToRemove[$itemToRemove->getLineItemId()])) {
  90.                 $quantity $lineItemsToRemove[$itemToRemove->getLineItemId()];
  91.                 $lineItemsToRemove[$itemToRemove->getLineItemId()]->setQuantity($quantity->getQuantity() + $itemToRemove->getQuantity());
  92.                 continue;
  93.             }
  94.             $lineItemsToRemove[$itemToRemove->getLineItemId()] = $itemToRemove;
  95.         }
  96.         /** @var array $lineItemsToRemoveIDs */
  97.         $lineItemsToRemoveIDs array_keys($lineItemsToRemove);
  98.         $newRestOfCart = new LineItemFlatCollection();
  99.         // this is our running buffer
  100.         // for the items that need to be removed
  101.         $deleteBuffer = [];
  102.         // make sure we have an ID index for
  103.         // all our delete-items with a qty of 0
  104.         foreach (array_keys($lineItemsToRemove) as $id) {
  105.             $deleteBuffer[$id] = 0;
  106.         }
  107.         foreach ($restOfCart as $item) {
  108.             // if its a totally different item
  109.             // just add it to the rest of our cart
  110.             if (!\in_array($item->getId(), $lineItemsToRemoveIDstrue)) {
  111.                 $newRestOfCart->add($item);
  112.             } else {
  113.                 // we have an item that should be removed
  114.                 // now we have to calculate how many of the item position (qty diff)
  115.                 // or if we have even reached our max amount of quantities to remove for this item
  116.                 $maxRemoveMeta $lineItemsToRemove[$item->getId()]->getQuantity();
  117.                 $alreadyDeletedCount $deleteBuffer[$item->getId()];
  118.                 // now check if we can remove our current item completely
  119.                 // or if we have a sub quantity that still needs to be
  120.                 // added to the rest of the cart
  121.                 if ($alreadyDeletedCount $item->getQuantity() <= $maxRemoveMeta) {
  122.                     // remove completely
  123.                     $deleteBuffer[$item->getId()] += $item->getQuantity();
  124.                 } else {
  125.                     $toDeleteCount $maxRemoveMeta $alreadyDeletedCount;
  126.                     $keepCount $item->getQuantity() - $toDeleteCount;
  127.                     // mark our diff as "deleted"
  128.                     $deleteBuffer[$item->getId()] += $toDeleteCount;
  129.                     // add the keep count to our item
  130.                     // and the item to the rest of our cart
  131.                     $item->setQuantity($keepCount);
  132.                     $newRestOfCart->add($item);
  133.                 }
  134.             }
  135.         }
  136.         return $newRestOfCart;
  137.     }
  138.     /**
  139.      * @throws InvalidQuantityException
  140.      * @throws LineItemNotStackableException
  141.      */
  142.     private function splitQuantities(LineItemCollection $cartItemsSalesChannelContext $context): LineItemFlatCollection
  143.     {
  144.         $items = [];
  145.         foreach ($cartItems as $item) {
  146.             $isStackable $item->isStackable();
  147.             $item->setStackable(true);
  148.             for ($i 1$i <= $item->getQuantity(); ++$i) {
  149.                 $tmpItem $this->quantitySplitter->split($item1$context);
  150.                 $items[] = $tmpItem;
  151.             }
  152.             $item->setStackable($isStackable);
  153.         }
  154.         return new LineItemFlatCollection($items);
  155.     }
  156. }