Skip to content

Commit 2bd4ba1

Browse files
authored
ENGCOM-3979: Fix performance leak in salesrule collection #20484
2 parents c0d778c + 76e766a commit 2bd4ba1

File tree

1 file changed

+109
-84
lines changed

1 file changed

+109
-84
lines changed

app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php

Lines changed: 109 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
use Magento\Framework\DB\Select;
1010
use Magento\Framework\Serialize\Serializer\Json;
1111
use Magento\Quote\Model\Quote\Address;
12+
use Magento\SalesRule\Api\Data\CouponInterface;
13+
use Magento\SalesRule\Model\Coupon;
14+
use Magento\SalesRule\Model\Rule;
1215

1316
/**
1417
* Sales Rules resource collection model.
@@ -107,12 +110,15 @@ protected function mapAssociatedEntities($entityType, $objectField)
107110

108111
$associatedEntities = $this->getConnection()->fetchAll($select);
109112

110-
array_map(function ($associatedEntity) use ($entityInfo, $ruleIdField, $objectField) {
111-
$item = $this->getItemByColumnValue($ruleIdField, $associatedEntity[$ruleIdField]);
112-
$itemAssociatedValue = $item->getData($objectField) === null ? [] : $item->getData($objectField);
113-
$itemAssociatedValue[] = $associatedEntity[$entityInfo['entity_id_field']];
114-
$item->setData($objectField, $itemAssociatedValue);
115-
}, $associatedEntities);
113+
array_map(
114+
function ($associatedEntity) use ($entityInfo, $ruleIdField, $objectField) {
115+
$item = $this->getItemByColumnValue($ruleIdField, $associatedEntity[$ruleIdField]);
116+
$itemAssociatedValue = $item->getData($objectField) ?? [];
117+
$itemAssociatedValue[] = $associatedEntity[$entityInfo['entity_id_field']];
118+
$item->setData($objectField, $itemAssociatedValue);
119+
},
120+
$associatedEntities
121+
);
116122
}
117123

118124
/**
@@ -141,6 +147,7 @@ protected function _afterLoad()
141147
* @param string $couponCode
142148
* @param string|null $now
143149
* @param Address $address allow extensions to further filter out rules based on quote address
150+
* @throws \Zend_Db_Select_Exception
144151
* @use $this->addWebsiteGroupDateFilter()
145152
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
146153
* @return $this
@@ -153,32 +160,24 @@ public function setValidationFilter(
153160
Address $address = null
154161
) {
155162
if (!$this->getFlag('validation_filter')) {
156-
/* We need to overwrite joinLeft if coupon is applied */
157-
$this->getSelect()->reset();
158-
parent::_initSelect();
163+
$this->prepareSelect($websiteId, $customerGroupId, $now);
159164

160-
$this->addWebsiteGroupDateFilter($websiteId, $customerGroupId, $now);
161-
$select = $this->getSelect();
165+
$noCouponRules = $this->getNoCouponCodeSelect();
162166

163-
$connection = $this->getConnection();
164-
if (strlen($couponCode)) {
165-
$noCouponWhereCondition = $connection->quoteInto(
166-
'main_table.coupon_type = ?',
167-
\Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON
168-
);
169-
$relatedRulesIds = $this->getCouponRelatedRuleIds($couponCode);
170-
171-
$select->where(
172-
$noCouponWhereCondition . ' OR main_table.rule_id IN (?)',
173-
$relatedRulesIds,
174-
Select::TYPE_CONDITION
175-
);
167+
if ($couponCode) {
168+
$couponRules = $this->getCouponCodeSelect($couponCode);
169+
170+
$allAllowedRules = $this->getConnection()->select();
171+
$allAllowedRules->union([$noCouponRules, $couponRules], Select::SQL_UNION_ALL);
172+
173+
$wrapper = $this->getConnection()->select();
174+
$wrapper->from($allAllowedRules);
175+
176+
$this->_select = $wrapper;
176177
} else {
177-
$this->addFieldToFilter(
178-
'main_table.coupon_type',
179-
\Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON
180-
);
178+
$this->_select = $noCouponRules;
181179
}
180+
182181
$this->setOrder('sort_order', self::SORT_ORDER_ASC);
183182
$this->setFlag('validation_filter', true);
184183
}
@@ -187,72 +186,98 @@ public function setValidationFilter(
187186
}
188187

