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

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