DEV Community

Nicolas Dabene
Nicolas Dabene

Posted on • Originally published at nicolas-dabene.fr

Evolution of CartRule.php: PrestaShop 9.0.x 9.1.x

Unveiling the Future of Discounts: A Deep Dive into PrestaShop 9.1.x's CartRule.php Evolution

PrestaShop is on the cusp of a major transformation with its 9.1.x branch, particularly concerning how discounts are managed. A brand-new discount system is being introduced, currently safeguarded by a feature flag. This significant architectural shift is meticulously implemented within the classes/CartRule.php file, which forms the bedrock of this modern system while carefully preserving compatibility with existing cart rules.

This article explores the pivotal enhancements within CartRule.php as PrestaShop transitions from version 9.0.x to 9.1.x. We'll examine new dependencies, the integration of feature flags, refined discount typing, and crucial updates to discount calculation methods. This guide is essential for any PrestaShop developer aiming to understand and adapt to these forthcoming changes.

Key Information Sources: PrestaShop DevDocs on 9.1.x changes, Official Blog's insights on the improved discount system, PrestaShop 9.1 Beta announcement, January 2026 Core Monthly, and the GitHub develop branch for CartRule.php.


1. Essential New Imports and Dependencies

The first indication of a significant shift lies in the file's header.

9.0.x Perspective

In PrestaShop 9.0.x, the CartRuleCore class relied on the standard PrestaShop imports, lacking any direct links to a dedicated discount framework.

9.1.x (Develop Branch) Perspective

The 9.1.x branch introduces several vital use statements at the top of the file, signaling a deeper integration with new core functionalities:

use PrestaShop\PrestaShop\Adapter\ContainerFinder;
use PrestaShop\PrestaShop\Adapter\Discount\Application\DiscountApplicationService;
use PrestaShop\PrestaShop\Core\Domain\CartRule\CartRuleSettings;
use PrestaShop\PrestaShop\Core\Domain\Discount\DiscountSettings;
use PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject\DiscountType;
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagSettings;
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagStateCheckerInterface;
Enter fullscreen mode Exit fullscreen mode

What this means for developers: The CartRule class now seamlessly connects with:

  • DiscountType: A specialized Value Object to precisely categorize discount application levels (e.g., ORDER_LEVEL for entire cart, PRODUCT_LEVEL for specific items).
  • DiscountApplicationService: The central service orchestrating how discounts are applied within this enhanced system.
  • FeatureFlagSettings / FeatureFlagStateCheckerInterface: Components enabling dynamic checks to determine if the new discount system is actively enabled.

2. Integrating the Discount Feature Flag

A cornerstone of the 9.1.x update is the strategic use of a feature flag, allowing the new system to coexist with the old one during the transition.

Introducing: isDiscountFeatureFlagEnabled()

A crucial private method has been added in 9.1.x to verify the status of this feature flag:

private function isDiscountFeatureFlagEnabled(): bool
{
    return $this->getFeatureFlagManager() !== null
        && $this->getFeatureFlagManager()->isEnabled(FeatureFlagSettings::FEATURE_FLAG_DISCOUNT);
}
Enter fullscreen mode Exit fullscreen mode

Its function: This method effectively serves as a toggle throughout the CartRule.php file. When the flag is inactive (which is the default state), the system's behavior mirrors that of 9.0.x. However, once activated, the advanced new discount logic takes precedence.

The Helper Method: getFeatureFlagManager()

A protected method is now available to retrieve an instance of FeatureFlagStateCheckerInterface from the Symfony container:

protected function getFeatureFlagManager(): ?FeatureFlagStateCheckerInterface
Enter fullscreen mode Exit fullscreen mode

3. The getType() Method: A New Approach to Discount Typing

PrestaShop 9.1.x streamlines how cart rules are categorized through the introduction of a dedicated method.

Added in 9.1.x: getType()

The new getType() method provides a structured way to classify cart rules within the updated discount framework, defining four distinct types:

Type Description
Catalog (PRODUCT_LEVEL) Discounts applied to specific products or product categories.
Cart (ORDER_LEVEL) Discounts applied to the overall cart total (excluding shipping).
Free Shipping Offers free shipping for an order.
Free Gift Adds a complimentary product to the order.

