diff --git a/vendor/magento/module-checkout/Controller/Cart/Add.php b/vendor/magento/module-checkout/Controller/Cart/Add.php index 009505d8e7b..b5d0e20f05a 100644 --- a/vendor/magento/module-checkout/Controller/Cart/Add.php +++ b/vendor/magento/module-checkout/Controller/Cart/Add.php @@ -6,10 +6,11 @@ */ namespace Magento\Checkout\Controller\Cart; -use Magento\Checkout\Model\Cart\RequestQuantityProcessor; -use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Checkout\Model\AddProductToCart; use Magento\Checkout\Model\Cart as CustomerCart; +use Magento\Checkout\Model\Cart\RequestQuantityProcessor; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ResponseInterface; use Magento\Framework\Controller\ResultInterface; @@ -32,6 +33,11 @@ class Add extends \Magento\Checkout\Controller\Cart implements HttpPostActionInt */ private $quantityProcessor; + /** + * @var AddProductToCart + */ + private AddProductToCart $addProductToCart; + /** * @param \Magento\Framework\App\Action\Context $context * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig @@ -41,6 +47,7 @@ class Add extends \Magento\Checkout\Controller\Cart implements HttpPostActionInt * @param CustomerCart $cart * @param ProductRepositoryInterface $productRepository * @param RequestQuantityProcessor|null $quantityProcessor + * @param AddProductToCart|null $addProductToCart * @codeCoverageIgnore */ public function __construct( @@ -51,7 +58,8 @@ class Add extends \Magento\Checkout\Controller\Cart implements HttpPostActionInt \Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator, CustomerCart $cart, ProductRepositoryInterface $productRepository, - ?RequestQuantityProcessor $quantityProcessor = null + ?RequestQuantityProcessor $quantityProcessor = null, + ?AddProductToCart $addProductToCart = null ) { parent::__construct( $context, @@ -64,6 +72,8 @@ class Add extends \Magento\Checkout\Controller\Cart implements HttpPostActionInt $this->productRepository = $productRepository; $this->quantityProcessor = $quantityProcessor ?? ObjectManager::getInstance()->get(RequestQuantityProcessor::class); + $this->addProductToCart = $addProductToCart + ?? ObjectManager::getInstance()->get(AddProductToCart::class); } /** @@ -122,11 +132,7 @@ class Add extends \Magento\Checkout\Controller\Cart implements HttpPostActionInt return $this->goBack(); } - $this->cart->addProduct($product, $params); - if (!empty($related)) { - $this->cart->addProductsByIds(explode(',', $related)); - } - $this->cart->save(); + $this->addProductToCart->execute($this->cart, $product, $params, $related ? explode(',', $related) : []); /** * @todo remove wishlist observer \Magento\Wishlist\Observer\AddToCart diff --git a/vendor/magento/module-checkout/Model/AddProductToCart.php b/vendor/magento/module-checkout/Model/AddProductToCart.php new file mode 100644 index 00000000000..57d049f81aa --- /dev/null +++ b/vendor/magento/module-checkout/Model/AddProductToCart.php @@ -0,0 +1,91 @@ +getQuote()->getId()) { + return $this->add($cart, $product, $buyRequest, $related); + } + + return $this->quoteMutex->execute( + [(int) $cart->getQuote()->getId()], + function (array $quotes = []) use ($cart, $product, $buyRequest, $related) { + $reload = true; + // check if the mutex provided the quote + if (!empty($quotes)) { + // check if the quote was updated since the last load + // if not, we can use the quote in memory to avoid full reload which is expensive and unnecessary + $lastUpdatedAt = $cart->getQuote()->getUpdatedAt() + ?: $cart->getQuote()->getOrigData(CartInterface::KEY_UPDATED_AT); + $quote = current($quotes); + $updatedAt = $quote->getUpdatedAt(); + $reload = $updatedAt + && $lastUpdatedAt + && $this->dateTime->timestamp($updatedAt) > $this->dateTime->timestamp($lastUpdatedAt); + } + if ($reload) { + // bypass repository cache by creating a new repository instead of using the shared repository + $quote = $this->quoteRepositoryFactory->create()->getActive($cart->getQuote()->getId()); + $cart->setQuote($quote); + $cart->getCheckoutSession()->replaceQuote($quote); + } + return $this->add($cart, $product, $buyRequest, $related); + }, + ); + } + + /** + * Add product to cart + * + * @param Cart $cart + * @param Product $product + * @param array $buyRequest + * @param array $related + * @return bool + */ + private function add(Cart $cart, Product $product, array $buyRequest, array $related = []): bool + { + $cart->addProduct($product, $buyRequest); + if (!empty($related)) { + $cart->addProductsByIds($related); + } + $cart->save(); + return true; + } +} diff --git a/vendor/magento/module-checkout/etc/di.xml b/vendor/magento/module-checkout/etc/di.xml index 9a23e034385..590c4478d87 100644 --- a/vendor/magento/module-checkout/etc/di.xml +++ b/vendor/magento/module-checkout/etc/di.xml @@ -56,4 +56,9 @@ + + + Magento\Quote\Model\ResourceModel\LoadQuoteByIdMutex + + diff --git a/vendor/magento/module-quote/Model/ResourceModel/LoadQuoteByIdMutex.php b/vendor/magento/module-quote/Model/ResourceModel/LoadQuoteByIdMutex.php new file mode 100644 index 00000000000..de477cb3dff --- /dev/null +++ b/vendor/magento/module-quote/Model/ResourceModel/LoadQuoteByIdMutex.php @@ -0,0 +1,59 @@ +quoteResourceModel->getConnection(); + $connection->beginTransaction(); + $query = $connection->select() + ->from($this->quoteResourceModel->getMainTable()) + ->where($connection->prepareSqlCondition($this->quoteResourceModel->getIdFieldName(), ['in' => $maskedIds])) + ->forUpdate(true); + $rows = $connection->fetchAll($query); + $quotes = []; + foreach ($rows as $data) { + $quote = $this->quoteFactory->create(); + $quote->setData($data); + $quote->setOrigData(); + $quotes[] = $quote; + } + $args[] = $quotes; + try { + $result = $callable(...$args); + $connection->commit(); + return $result; + } catch (\Throwable $e) { + $connection->rollBack(); + throw $e; + } + } +}