Skip to content

v1.30.0

Choose a tag to compare

@SanderMuller SanderMuller released this 18 Jun 12:47
· 4 commits to main since this release
Immutable release. Only release title and notes can be modified.
71f09e7

FluentValidator now carries the same optimizations as the other entry points,
and conditional rules written as fluent objects against a wildcard sibling path
now match native Laravel. Both surfaced from a production JSON-import use case
validating large conditional wildcard arrays.

Performance

FluentValidator is now O(n) on conditional wildcard arrays

FluentValidator previously only applied O(n) wildcard expansion and then
handed the fully-expanded rules to a plain Illuminate\Validation\Validator,
which re-resolved exclude_unless/exclude_if itself — quadratic on large
arrays. It now extends OptimizedValidator and shares the full optimized
preparation with HasFluentRules and RuleSet::validate(): pre-evaluation of
conditional rules, fast-check closures, and batched exists/unique queries
(one whereIn instead of one query per row).

On a conditional-heavy import the difference is an order of magnitude — what was
super-linear is now linear, on par with RuleSet::validate(). This also makes
the README's "four optimizations" description accurate for FluentValidator,
which previously only received the first.

Behaviour note: FluentValidator's parent changed from Validator to
OptimizedValidator (itself a Validator subclass, so the public API is
unchanged). Validation verdicts move toward native parity; if a subclass
overrode passes() or depended on the plain validator's internal flow, review
it on upgrade.

Fixed

Object-form conditional rules with a wildcard dependent path

A conditional rule written as a FluentRule object whose dependent path is the
full wildcard sibling — e.g.
FluentRule::field()->requiredUnless('items.*.type', 'pause') — was
mis-evaluated in the per-item path: the items.*. prefix was only stripped for
array- and string-form rules, so the object form kept the wildcard and the
reducer's lookup missed. Object and array forms now behave identically and match
native Laravel. Custom labels and messages on the object are preserved.

exclude_* parity for coerced and associative-key dependents

The conditional pre-evaluation compared the dependent value with a plain string
match, which diverged from Laravel for values needing coercion. exclude_unless
/ exclude_if against a null, boolean, or non-scalar dependent — and against
an associative (non-numeric) wildcard key — are now deferred to the validator
and evaluated authoritatively, so verdicts and the validated() payload match
native. Wildcard dependent paths also resolve to the key at the matching path
position, so associative parents (items.foo.type) are no longer back-filled
from an unrelated numeric descendant.

Internal

  • Extracted the shared optimized-validation preparation into a single
    PreparesOptimizedRules trait used by both HasFluentRules and
    FluentValidator, so the two entry points cannot drift.
  • Conditional pre-evaluation now returns an explicit three-state verdict
    (exclude / not-excluded / defer); deferred attributes are left intact for the
    validator rather than fast-checked away.
  • Added parity regression tests covering object-form wildcard conditionals, the
    coercion/associative exclude_* cases, batched-DB query counts, and a
    FluentValidator conditional-import benchmark.

Full Changelog: 1.29.0...1.30.0


Benchmark results

Scenario Optimizations Native Laravel Optimized Speedup
Product import — 500 items, simple rules Wildcard, fast-check 222.0ms 3.2ms ~69x
Nested order lines — 1000 orders × 5 line items Wildcard, fast-check (nested) 2918.3ms 18.2ms ~160x
Event scheduling — 100 items, field-ref dates Wildcard, partial fast-check 32.1ms 1.0ms ~32x
Article submission — 50 items, custom Rule objects Wildcard only 10.2ms 3.3ms ~3x
Conditional import — 100 items, 47 conditional fields Wildcard, pre-evaluation 3539.6ms 68.2ms ~52x
Login form — 3 fields, no wildcards Fast-check (flat) 0.2ms 0.0ms ~14x