This method yields a DiscountType Value Object, determined by the cart rule's internal setup (e.g., reduction_product, free_shipping, gift_product fields).

Developer impact: Developers can now leverage $cartRule->getType() for querying discount types, simplifying logic that previously required manual checks of various individual fields.


4. Significant Adjustments within getContextualValue()

The getContextualValue() method, central to how discounts are computed, has received some of the most profound modifications.

4.1 Order-Level Percentage Reductions (ORDER_LEVEL)

In 9.0.x: Percentage-based reductions were exclusively applied to the total value of products in the cart.

In 9.1.x: With the feature flag enabled and the discount classified as ORDER_LEVEL, the calculation expands:

if ($this->getType() === DiscountType::ORDER_LEVEL
    && $this->reduction_percent > 0.00
    && $this->reduction_product == 0) {
    $order_products_total = $context->cart->getOrderTotal($use_tax, Cart::ONLY_PRODUCTS, $package_products);
    $order_shipping_total = $context->cart->getOrderTotal($use_tax, Cart::ONLY_SHIPPING, $package_products);
    $order_total = $order_products_total + $order_shipping_total;
    $reduction_value += $order_total * $this->reduction_percent / 100;
}
Enter fullscreen mode Exit fullscreen mode

Crucial update: The percentage discount now encompasses both product totals and shipping expenses, a significant departure from applying only to products.

4.2 Fixed Amount Reduction — Revised Capping Logic

In 9.0.x: The maximum reduction for a fixed amount was capped at the product total.

In 9.1.x: For ORDER_LEVEL discounts with the feature flag active, the cap now includes shipping costs:

if ($this->isDiscountFeatureFlagEnabled() && $this->getType() === DiscountType::ORDER_LEVEL) {
    $max_reduction_amount = $this->reduction_tax
        ? $cart_amount_ti + $context->cart->getOrderTotal(true, Cart::ONLY_SHIPPING, $package_products)
        : $cart_amount_te + $context->cart->getOrderTotal(false, Cart::ONLY_SHIPPING, $package_products);
    $reduction_amount = min($reduction_amount, $max_reduction_amount);
}
Enter fullscreen mode Exit fullscreen mode

4.3 Current Cart Amount Calculation Refinement

When applying a fixed amount reduction (reduction_amount > 0), the calculation for $current_cart_amount now incorporates shipping costs for ORDER_LEVEL discounts:

if ($this->isDiscountFeatureFlagEnabled() && $this->getType() === DiscountType::ORDER_LEVEL) {
    $current_cart_amount += $this->reduction_tax
        ? $context->cart->getOrderTotal(true, Cart::ONLY_SHIPPING, $package_products)
        : $context->cart->getOrderTotal(false, Cart::ONLY_SHIPPING, $package_products);
}
Enter fullscreen mode Exit fullscreen mode

