diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md index b4d6651fd7..62a4d71c0a 100644 --- a/CHANGELOG-WIP.md +++ b/CHANGELOG-WIP.md @@ -4,6 +4,8 @@ - It’s now possible to specifically make discounts require a coupon code. ([#3132](https://github.com/craftcms/commerce/issues/3132)) - Country code defaults to the store’s country when creating a new address on the Order Edit page. ([#3306](https://github.com/craftcms/commerce/issues/3306)) - Product conditions can now have a “Variant Search” rule. ([#3689](https://github.com/craftcms/commerce/issues/3689)) +- Improved the performance of adding items to the cart. +- Improved the performance of shipping rule matching when an order condition formula is used. ([3653](https://github.com/craftcms/commerce/pull/3653)) ### Administration diff --git a/src/controllers/CartController.php b/src/controllers/CartController.php index c4ec3d6a69..2c6b805254 100644 --- a/src/controllers/CartController.php +++ b/src/controllers/CartController.php @@ -135,7 +135,7 @@ public function actionUpdateCart(): ?Response // Get the cart from the request or from the session. // When we are about to update the cart, we consider it a real cart at this point, and want to actually create it in the DB. - $this->_cart = $this->_getCart(true); + $this->_cart = $this->_getCart(); // Can clear line items when updating the cart $clearLineItems = $this->request->getParam('clearLineItems'); @@ -580,7 +580,7 @@ private function _getCart(bool $forceSave = false): Order // Get the cart from the order number $cart = Order::find()->number($orderNumber)->isCompleted(false)->one(); - if (!$cart) { + if ($cart === null) { throw new NotFoundHttpException('Cart not found'); } @@ -590,7 +590,13 @@ private function _getCart(bool $forceSave = false): Order $requestForceSave = (bool)$this->request->getBodyParam('forceSave'); $doForceSave = ($requestForceSave || $forceSave); - return Plugin::getInstance()->getCarts()->getCart($doForceSave); + $this->_cart = Plugin::getInstance()->getCarts()->getCart($doForceSave); + + if (!$this->_cart->id) { + Craft::$app->getElements()->saveElement($this->_cart, false); + } + + return $this->_cart; } /** @@ -685,7 +691,7 @@ private function _setAddresses(): void $this->_cart->sourceBillingAddressId = $billingAddressId; /** @var Address $cartBillingAddress */ - $cartBillingAddress = Craft::$app->getElements()->duplicateElement($userBillingAddress, [ + $cartBillingAddress = Craft::$app->getElements()->duplicateElement($userBillingAddress, [ 'owner' => $this->_cart, ]); $this->_cart->setBillingAddress($cartBillingAddress); diff --git a/src/models/ShippingRule.php b/src/models/ShippingRule.php index b6c9388b74..78dc9aa8c6 100644 --- a/src/models/ShippingRule.php +++ b/src/models/ShippingRule.php @@ -229,8 +229,9 @@ function($attribute) { if (!$order) { $order = new Order(); } + $orderAsArray = Plugin::getInstance()->getShippingMethods()->getSerializedOrderForMatchingRules($order); $orderConditionParams = [ - 'order' => $order->toArray([], ['lineItems.snapshot', 'shippingAddress', 'billingAddress']), + 'order' => $orderAsArray, ]; if (!Plugin::getInstance()->getFormulas()->validateConditionSyntax($this->{$attribute}, $orderConditionParams)) { $this->addError($attribute, Craft::t('commerce', 'Invalid order condition syntax.')); @@ -273,10 +274,9 @@ public function matchOrder(Order $order): bool $lineItems = $order->getLineItems(); if ($this->orderConditionFormula) { - $fieldsAsArray = $order->getSerializedFieldValues(); - $orderAsArray = $order->toArray([], ['lineItems.snapshot', 'shippingAddress', 'billingAddress']); + $orderAsArray = Plugin::getInstance()->getShippingMethods()->getSerializedOrderForMatchingRules($order); $orderConditionParams = [ - 'order' => array_merge($orderAsArray, $fieldsAsArray), + 'order' => $orderAsArray, ]; if (!Plugin::getInstance()->getFormulas()->evaluateCondition($this->orderConditionFormula, $orderConditionParams, 'Evaluate Shipping Rule Order Condition Formula')) { return false; diff --git a/src/services/Carts.php b/src/services/Carts.php index 72af206fac..f67cdcabaa 100644 --- a/src/services/Carts.php +++ b/src/services/Carts.php @@ -99,7 +99,7 @@ public function init() /** * Get the current cart for this session. * - * @param bool $forceSave Force the cart to save when requesting it. + * @param bool $forceSave Force the cart. * @throws ElementNotFoundException * @throws Exception * @throws Throwable diff --git a/src/services/ShippingMethods.php b/src/services/ShippingMethods.php index 77d2aa294e..dbb880dad6 100644 --- a/src/services/ShippingMethods.php +++ b/src/services/ShippingMethods.php @@ -59,6 +59,11 @@ class ShippingMethods extends Component */ private ?array $_allShippingMethods = null; + /** + * @var array + */ + private array $_serializedOrdersByNumber = []; + /** * Returns the Commerce managed shipping methods stored in the database. * @@ -141,9 +146,31 @@ public function getMatchingShippingMethods(Order $order): array $shippingMethods[$method->getHandle()] = $method; // Keep the key being the handle of the method for front-end use. } + // Clear the memoized data so next time we watch to match rules, we get fresh data. + $this->_serializedOrdersByNumber = []; + return $shippingMethods; } + /** + * Creates an order as an array for matching rules. + * We do this centrally here so that we can clear the memoized data centrally. + * + * @param Order $order + * @return array + */ + public function getSerializedOrderForMatchingRules(Order $order): array + { + if (isset($this->_serializedOrdersByNumber[$order->number])) { + return $this->_serializedOrdersByNumber[$order->number]; + } + + $fieldsAsArray = $order->getSerializedFieldValues(); + $orderAsArray = $order->toArray([], ['lineItems.snapshot', 'shippingAddress', 'billingAddress']); + $this->_serializedOrdersByNumber[$order->number] = array_merge($orderAsArray, $fieldsAsArray); + return $this->_serializedOrdersByNumber[$order->number]; + } + /** * Get a matching shipping rule for Order and shipping method. *