Skip to content

Speed up validation and building of validators - #32

Merged
antonfin merged 1 commit into
update-test-suitefrom
performance
Oct 19, 2026
Merged

antonfin merged 1 commit into
update-test-suitefrom
performance

Conversation

@koorchik

Copy link
Copy Markdown
Member

Depends on #31, please merge it after #31. Until then this PR targets the branch of #31, so it shows only its own commit.

Summary

Validation is 1.2-1.4 times faster, list_of and or 1.8 times, building a validator 1.5-1.8 times. The behavior does not change.

Operations per second, the same core, before and after:

Scenario PHP 8.5 PHP 7.2
Validate a form with 12 fields 133564 → 164736 (x1.23) 63612 → 78537 (x1.23)
Validate the form with errors 183118 → 259537 (x1.42) 105325 → 142583 (x1.35)
Build a validator and validate the form 37313 → 57583 (x1.54) 22999 → 37266 (x1.62)
Validate nested data (meta rules) 16089 → 22333 (x1.39) 12295 → 16620 (x1.35)
Build a validator with meta rules 36792 → 64799 (x1.76) 27959 → 54170 (x1.94)
list_of with 10 items 170100 → 309592 (x1.82) 134855 → 243877 (x1.81)
or 795777 → 1419614 (x1.78) 670635 → 1056665 (x1.58)
iso_date 782155 → 1979233 (x2.53) 677039 → 1721187 (x2.54)
one_of with 26 values 2059354 → 3303844 (x1.60) 1635113 → 2764467 (x1.69)
The LIVR test suite, building a validator for every case 939 → 1437 (x1.53) 668 → 1033 (x1.55)

Building a validator matters most in PHP, as it is usually done on every request.

Changes

  • validate() keeps the output of a field in variables instead of looking it up in the result twice for every rule.
  • Util::isAssocArray() uses array_is_list() on PHP 8.1+ and a loop on older versions instead of making two arrays.
  • A new validator shares the default rules instead of copying them, and registerRules() skips the merge when meta rules register the same rules.
  • Rule builders given as Class::method strings are called as closures, which are not resolved on every call.
  • parseRule() takes the first key without reset(), which copies the array.
  • New getFieldRule($fieldName) returns the rules of a field as one rule, like in the JavaScript implementation. list_of, or and aliases use it instead of calling validate() for every value. It is described in the README.
  • iso_date uses checkdate() instead of DateTime. This also fixes PHP 7.2, where DateTime turns 2769-12-31 into 2770-01-00, so some real dates were rejected and some wrong ones accepted.
  • one_of looks values up in a set instead of in_array().

Testing

  • PHPUnit on PHP 7.2 and 8.5: 77 tests pass, including a new test for getFieldRule(). phpcs passes.
  • The LIVR conformance harness: 68 of 70 cases pass, as with Update the LIVR test suite and fix the bugs it finds #31.
  • 200,000 random cases (random rules, including meta rules, aliases and auto trim, with random data) give the same output and errors before and after, on PHP 7.2 and 8.5.
  • iso_date gives the same results as before for every date from 0000 to 9999 (months 00-19, days 00-39) and for 300,000 changed strings on PHP 8.5. On PHP 7.2 only the dates above differ.

Found while testing, not changed here: on PHP 8, trim, to_lc and to_uc throw a TypeError when a list or an object has an array in it, for example ['a', ['b']].

On PHP 8.5 validation is 1.2-1.4 times faster, list_of and or 1.8 times,
building a validator 1.5-1.8 times. The LIVR test suite, where every case
builds a validator, runs 1.5 times faster. PHP 7.2 gets about the same.

- validate() keeps the output of a field in variables instead of looking it
  up in the result twice for every rule
- Util::isAssocArray() uses array_is_list() on PHP 8.1+ and a loop on older
  versions instead of making two arrays
- A new validator shares the default rules instead of copying them, and
  registerRules() skips the merge when meta rules register the same rules
- Rule builders given as "Class::method" strings are called as closures,
  which are not resolved on every call
- parseRule() takes the first key without reset(), which copies the array
- getFieldRule() returns the rules of a field as one rule, like in the
  JavaScript implementation. list_of, or and aliases use it instead of
  calling validate() for every value.
- iso_date uses checkdate() instead of DateTime. This also fixes PHP 7.2,
  where DateTime turns 2769-12-31 into 2770-01-00.
- one_of looks values up in a set instead of in_array()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants