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

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