Memoize constraint resolution and attribute lookups in EntityValidator - #85
Merged
Conversation
sylfabre
force-pushed
the
feat/memoize-entity-validator
branch
2 times, most recently
from
August 22, 2026 11:31
edaba15 to
076bb05
Compare
|
❌ The last analysis has failed. |
Cache per (class, field): the resolved constraint set and the OnlyValidateOnUpdate attribute presence, and reuse a single PropertyAccessor instance, since all three only depend on static class metadata. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…HPStan 2.2 The CI upgrades PHPStan to latest after the prefer-lowest install; shipmonk 4.2 predates the ExpressionResultStorage API and crashes on files using callables. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Rector Older releases register StaticArrowFunctionRector, which current Rector rejects. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
sylfabre
force-pushed
the
feat/memoize-entity-validator
branch
from
August 23, 2026 20:54
89c0b10 to
120a028
Compare
…upgrade on lowest-deps CI Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2 tasks
…phpstan-rules; fold them into the lowest-deps tool-upgrade step instead Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What
EntityValidatorrecomputed three things on every validated entity instance, even though they only depend on the entity class (and are static for the lifetime of the process):getConstraints($class, $field)rebuilt the whole constraint array from the Doctrine mapping for every entity instance. It is now memoized per(class, field): the previous body moved to a privatebuildConstraints()andgetConstraints()returns the cached set. The public API (getConstraints()/getConstraintsForType()) keeps its signature and behavior, so external callers benefit from the cache too.OnlyValidateOnUpdateattribute lookup incheckIfFieldNeedsToBeValidated()performed anew \ReflectionClass()+ attribute scan + parent-class recursion per field per instance. The attribute presence is now cached as a boolean per(class, field); only when the attribute is present does the (inherently per-instance) UnitOfWork changeset check still run.PropertyAccess::createPropertyAccessor()was called on everyvalidate()call.PropertyAccessorcaches property read-info per instance, so a fresh accessor forced re-resolution every time. A single lazily-created instance is now reused (no constructor/DI change).Why
Profiling a large application using this bundle, which persists many entities per request, showed for a batch of ~720 validated entities: ~9 700 constraint-set rebuilds constructing ~15 800 constraint objects (for only ~150 unique class/field pairs), ~22 000
ReflectionClassinstantiations, and ~17 900 property read-info resolutions — all recomputing values that never change within a process.Why it is safe
(class, field)is safe.OnlyValidateOnUpdateattribute is a static fact of the class definition. The per-instance part (the UnitOfWork changeset lookup) is not cached.PropertyAccessoris stateless apart from its internal read-info cache, which is exactly what reusing the instance is meant to leverage.Behavior is unchanged and covered by tests:
getConstraints()calls return the same (cached) set (assertSame),OnlyValidateOnUpdate: the field is skipped when not in the changeset (insert) and validated when it is (update).Full suite + phpcs + phpstan + rector are green locally on PHP 8.4 with highest deps (the CI matrix covers the other combinations).
🤖 Generated with Claude Code