Two additions this release: the FluentSchema builder — an instance-based mirror
of the FluentRule factory that lets you drop the FluentRule:: prefix — and a fifth
automatic optimization, worker-global memoization of Laravel's rule-string parsing.
Both are backward-compatible; existing rules() methods and call sites are unchanged.
Added
-
FluentSchema builder. Define a
schema(FluentSchema $rules)method on a form
request and chain field starters off the injected$rulesbuilder instead of
repeating theFluentRule::prefix on every line. When a request defines both
schema()andrules(),schema()wins — detection keys off the
FluentSchema-typed parameter, so an unrelatedschema()method is never hijacked.
For inline use,RuleSet::define(fn (FluentSchema $rules) => [...])builds a
RuleSetfrom the same builder.FluentSchemaexposes typed delegates for every
FluentRulefactory (single source of truth onFluentRule) and forwards macros,
and mirrors theschema(JsonSchema)shape Laravel's AI SDK uses for structured
output. -
MemoizingValidator. AValidatorsubclass that memoizes
ValidationRuleParser::parse()for string rules in a bounded, worker-global static.
Laravel re-parses each string rule on every internal probe — and, on a validator
reused across array items, once per item; the memo collapses those repeats to a
single hash lookup.OptimizedValidatornow extends it, so the residual slow path
of the optimized entry points (HasFluentRules,FluentValidator,
RuleSet::validate(),HasFluentValidation) picks it up automatically. Output stays
byte-identical to Laravel; only string rules are cached. The cache is soft-capped and
reset on overflow, and holds only rule-string → parse-result pairs (never request
data), so it stays Octane-safe.
Performance
- Per-item slow-rule validation reuses parsed rules across an array.
RuleSet::validate()andHasFluentRulesroute per-item slow-rule validators
throughMemoizingValidator, so a large array whose items fall through to Laravel
(customRuleobjects, cross-field references) parses each rule string once for the
whole array rather than re-parsing per item. The gain is largest on conditional-heavy
and slow-rule-heavy payloads; simple fast-checkable arrays are unaffected — they
already bypass Laravel entirely. - Custom validator resolvers are preserved. When an application registers a custom
Validator::resolver(), the per-item path uses that validator unchanged, so any
resolver-provided behaviour continues to apply.
Internal
- Extracted the factory-state reflection copy shared by the optimized validator paths
intoValidatorStateCopier, and taught it to carry wildcardimplicitAttributesso
nested-wildcard expansion metadata pairs with the copied rules.
What's Changed
- chore(deps): bump actions/checkout from 6 to 7 by @dependabot[bot] in #18
Full Changelog: 1.30.1...1.31.0
Benchmark results
| Scenario | Optimizations | Native Laravel | Optimized | Speedup |
|---|---|---|---|---|
| Product import — 500 items, simple rules | Wildcard, fast-check | 232.4ms | 3.4ms | ~69x |
| Nested order lines — 1000 orders × 5 line items | Wildcard, fast-check (nested) | 2885.6ms | 21.9ms | ~132x |
| Event scheduling — 100 items, field-ref dates | Wildcard, partial fast-check | 33.3ms | 1.0ms | ~32x |
| Article submission — 50 items, custom Rule objects | Wildcard only | 10.7ms | 3.0ms | ~4x |
| Conditional import — 100 items, 47 conditional fields | Wildcard, pre-evaluation | 3449.9ms | 51.5ms | ~67x |
| Login form — 3 fields, no wildcards | Fast-check (flat) | 0.2ms | 0.0ms | ~15x |