Discount Criteria Predicate Registry
Contract for registering predicates that evaluate discount-rule criteria against pricing context.
Overview
App\Services\Pricing\DiscountCriteriaPredicateRegistry stores the predicates understood by discount-rule criteria matching.
Core binds the registry as a singleton in AppServiceProvider and registers the six shipped criteria dimensions. CriteriaMatcher consumes the set when deciding whether a rule applies to a line and pricing context.
Public surface
| Method | Purpose |
|---|---|
register() |
Store one predicate under its key() |
get() |
Return a predicate for a key or throw |
has() |
Check whether a predicate key is registered |
all() |
Return the complete keyed predicate map |
Accepted contract and identity
Every registration implements App\Contracts\Pricing\DiscountCriteriaPredicate:
public function key(): string;
public function matches(
mixed $criterionValue,
DiscountCriteriaContext $context,
): bool;
key() is both the registry identity and the JSON criteria key handled by the predicate. matches() interprets that key's stored value against the supplied DiscountCriteriaContext.
The contract does not declare predicates side-effect-free or provide a priority. Current core predicates only evaluate their inputs, but that implementation detail is not promoted to a requirement for every registration.
Collisions and lookup failures
register() assigns directly into an associative map. A later predicate with the same key replaces the earlier object; duplicate registration is not rejected.
get($key) throws InvalidArgumentException with Unknown discount criteria predicate: {key} when the key is absent. has() provides the optional guard.
CriteriaMatcher does not perform throwing lookups. It iterates the registered predicates and only invokes those whose key is present in the active criteria.
Current core registrations
The complete built-in set is:
| Key | Current match |
|---|---|
catalogue_item_group_ids |
The context catalogue item group is in the configured ID list |
quantity_min |
The resolved line, aggregate-catalogue item, or aggregate-group quantity meets the minimum |
quantity_max |
The resolved line, aggregate-catalogue item, or aggregate-group quantity does not exceed the maximum |
catalogue_item_ids |
The context catalogue item is in the configured ID list |
tag_names |
Every configured tag is present, using case-insensitive comparison |
transaction_types |
The coerced transaction type is in the configured list |
Matching and applicability
CriteriaMatcher first removes inactive values: null, [], and the empty string. Null, empty, or entirely inactive criteria match automatically.
For remaining values, registered dimensions combine with AND logic. The matcher returns false as soon as one invoked predicate fails, and returns true when every invoked predicate passes.
The matcher iterates the registered predicate map without sorting. Since it short-circuits, evaluation order follows the warehoused registry order, but the registry promises no priority scheme.
An active criterion with no registered predicate is currently ignored because the matcher only iterates known registrations. Direct callers that require a known key should guard with has() or call get() and handle its exception.
Worked example
With the core catalogue_item_ids and quantity_min predicates registered, the current consumer accepts criteria in this shape:
$matches = app(CriteriaMatcher::class)->matches([
'catalogue_item_ids' => [12, 18],
'quantity_min' => 3,
], $context);
The result is true only when the context catalogue item is one of those IDs and the quantity selected by the context's rule scope is at least three.