5. Addressing Variable Inconsistency (PR #40424)

A notable bug fix, introduced by PR #40424 from @pjouglet, resolved inconsistencies in variable usage throughout the CartRule.php file.

Before this fix: Developers might have encountered mixed usage of variables, such as $ cart_rule->name and htmlspecialchars($cart_rule->name).

After the fix: Variable usage across the file is normalized, particularly within voucher compatibility error messages, enhancing security against XSS:

// 9.1.x - with htmlspecialchars for XSS security
return (!$display_error) ? false : $this->trans(
    'This voucher is not combinable with an other voucher already in your cart: %s',
    [htmlspecialchars($cart_rule->name)],
    'Shop.Notifications.Error'
);
Enter fullscreen mode Exit fullscreen mode

6. A Revamped Discount Compatibility System

The method for managing discount combinations has undergone a significant overhaul.

9.0.x Compatibility

The previous system relied on the ps_cart_rule_combination table and the cart_rule_restriction field. This approach involved storing each forbidden combination as a pair, which could lead to an exponential increase in data as the number of rules grew.

9.1.x Compatibility

The new system simplifies and enhances compatibility by categorizing discounts by their type:

  1. Discounts are no longer prioritized based on whether they are promo codes or automatically applied.
  2. A fixed application order is enforced:
    • Catalog (product-level) → Cart (order-level) → Free Shipping → Free Gift.
  3. Within the same type: Discounts are sorted by priority (lower values apply first), then by their creation date.
  4. The system performs dynamic re-evaluation with every modification to the cart.

7. Implications for CartRuleCalculator.php

While CartRuleCalculator.php is distinct from CartRule.php, the changes discussed are deeply interconnected. The CartRuleCalculator (found in src/Core/Cart/) now incorporates the same feature flag checks:

if ($cartRule->getType() === DiscountType::ORDER_LEVEL
    && (float) $cartRule->reduction_percent > 0
    && $cartRule->reduction_product == 0) {
    if ($this->isDiscountFeatureFlagEnabled()) {
        // New calculation: products + shipping
        $initialShippingFees = $this->calculator->getFees()->getInitialShippingFees();
        $productsTotal = $this->calculator->getRowTotal();
        $orderTotal = $productsTotal->add($initialShippingFees);
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

8. Corresponding Database Schema Updates

The 9.1.x release also brings database schema modifications to underpin this new discount system. These changes are detailed in install-dev/upgrade/sql/9.1.0.sql and are inherently linked to the feature flag. Notably, they will not alter existing tables as long as the feature flag remains disabled.


9. Snapshot of Key Changes

Here's a concise overview of the differences between the two versions:

Aspect PrestaShop 9.0.x PrestaShop 9.1.x
Dependencies No new Discount System tie-ins DiscountType, DiscountSettings, FeatureFlagSettings
Feature Flag Support Absent isDiscountFeatureFlagEnabled() available
Discount Categorization Implicit (relying on individual fields) Explicitly via getType() returning a DiscountType
% on ORDER_LEVEL Applies only to products Applies to products + shipping costs
Fixed Amount Cap Limited to product total Includes product total + shipping (for ORDER_LEVEL)
XSS Security (Messages) Uses $cart_rule->name directly Employs htmlspecialchars($cart_rule->name)
Discount Compatibility Relies on ps_cart_rule_combination By type, with a fixed application order and priority
Backward Compatibility 9.0.x behavior maintained when flag is disabled

10. Essential Guidance for Module Developers

These changes necessitate a careful approach for anyone developing or maintaining PrestaShop modules. Here are some key recommendations:

  1. Avoid Overriding CartRule.php: Given the rapid evolution of this file, directly overriding CartRule.php for 9.1.x compatibility is highly discouraged. It's an active development area with frequent updates.
  2. Incorporate Feature Flag Checks: If your module interacts with discounts, explicitly check the feature flag status:

    $featureFlagManager = $this->get(FeatureFlagStateCheckerInterface::class);
    if ($featureFlagManager->isEnabled(FeatureFlagSettings::FEATURE_FLAG_DISCOUNT)) {
        // Implement logic for the new discount system
    }
    
  3. Thoroughly Test Both Modes: Merchants can toggle the discount feature flag. Ensure your module functions correctly when the flag is both enabled and disabled.

  4. Leverage getType(): Opt for $cartRule->getType() instead of manually inspecting fields like reduction_product, free_shipping, or gift_product. This will simplify your code and improve future compatibility.

  5. Re-evaluate Shipping Calculations: If your module calculates discount totals, be aware that shipping cost inclusion in calculations changes based on the discount type in 9.1.x. Adjust your logic accordingly.


The upcoming changes in PrestaShop 9.1.x, particularly within CartRule.php, represent a significant step towards a more robust and flexible discount management system. By understanding and adapting to these updates, module developers can ensure their creations remain compatible, secure, and performant within the evolving PrestaShop ecosystem.


Stay Ahead in PrestaShop Development!

Want to deepen your understanding of PrestaShop's core mechanics and master development best practices?
Subscribe to Nicolas Dabène's YouTube channel for insightful tutorials and updates: Nicolas Dabène on YouTube

Connect with Nicolas and other industry professionals on LinkedIn to discuss PrestaShop, e-commerce, and development challenges: Nicolas Dabène on LinkedIn


Further Reading & References

Top comments (0)