Deal Price Validator Registry
Contract for registering validators that run before proportional deal-price distribution.
Overview
App\Services\Pricing\DealPriceValidatorRegistry stores validation steps for proportional deal-price distribution.
Core binds the registry as a singleton in AppServiceProvider, but currently ships with no validators. The extension seam is consumed by DealPriceProportionalDistributor before it calculates any line allocations.
Public surface
| Method | Purpose |
|---|---|
register() |
Store one validator under its key() |
get() |
Return the validator for a key or throw |
has() |
Check whether a validator key is registered |
all() |
Return the complete keyed validator map |
Accepted contract and identity
Every registration implements App\Contracts\Pricing\DealPriceValidator:
public function key(): string;
public function validate(
Rental $rental,
int $dealTotalMinor,
?RentalItem $groupItem = null,
): void;
The validator's key() is the registry identity. validate() receives the rental, requested deal total in minor units, and an optional group item when distribution is scoped to one revenue group.
Validators communicate rejection by throwing. The contract does not prescribe one exception type, return validation metadata, or declare validators pure or side-effect-free.
Collisions and lookup failures
register() assigns directly into an associative map. Registering another validator with the same key replaces the earlier object; duplicate keys are not rejected.
get($key) throws InvalidArgumentException with Unknown deal price validator: {key} for an unregistered key. Use has() when a lookup is optional.
The normal distributor does not call get(): it consumes every value returned by all().
Core registrations and execution order
The framework currently ships with no validators. Consequently, the core registry starts as an empty map and deal-price distribution proceeds directly to its built-in calculations and validation.
The registry has no priority field or sorting API. DealPriceProportionalDistributor iterates all() directly, so registered validators run in registry iteration order and the first thrown exception stops the operation. No broader ordering or error-aggregation guarantee is provided.
After registered validators pass, the distributor enforces its own invariants, including that the requested total cannot fall below non-discountable or manually discounted line totals.
Runtime consumer
DealPriceProportionalDistributor::distribute() invokes every registered validator before loading the scoped lines and calculating proportional discounts. A validator therefore sees the requested input before the distributor derives baselines or applies its largest-remainder allocation.
The same call shape serves both rental-wide and group-scoped deal prices. A null group item means the whole rental; a non-null group item narrows the later distribution, and is passed unchanged to every validator.
Worked example
This is the current consumption loop:
foreach ($validators->all() as $validator) {
$validator->validate($rental, $dealTotalMinor, $groupItem);
}
With the core empty registry, the loop performs no calls. When a validator is registered, returning normally allows the next validator or the distributor to continue; throwing aborts distribution.