Rule evaluation
The lifecycle of rule evaluation from UI to decision-making can be summarized as follows:
- The Rule Builder lets a user create a rule tree (containers and conditions).
- The rule system validates each condition against the corresponding rule class from the registry.
- Valid rule and rule condition records are stored in the database.
- At runtime, a domain builds an appropriate rule scope and, if needed, computes matching rules for that scope in advance.
- Features either filter by rule IDs exposed on the context or evaluate a specific rule tree directly by calling it.
The sections below explain the individual steps in more detail.
1. From Rule Builder to stored rule definition
1.1. Rule trees and conditions
When a user configures a rule in the Rule Builder:
- The visual tree is mapped to a
ruleentity representing the whole rule and multiplerule_conditionrecords representing both container nodes and leaf conditions. - The tree structure is stored via
parent_idreferences betweenrule_conditionrecords. - Each
rule_conditionhas a type that maps to a rule class (implementingRule) and stores its configured values (operator, thresholds, IDs, etc.) in avalueJSON field.
So the Rule Builder is building the structure and configuration that will later be hydrated into a tree of Rule objects for evaluation.
1.2. Validation
Before writes are accepted, RuleValidator validates each condition. It subscribes to write events and inspects commands targeting RuleConditionEntity. For each condition, it:
- Resolves the condition type to a rule class via the
RuleConditionRegistry. - Instantiates the rule and uses its constraints to understand which fields and operators are valid.
If the payload does not match what the rule class declares (wrong fields, types, operators), the write is rejected.
2. Preparing evaluation
Rules do not fetch data themselves, they always evaluate against a provided rule scope.
2.1. Rule scope specification
The abstract RuleScope defines the minimal contract for evaluation. Domains extend it to add domain-specific data.
CheckoutRuleScope- base for checkout-related rules.CartRuleScope- adds access to cart data.FlowRuleScope- adds access to order data.LineItemScope- focuses on a single line item.
2.2. Scope owners
Different parts of the system are responsible for constructing scopes:
Cart / Checkout:
CartRuleLoaderis the main entry point for cart and checkout rule evaluation, building the necessary scopes and evaluating rules against them.Flows:
FlowRuleScopeBuilderis responsible for buildingFlowRuleScope. It reconstructs a cart-like context from an order and runs data collectors so rules see realistic checkout data.Line items: Classes like
AnyRuleLineItemMatcherconstructLineItemScopewhen they need to test rules against individual line items.
The important point is: rules themselves are pure functions that depend only on the scope they receive. They do not depend on a global state.
3. Matching rules
For some domains (checkout), the system evaluates all rules upfront and exposes the IDs of matching rules in the context so features can filter by them.
3.1. Candidate loading
CartRuleLoader is central for checkout. It uses the AbstractRuleLoader to load a collection of rules and narrows these to context-relevant rules before evaluating anything.
3.2. Iterative matching
To determine which of these candidates actually match the current cart context, CartRuleLoader builds the scope and uses RuleCollection::filterMatchingRules(...) to keep only rules whose payload rule tree evaluates to true for that scope. The matching rule IDs are then exposed on the SalesChannelContext.
Because the set of matching rules can affect cart processors (promotions, shipping, etc.) which in turn change the cart, the loader may need to iterate:
The result is a self-consistent pair of (cart, matching rule IDs) in the context.
4. Rules at runtime
Once rules are validated and the context knows which rules match, features can consume them in two ways.
4.1. ID-based decisions
Features that only need to know "is this entity available in the current context?" attach a rule ID then filter by IDs exposed on the context. For example:
- Entities like
shipping_method,payment_methodortax_providerhave anavailability_rule_idfield. - Items with availability rules are allowed if their rule ID is in
SalesChannelContext::getRuleIds().
4.2. Direct evaluation
Other features need to evaluate a particular rule against a specific scope (flow, cart calculation, etc.). In these cases, the feature fetches the rule tree from the database, builds the appropriate scope for the current need and calls Rule::match(RuleScope $scope) on the root rule.
The following sequence shows how a container rule delegates to its children:
Since the OrRule requires at least one child to match, and both return false, the entire rule evaluates to false.