189188
/**
190-
* Get rules ids related to coupon code
189+
* Recreate the default select object for specific needs of salesrule evaluation with coupon codes.
191190
*
192-
* @param string $couponCode
193-
* @return array
191+
* @param int $websiteId
192+
* @param int $customerGroupId
193+
* @param string $now
194194
*/
195-
private function getCouponRelatedRuleIds(string $couponCode): array
195+
private function prepareSelect($websiteId, $customerGroupId, $now)
196196
{
197-
$connection = $this->getConnection();
198-
$select = $connection->select()->from(
199-
['main_table' => $this->getTable('salesrule')],
200-
'rule_id'
197+
$this->getSelect()->reset();
198+
parent::_initSelect();
199+
200+
$this->addWebsiteGroupDateFilter($websiteId, $customerGroupId, $now);
201+
}
202+
203+
/**
204+
* Return select object to determine all active rules not needing a coupon code.
205+
*
206+
* @return Select
207+
*/
208+
private function getNoCouponCodeSelect()
209+
{
210+
$noCouponSelect = clone $this->getSelect();
211+
212+
$noCouponSelect->where(
213+
'main_table.coupon_type = ?',
214+
Rule::COUPON_TYPE_NO_COUPON
201215
);
202-
$select->joinLeft(
203-
['rule_coupons' => $this->getTable('salesrule_coupon')],
204-
$connection->quoteInto(
205-
'main_table.rule_id = rule_coupons.rule_id AND main_table.coupon_type != ?',
206-
\Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON,
207-
null
208-
)
216+
217+
$noCouponSelect->columns([Coupon::KEY_CODE => new \Zend_Db_Expr('NULL')]);
218+
219+
return $noCouponSelect;
220+
}
221+
222+
/**
223+
* Determine all active rules that are valid for the given coupon code.
224+
*
225+
* @param string $couponCode
226+
* @return Select
227+
*/
228+
private function getCouponCodeSelect($couponCode)
229+
{
230+
$couponSelect = clone $this->getSelect();
231+
232+
$this->joinCouponTable($couponCode, $couponSelect);
233+
234+
$notExpired = $this->getConnection()->quoteInto(
235+
'(rule_coupons.expiration_date IS NULL OR rule_coupons.expiration_date >= ?)',
236+
$this->_date->date()->format('Y-m-d')
209237
);
210238

211-
$autoGeneratedCouponCondition = [
212-
$connection->quoteInto(
213-
"main_table.coupon_type = ?",
214-
\Magento\SalesRule\Model\Rule::COUPON_TYPE_AUTO
215-
),
216-
$connection->quoteInto(
217-
"rule_coupons.type = ?",
218-
\Magento\SalesRule\Api\Data\CouponInterface::TYPE_GENERATED
219-
),
220-
];
221-
222-
$orWhereConditions = [
223-
"(" . implode($autoGeneratedCouponCondition, " AND ") . ")",
224-
$connection->quoteInto(
225-
'(main_table.coupon_type = ? AND main_table.use_auto_generation = 1 AND rule_coupons.type = 1)',
226-
\Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC
227-
),
228-
$connection->quoteInto(
229-
'(main_table.coupon_type = ? AND main_table.use_auto_generation = 0 AND rule_coupons.type = 0)',
230-
\Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC
231-
),
232-
];
233-
234-
$andWhereConditions = [
235-
$connection->quoteInto(
236-
'rule_coupons.code = ?',
237-
$couponCode
238-
),
239-
$connection->quoteInto(
240-
'(rule_coupons.expiration_date IS NULL OR rule_coupons.expiration_date >= ?)',
241-
$this->_date->date()->format('Y-m-d')
242-
),
243-
];
244-
245-
$orWhereCondition = implode(' OR ', $orWhereConditions);
246-
$andWhereCondition = implode(' AND ', $andWhereConditions);
247-
248-
$select->where(
249-
'(' . $orWhereCondition . ') AND ' . $andWhereCondition,
239+
$isAutogenerated =
240+
$this->getConnection()->quoteInto('main_table.coupon_type = ?', Rule::COUPON_TYPE_AUTO)
241+
. ' AND ' .
242+
$this->getConnection()->quoteInto('rule_coupons.type = ?', CouponInterface::TYPE_GENERATED);
243+
244+
$isValidSpecific =
245+
$this->getConnection()->quoteInto('(main_table.coupon_type = ?)', Rule::COUPON_TYPE_SPECIFIC)
246+
. ' AND (' .
247+
'(main_table.use_auto_generation = 1 AND rule_coupons.type = 1)'
248+
. ' OR ' .
249+
'(main_table.use_auto_generation = 0 AND rule_coupons.type = 0)'
250+
. ')';
251+
252+
$couponSelect->where(
253+
"$notExpired AND ($isAutogenerated OR $isValidSpecific)",
250254
null,
251255
Select::TYPE_CONDITION
252256
);
253-
$select->group('main_table.rule_id');
254257

255-
return $connection->fetchCol($select);
258+
return $couponSelect;
259+
}
260+
261+
/**
262+
* Join coupong table to select.
263+
*
264+
* @param string $couponCode
265+
* @param Select $couponSelect
266+
*/
267+
private function joinCouponTable($couponCode, Select $couponSelect)
268+
{
269+
$couponJoinCondition =
270+
'main_table.rule_id = rule_coupons.rule_id'
271+
. ' AND ' .
272+
$this->getConnection()->quoteInto('main_table.coupon_type <> ?', Rule::COUPON_TYPE_NO_COUPON)
273+
. ' AND ' .
274+
$this->getConnection()->quoteInto('rule_coupons.code = ?', $couponCode);
275+
276+
$couponSelect->joinInner(
277+
['rule_coupons' => $this->getTable('salesrule_coupon')],
278+
$couponJoinCondition,
279+
[Coupon::KEY_CODE]
280+
);
256281
}
257282

258283
/**

0 commit comments

Comments
 (0)