Performance enhancements for order admin - Eager Loading #4196
-
Optimize Control Panel Order Detail Performance with Eager LoadingProblemWhen viewing order details in the Craft Commerce Control Panel, the system generates excessive duplicate database queries due to missing eager loading. This creates significant performance issues, especially for orders with multiple line items. Observed Performance IssuesBefore optimization:
After optimization:
Root Causes
SolutionI have a PR ready that adds comprehensive eager loading support for CP order detail views: 1. Enhanced Orders::getOrderById() with Eager Loading ParameterFile: Added optional public function getOrderById(int $id, bool $withAll = false): ?Order
{
if (!$id) {
return null;
}
$query = Order::find()->id($id)->status(null);
if ($withAll) {
$query->withAll();
}
return $query->one();
}2. Updated OrdersController to Use Eager LoadingFile: Modified $order = $plugin->getOrders()->getOrderById($orderId, withAll: true);3. Added Purchasables Eager Loading to OrderQueryFile: Added public bool $withPurchasables = false;
public function withPurchasables(bool $value = true): OrderQuery
{
$this->withPurchasables = $value;
return $this;
}Updated if ($this->withPurchasables === true || $this->withAll) {
foreach ($orders as $order) {
$lineItems = $order->getLineItems();
if (!empty($lineItems)) {
$siteId = $order->orderSiteId;
$customerId = $order->customerId;
$lineItems = Plugin::getInstance()->getLineItems()
->eagerLoadPurchasablesForLineItems($lineItems, $siteId, $customerId);
$order->setLineItems($lineItems);
}
}
}4. Implemented Batch Purchasable LoadingFile: Added public function eagerLoadPurchasablesForLineItems(array $lineItems, ?int $siteId = null, int|false|null $forCustomer = null): array
{
// Group purchasable IDs by element type
$purchasableIdsByType = [];
foreach ($lineItems as $lineItem) {
if ($lineItem->purchasableId && $lineItem->type !== LineItemType::Custom) {
$elementType = \Craft::$app->getElements()->getElementTypeById($lineItem->purchasableId);
if ($elementType && class_exists($elementType)) {
$purchasableIdsByType[$elementType][] = $lineItem->purchasableId;
}
}
}
if (empty($purchasableIdsByType)) {
return $lineItems;
}
$siteId ??= \Craft::$app->getSites()->getCurrentSite()->id;
// Batch load all purchasables by type
$purchasablesById = [];
foreach ($purchasableIdsByType as $elementType => $ids) {
$query = \Craft::$app->getElements()->createElementQuery($elementType)
->id(array_unique($ids))
->siteId($siteId)
->status(null)
->drafts(null)
->provisionalDrafts(null)
->revisions(null);
if ($forCustomer !== null && $query instanceof \craft\commerce\elements\db\PurchasableQuery) {
$query->forCustomer($forCustomer);
}
foreach ($query->all() as $purchasable) {
$purchasablesById[$purchasable->id] = $purchasable;
}
}
// Assign purchasables to line items
foreach ($lineItems as $lineItem) {
if (isset($purchasablesById[$lineItem->purchasableId])) {
$lineItem->setPurchasable($purchasablesById[$lineItem->purchasableId]);
}
}
return $lineItems;
}Performance ImpactQuery Reduction
Specific Optimizations
Backward Compatibility100% Backward Compatible
TestingTested on:
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
|
Thanks for this! Can you please make a PR, and we can collab on it from there? Thanks! |
Beta Was this translation helpful? Give feedback.
@justinholtweb
Thanks for this! Can you please make a PR, and we can collab on it from there? Thanks!