77namespace Magento \ConfigurableProduct \Pricing \Price ;
88
99use Magento \Catalog \Model \Product ;
10+ use Magento \ConfigurableProduct \Model \ConfigurableMaxPriceCalculator ;
1011use Magento \Framework \App \ObjectManager ;
1112use Magento \Framework \ObjectManager \ResetAfterRequestInterface ;
1213use Magento \Framework \Pricing \Price \AbstractPrice ;
@@ -54,12 +55,23 @@ class ConfigurableRegularPrice extends AbstractPrice implements
5455 */
5556 private $ lowestPriceOptionsProvider ;
5657
58+ /**
59+ * @var ConfigurableMaxPriceCalculator
60+ */
61+ private $ configurableMaxPriceCalculator ;
62+
63+ /**
64+ * @var array<int, bool>
65+ */
66+ private $ equalFinalPriceCache = [];
67+
5768 /**
5869 * @param \Magento\Framework\Pricing\SaleableInterface $saleableItem
5970 * @param float $quantity
6071 * @param \Magento\Framework\Pricing\Adjustment\CalculatorInterface $calculator
6172 * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
6273 * @param PriceResolverInterface $priceResolver
74+ * @param ConfigurableMaxPriceCalculator $configurableMaxPriceCalculator
6375 * @param LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider
6476 */
6577 public function __construct (
@@ -68,12 +80,14 @@ public function __construct(
6880 \Magento \Framework \Pricing \Adjustment \CalculatorInterface $ calculator ,
6981 \Magento \Framework \Pricing \PriceCurrencyInterface $ priceCurrency ,
7082 PriceResolverInterface $ priceResolver ,
83+ ConfigurableMaxPriceCalculator $ configurableMaxPriceCalculator ,
7184 ?LowestPriceOptionsProviderInterface $ lowestPriceOptionsProvider = null
7285 ) {
7386 parent ::__construct ($ saleableItem , $ quantity , $ calculator , $ priceCurrency );
7487 $ this ->priceResolver = $ priceResolver ;
7588 $ this ->lowestPriceOptionsProvider = $ lowestPriceOptionsProvider ?:
7689 ObjectManager::getInstance ()->get (LowestPriceOptionsProviderInterface::class);
90+ $ this ->configurableMaxPriceCalculator = $ configurableMaxPriceCalculator ;
7791 }
7892
7993 /**
@@ -184,31 +198,71 @@ private function getConfigurableOptionsProvider()
184198 public function _resetState (): void
185199 {
186200 $ this ->values = [];
201+ $ this ->equalFinalPriceCache = [];
187202 }
188203
189204 /**
190- * Check whether Configurable Product has more than one child product and if their prices are equal
205+ * Check whether Configurable Product have more than one children products
191206 *
192207 * @param SaleableInterface $product
193208 * @return bool
209+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
210+ * @SuppressWarnings(PHPMD.NPathComplexity)
194211 */
195212 public function isChildProductsOfEqualPrices (SaleableInterface $ product ): bool
196213 {
197- // Get all child products of the configurable product
198- $ childProducts = $ product ->getTypeInstance ()->getUsedProducts ($ product );
199- if (count ($ childProducts ) <= 1 ) {
200- return false ; // Not more than one child product
214+ $ storeId = (int ) ($ product ->getStoreId () ?: 0 );
215+ $ cacheKey = (int ) $ product ->getId () . ': ' . $ storeId ;
216+ if (isset ($ this ->equalFinalPriceCache [$ cacheKey ])) {
217+ return $ this ->equalFinalPriceCache [$ cacheKey ];
218+ }
219+
220+ $ memoKey = '_children_final_prices_equal_store_ ' . $ storeId ;
221+ $ memoized = $ product ->getData ($ memoKey );
222+ if ($ memoized !== null ) {
223+ return (bool ) $ memoized ;
201224 }
202225
203- $ prices = [];
204- foreach ($ childProducts as $ child ) {
205- $ prices [] = $ child ->getFinalPrice ();
226+ // Listing fast-path: if index fields are present, rely on them and avoid any child loading
227+ $ minIndexed = $ product ->getData ('minimal_price ' );
228+ $ maxIndexed = $ product ->getData ('max_price ' );
229+ if (is_numeric ($ minIndexed ) && is_numeric ($ maxIndexed )) {
230+ $ result = ((float )$ minIndexed === (float )$ maxIndexed );
231+ $ this ->equalFinalPriceCache [$ cacheKey ] = $ result ;
232+ $ product ->setData ($ memoKey , $ result );
233+ return $ result ;
206234 }
207235
208- $ minPrice = min ($ prices );
209- $ maxPrice = max ($ prices );
236+ $ children = $ product ->getTypeInstance ()->getUsedProducts ($ product );
237+ $ firstFinal = null ;
238+ $ saleableChildrenCount = 0 ;
239+ $ allEqual = true ;
240+ foreach ($ children as $ child ) {
241+ if (!$ child ->isSalable ()) {
242+ continue ;
243+ }
244+ $ saleableChildrenCount ++;
245+ $ value = $ child ->getPriceInfo ()->getPrice ('final_price ' )->getAmount ()->getValue ();
246+ if ($ firstFinal === null ) {
247+ $ firstFinal = $ value ;
248+ continue ;
249+ }
250+ if ($ value != $ firstFinal ) {
251+ $ allEqual = false ;
252+ break ;
253+ }
254+ }
255+
256+ if ($ saleableChildrenCount <= 1 || $ firstFinal === null || !$ allEqual ) {
257+ $ product ->setData ($ memoKey , false );
258+ $ this ->equalFinalPriceCache [$ cacheKey ] = false ;
259+ return false ;
260+ }
210261
211- // Return true only if all child prices are equal (min == max)
212- return $ minPrice == $ maxPrice ;
262+ // Guard against parent-level extra discounts (compute only when children are equal)
263+ $ result = !($ product ->getFinalPrice () < $ firstFinal );
264+ $ this ->equalFinalPriceCache [$ cacheKey ] = $ result ;
265+ $ product ->setData ($ memoKey , $ result );
266+ return $ result ;
213267 }
214268}
0 commit comments