From 00fdf9a4faad4c4bdc8da8536158b63879b84869 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 19 Oct 2026 22:32:34 +0200 Subject: [PATCH] [perf] Decide named constructor from node visibility before reflection --- bench/named_constructor_visibility_bench.php | 98 +++++++++++++++++++ .../ClassMethodManipulator.php | 15 +-- 2 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 bench/named_constructor_visibility_bench.php diff --git a/bench/named_constructor_visibility_bench.php b/bench/named_constructor_visibility_bench.php new file mode 100644 index 00000000000..993196cf7e6 --- /dev/null +++ b/bench/named_constructor_visibility_bench.php @@ -0,0 +1,98 @@ +create(); +$rectorConfig->boot(); + +/** @var TestingParser $testingParser */ +$testingParser = $rectorConfig->make(TestingParser::class); + +/** @var BetterNodeFinder $betterNodeFinder */ +$betterNodeFinder = $rectorConfig->make(BetterNodeFinder::class); + +$classMethodManipulator = $rectorConfig->make(ClassMethodManipulator::class); + +$source = "parseFileToDecoratedNodes($sourceFilePath); + +/** @var ClassMethod[] $constructors */ +$constructors = array_filter( + $betterNodeFinder->findInstanceOf($stmts, ClassMethod::class), + static fn (ClassMethod $classMethod): bool => $classMethod->name->toString() === MethodName::CONSTRUCT +); + +$cpuSeconds = static function (): float { + $usage = getrusage(); + return $usage['ru_utime.tv_sec'] + $usage['ru_utime.tv_usec'] / 1_000_000 + + $usage['ru_stime.tv_sec'] + $usage['ru_stime.tv_usec'] / 1_000_000; +}; + +$run = static function () use ($classMethodManipulator, $constructors): void { + foreach ($constructors as $constructor) { + $classMethodManipulator->isNamedConstructor($constructor); + } +}; + +// warm up autoloading/reflection caches once, excluded from timing +$run(); + +$wallStart = microtime(true); +$cpuStart = $cpuSeconds(); + +for ($repeat = 0; $repeat < REPEAT; ++$repeat) { + $run(); +} + +$cpu = $cpuSeconds() - $cpuStart; +$wall = microtime(true) - $wallStart; + +@unlink($sourceFilePath); + +$totalCalls = REPEAT * count($constructors); + +printf('constructors: %d, repeats: %d, isNamedConstructor() calls: %d%s', count($constructors), REPEAT, $totalCalls, PHP_EOL); +printf('CPU: %.4f s (%.3f us/call)%s', $cpu, $cpu / $totalCalls * 1_000_000, PHP_EOL); +printf('wall: %.4f s%s', $wall, PHP_EOL); diff --git a/src/NodeManipulator/ClassMethodManipulator.php b/src/NodeManipulator/ClassMethodManipulator.php index 2d23690f4c6..288e33dbba9 100644 --- a/src/NodeManipulator/ClassMethodManipulator.php +++ b/src/NodeManipulator/ClassMethodManipulator.php @@ -25,20 +25,21 @@ public function isNamedConstructor(ClassMethod $classMethod): bool return false; } - $classReflection = $this->reflectionResolver->resolveClassReflection($classMethod); - if (! $classReflection instanceof ClassReflection) { - return false; - } - + // visibility is local to the node; only the protected case needs the class final keyword if ($classMethod->isPrivate()) { return true; } - if ($classReflection->isFinalByKeyword()) { + if (! $classMethod->isProtected()) { + return false; + } + + $classReflection = $this->reflectionResolver->resolveClassReflection($classMethod); + if (! $classReflection instanceof ClassReflection) { return false; } - return $classMethod->isProtected(); + return ! $classReflection->isFinalByKeyword(); } public function hasParentMethodOrInterfaceMethod(Class_ $class, string $methodName): bool