From 395eba2a9cb12d6ee357cf7c6d9f2efb647909af Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Fri, 17 Jul 2026 11:58:17 +0200 Subject: [PATCH] Cache QueryBuilder class classification Cache whether each stable receiver class name is a QueryBuilder instead of repeating ClassReflection::is() for every call on that class. Receiver classification is argument- and scope-independent, and the measured resolver performs more than 325,000 receiver reflection passes. Caching only this boolean avoids repeated hierarchy checks without caching scope-dependent method resolution. Benchmark caveat: the recorded five-run Phase 5 candidate included this cache plus the separately committed cheap-check reorder; the cache was not measured alone. Together they reduced median elapsed time from 28.45 s to 27.57 s (-3.1%) and aggregate user+sys CPU from 168.90 s to 164.70 s (-2.5%). All five follow-up optimizations combined reduced elapsed time by 8.7% and CPU by 5.4% across 20 interleaved runs. --- .../ReturnQueryBuilderExpressionTypeResolverExtension.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Type/Doctrine/QueryBuilder/ReturnQueryBuilderExpressionTypeResolverExtension.php b/src/Type/Doctrine/QueryBuilder/ReturnQueryBuilderExpressionTypeResolverExtension.php index 5f308ba1..a4502807 100644 --- a/src/Type/Doctrine/QueryBuilder/ReturnQueryBuilderExpressionTypeResolverExtension.php +++ b/src/Type/Doctrine/QueryBuilder/ReturnQueryBuilderExpressionTypeResolverExtension.php @@ -25,6 +25,9 @@ class ReturnQueryBuilderExpressionTypeResolverExtension implements ExpressionTyp private OtherMethodQueryBuilderParser $otherMethodQueryBuilderParser; + /** @var array */ + private array $queryBuilderClasses = []; + public function __construct( OtherMethodQueryBuilderParser $otherMethodQueryBuilderParser ) @@ -85,7 +88,8 @@ private function getMethodReflection(CallLike $call, Scope $scope): ?MethodRefle $methodName = $call->name->name; foreach ($callerType->getObjectClassReflections() as $callerClassReflection) { - if ($callerClassReflection->is(QueryBuilder::class)) { + $className = $callerClassReflection->getName(); + if (($this->queryBuilderClasses[$className] ??= $callerClassReflection->is(QueryBuilder::class))) { return null; // covered by QueryBuilderMethodDynamicReturnTypeExtension } if ($callerClassReflection->is(EntityRepository::class) && $methodName === 'createQueryBuilder') {