Skip to content

Commit df304b6

Browse files
committed
add simple node lookup script
1 parent 22d8f5f commit df304b6

3 files changed

Lines changed: 347 additions & 2 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace fixture;
4+
5+
final class SomeSnippet
6+
{
7+
public function someMethod(string $param, int $anotherParam): void
8+
{
9+
foreach ([1, 2, 3] as $item) {
10+
echo $item * $anotherParam . ' ' . $param . PHP_EOL;
11+
}
12+
}
13+
}
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\FunctionLike;
7+
use PhpParser\ParserFactory;
8+
9+
require __DIR__ . '/../../vendor/autoload.php';
10+
11+
final class Map
12+
{
13+
public const NODE_INSTANCE_TO_TYPE_MAP = [
14+
\PhpParser\Node\Arg::class => [
15+
\PhpParser\Node\Arg::class,
16+
],
17+
\PhpParser\Node\ArrayItem::class => [
18+
\PhpParser\Node\ArrayItem::class,
19+
],
20+
\PhpParser\Node\ComplexType::class => [
21+
\PhpParser\Node\IntersectionType::class,
22+
\PhpParser\Node\UnionType::class,
23+
],
24+
\PhpParser\Node\Expr::class => [
25+
\PhpParser\Node\Expr\ArrayDimFetch::class,
26+
\PhpParser\Node\Expr\Array_::class,
27+
\PhpParser\Node\Expr\ArrayItem::class,
28+
\PhpParser\Node\Expr\Assign::class,
29+
\PhpParser\Node\Expr\AssignOp::class,
30+
\PhpParser\Node\Expr\AssignRef::class,
31+
\PhpParser\Node\Expr\BinaryOp::class,
32+
\PhpParser\Node\Expr\BooleanNot::class,
33+
\PhpParser\Node\Expr\Cast::class,
34+
//\PhpParser\Node\Expr\Clone_:class,
35+
\PhpParser\Node\Expr\Closure::class,
36+
\PhpParser\Node\Expr\ClosureUse::class,
37+
\PhpParser\Node\Expr\ConstFetch::class,
38+
//\PhpParser\Node\Expr\Empty_:class,
39+
\PhpParser\Node\Expr\ErrorSuppress::class,
40+
\PhpParser\Node\Expr\Exit_::class,
41+
\PhpParser\Node\Expr\FuncCall::class,
42+
//\PhpParser\Node\Expr\Include_:class,
43+
\PhpParser\Node\Expr\Instanceof_::class,
44+
//\PhpParser\Node\Expr\Isset_:class,
45+
\PhpParser\Node\Expr\List_::class,
46+
//\PhpParser\Node\Expr\MathError::class,
47+
\PhpParser\Node\Expr\Match_::class,
48+
\PhpParser\Node\Expr\MethodCall::class,
49+
\PhpParser\Node\Expr\New_::class,
50+
\PhpParser\Node\Expr\NullsafeMethodCall::class,
51+
\PhpParser\Node\Expr\NullsafePropertyFetch::class,
52+
\PhpParser\Node\Expr\PostDec::class,
53+
\PhpParser\Node\Expr\PostInc::class,
54+
\PhpParser\Node\Expr\PreDec::class,
55+
\PhpParser\Node\Expr\PreInc::class,
56+
\PhpParser\Node\Expr\Print_::class,
57+
\PhpParser\Node\Expr\PropertyFetch::class,
58+
\PhpParser\Node\Expr\ShellExec::class,
59+
\PhpParser\Node\Expr\StaticCall::class,
60+
\PhpParser\Node\Expr\StaticPropertyFetch::class,
61+
\PhpParser\Node\Expr\Ternary::class,
62+
\PhpParser\Node\Expr\Throw_::class,
63+
\PhpParser\Node\Expr\UnaryMinus::class,
64+
\PhpParser\Node\Expr\UnaryPlus::class,
65+
\PhpParser\Node\Expr\Variable::class,
66+
\PhpParser\Node\Expr\Yield_::class,
67+
\PhpParser\Node\Expr\YieldFrom::class,
68+
\PhpParser\Node\Expr\ArrowFunction::class,
69+
],
70+
\PhpParser\Node\Expr\AssignOp::class => [
71+
\PhpParser\Node\Expr\AssignOp\BitwiseAnd::class,
72+
\PhpParser\Node\Expr\AssignOp\BitwiseOr::class,
73+
\PhpParser\Node\Expr\AssignOp\BitwiseXor::class,
74+
\PhpParser\Node\Expr\AssignOp\Coalesce::class,
75+
\PhpParser\Node\Expr\AssignOp\Concat::class,
76+
\PhpParser\Node\Expr\AssignOp\Div::class,
77+
\PhpParser\Node\Expr\AssignOp\Minus::class,
78+
\PhpParser\Node\Expr\AssignOp\Mod::class,
79+
\PhpParser\Node\Expr\AssignOp\Mul::class,
80+
\PhpParser\Node\Expr\AssignOp\Plus::class,
81+
\PhpParser\Node\Expr\AssignOp\Pow::class,
82+
\PhpParser\Node\Expr\AssignOp\ShiftLeft::class,
83+
\PhpParser\Node\Expr\AssignOp\ShiftRight::class,
84+
],
85+
\PhpParser\Node\Expr\BinaryOp::class => [
86+
\PhpParser\Node\Expr\BinaryOp\BitwiseAnd::class,
87+
\PhpParser\Node\Expr\BinaryOp\BitwiseOr::class,
88+
\PhpParser\Node\Expr\BinaryOp\BitwiseXor::class,
89+
\PhpParser\Node\Expr\BinaryOp\BooleanAnd::class,
90+
\PhpParser\Node\Expr\BinaryOp\BooleanOr::class,
91+
\PhpParser\Node\Expr\BinaryOp\Coalesce::class,
92+
\PhpParser\Node\Expr\BinaryOp\Concat::class,
93+
\PhpParser\Node\Expr\BinaryOp\Div::class,
94+
\PhpParser\Node\Expr\BinaryOp\Equal::class,
95+
\PhpParser\Node\Expr\BinaryOp\Greater::class,
96+
\PhpParser\Node\Expr\BinaryOp\GreaterOrEqual::class,
97+
\PhpParser\Node\Expr\BinaryOp\Identical::class,
98+
\PhpParser\Node\Expr\BinaryOp\LogicalAnd::class,
99+
\PhpParser\Node\Expr\BinaryOp\LogicalOr::class,
100+
\PhpParser\Node\Expr\BinaryOp\LogicalXor::class,
101+
\PhpParser\Node\Expr\BinaryOp\Minus::class,
102+
\PhpParser\Node\Expr\BinaryOp\Mod::class,
103+
\PhpParser\Node\Expr\BinaryOp\Mul::class,
104+
\PhpParser\Node\Expr\BinaryOp\NotEqual::class,
105+
\PhpParser\Node\Expr\BinaryOp\NotIdentical::class,
106+
\PhpParser\Node\Expr\BinaryOp\Plus::class,
107+
\PhpParser\Node\Expr\BinaryOp\Pow::class,
108+
\PhpParser\Node\Expr\BinaryOp\ShiftLeft::class,
109+
\PhpParser\Node\Expr\BinaryOp\ShiftRight::class,
110+
\PhpParser\Node\Expr\BinaryOp\Smaller::class,
111+
\PhpParser\Node\Expr\BinaryOp\SmallerOrEqual::class,
112+
\PhpParser\Node\Expr\BinaryOp\Spaceship::class,
113+
],
114+
\PhpParser\Node\MatchArm::class => [
115+
\PhpParser\Node\MatchArm::class,
116+
],
117+
\PhpParser\Node\Name::class => [
118+
\PhpParser\Node\Name::class,
119+
\PhpParser\Node\Name\FullyQualified::class,
120+
\PhpParser\Node\Name\Relative::class,
121+
],
122+
\PhpParser\Node\Param::class => [
123+
\PhpParser\Node\Param::class,
124+
],
125+
\PhpParser\Node\Scalar::class => [
126+
\PhpParser\Node\Scalar\DNumber::class,
127+
\PhpParser\Node\Scalar\Encapsed::class,
128+
\PhpParser\Node\Scalar\EncapsedStringPart::class,
129+
\PhpParser\Node\Scalar\LNumber::class,
130+
\PhpParser\Node\Scalar\MagicConst::class,
131+
\PhpParser\Node\Scalar\String_::class,
132+
],
133+
\PhpParser\Node\Scalar\MagicConst::class => [
134+
\PhpParser\Node\Scalar\MagicConst\Class_::class,
135+
\PhpParser\Node\Scalar\MagicConst\Dir::class,
136+
\PhpParser\Node\Scalar\MagicConst\File::class,
137+
\PhpParser\Node\Scalar\MagicConst\Function_::class,
138+
\PhpParser\Node\Scalar\MagicConst\Line::class,
139+
\PhpParser\Node\Scalar\MagicConst\Method::class,
140+
\PhpParser\Node\Scalar\MagicConst\Namespace_::class,
141+
\PhpParser\Node\Scalar\MagicConst\Trait_::class,
142+
],
143+
\PhpParser\Node\Stmt::class => [
144+
\PhpParser\Node\Stmt\Break_::class,
145+
\PhpParser\Node\Stmt\Case_::class,
146+
\PhpParser\Node\Stmt\Catch_::class,
147+
\PhpParser\Node\Stmt\Class_::class,
148+
\PhpParser\Node\Stmt\ClassConst::class,
149+
\PhpParser\Node\Stmt\ClassMethod::class,
150+
\PhpParser\Node\Stmt\Const_::class,
151+
\PhpParser\Node\Stmt\Continue_::class,
152+
\PhpParser\Node\Stmt\Declare_::class,
153+
\PhpParser\Node\Stmt\Do_::class,
154+
\PhpParser\Node\Stmt\Echo_::class,
155+
\PhpParser\Node\Stmt\Enum_::class,
156+
\PhpParser\Node\Stmt\Expression::class,
157+
\PhpParser\Node\Stmt\Finally_::class,
158+
\PhpParser\Node\Stmt\For_::class,
159+
\PhpParser\Node\Stmt\Foreach_::class,
160+
\PhpParser\Node\Stmt\Function_::class,
161+
\PhpParser\Node\Stmt\Global_::class,
162+
\PhpParser\Node\Stmt\Goto_::class,
163+
\PhpParser\Node\Stmt\GroupUse::class,
164+
\PhpParser\Node\Stmt\HaltCompiler::class,
165+
\PhpParser\Node\Stmt\If_::class,
166+
\PhpParser\Node\Stmt\InlineHTML::class,
167+
\PhpParser\Node\Stmt\Interface_::class,
168+
\PhpParser\Node\Stmt\Label::class,
169+
\PhpParser\Node\Stmt\Namespace_::class,
170+
\PhpParser\Node\Stmt\Property::class,
171+
\PhpParser\Node\Stmt\PropertyHook::class,
172+
\PhpParser\Node\Stmt\Return_::class,
173+
\PhpParser\Node\Stmt\Static_::class,
174+
\PhpParser\Node\Stmt\StaticVar::class,
175+
\PhpParser\Node\Stmt\Switch_::class,
176+
\PhpParser\Node\Stmt\Throw_::class,
177+
\PhpParser\Node\Stmt\Trait_::class,
178+
\PhpParser\Node\Stmt\TraitUse::class,
179+
\PhpParser\Node\Stmt\TryCatch::class,
180+
\PhpParser\Node\Stmt\Unset_::class,
181+
\PhpParser\Node\Stmt\Use_::class,
182+
\PhpParser\Node\Stmt\UseUse::class,
183+
\PhpParser\Node\Stmt\While_::class,
184+
],
185+
\PhpParser\Node\Stmt\ClassLike::class => [
186+
\PhpParser\Node\Stmt\Class_::class,
187+
\PhpParser\Node\Stmt\Interface_::class,
188+
\PhpParser\Node\Stmt\Trait_::class,
189+
\PhpParser\Node\Stmt\Enum_::class,
190+
],
191+
\PhpParser\Node\FunctionLike::class => [
192+
\PhpParser\Node\Stmt\ClassMethod::class,
193+
\PhpParser\Node\Stmt\Function_::class,
194+
\PhpParser\Node\Expr\Closure::class,
195+
\PhpParser\Node\Expr\ArrowFunction::class,
196+
\PhpParser\Node\PropertyHook::class,
197+
],
198+
];
199+
}
200+
201+
$iterations = 10000; // number of traversals per run (set lower if file is big)
202+
$runs = 10;
203+
204+
// -----------------------------------------------------------------------------
205+
// 1. Helpers
206+
// -----------------------------------------------------------------------------
207+
208+
/**
209+
* Static table version – generic => list of concrete nodes
210+
*/
211+
function isFunctionLikeTable(Node $node): bool
212+
{
213+
return in_array(
214+
$node::class,
215+
Map::NODE_INSTANCE_TO_TYPE_MAP[FunctionLike::class] ?? [],
216+
true
217+
);
218+
}
219+
220+
function average(array $values): float
221+
{
222+
return array_sum($values) / count($values);
223+
}
224+
225+
// global sink to avoid dead-code elimination
226+
$GLOBALS['__bench_sink'] = false;
227+
228+
// -----------------------------------------------------------------------------
229+
// 2. Prepare nodes
230+
// -----------------------------------------------------------------------------
231+
232+
// real nodes from a real file
233+
$phpParserFactory = new ParserFactory();
234+
$phpParser = $phpParserFactory->createForHostVersion();
235+
236+
$stmts = $phpParser->parse(file_get_contents(__DIR__ . '/fixture/SomeSnippet.php'));
237+
238+
$nodeTraverser = new \PhpParser\NodeTraverser();
239+
240+
final class CheckPerformanceNodeVisitor extends \PhpParser\NodeVisitorAbstract
241+
{
242+
/** @var callable(Node): void */
243+
public $tester;
244+
245+
public function enterNode(Node $node)
246+
{
247+
// test here
248+
($this->tester)($node);
249+
}
250+
}
251+
252+
$visitor = new CheckPerformanceNodeVisitor();
253+
$nodeTraverser->addVisitor($visitor);
254+
255+
// -----------------------------------------------------------------------------
256+
// 3. Benchmark helper
257+
// -----------------------------------------------------------------------------
258+
259+
/**
260+
* @param callable(Node): void $callback
261+
*/
262+
function run_benchmark(
263+
\PhpParser\NodeTraverser $nodeTraverser,
264+
CheckPerformanceNodeVisitor $visitor,
265+
callable $callback,
266+
array $stmts,
267+
int $iterations
268+
): float {
269+
$visitor->tester = $callback;
270+
271+
$start = hrtime(true);
272+
273+
for ($i = 0; $i < $iterations; $i++) {
274+
$nodeTraverser->traverse($stmts);
275+
}
276+
277+
$elapsed = hrtime(true) - $start;
278+
279+
// return ns per traversal
280+
return $elapsed / $iterations;
281+
}
282+
283+
// -----------------------------------------------------------------------------
284+
// 4. Benchmark with multiple runs
285+
// -----------------------------------------------------------------------------
286+
287+
$tableDurations = [];
288+
$isADurations = [];
289+
290+
for ($run = 0; $run < $runs; $run++) {
291+
// Static table
292+
$tableDurations[] = run_benchmark(
293+
$nodeTraverser,
294+
$visitor,
295+
static function (Node $node): void {
296+
// static table lookup
297+
$GLOBALS['__bench_sink'] ^= isFunctionLikeTable($node);
298+
},
299+
$stmts,
300+
$iterations
301+
);
302+
303+
// is_a() with true
304+
$isADurations[] = run_benchmark(
305+
$nodeTraverser,
306+
$visitor,
307+
static function (Node $node): void {
308+
$GLOBALS['__bench_sink'] ^= is_a($node, FunctionLike::class, true);
309+
},
310+
$stmts,
311+
$iterations
312+
);
313+
}
314+
315+
// -----------------------------------------------------------------------------
316+
// 5. Output
317+
// -----------------------------------------------------------------------------
318+
319+
echo "Traversals per run: {$iterations}\n";
320+
echo "Runs: {$runs}\n\n";
321+
322+
echo "Average time per traversed file (nanoseconds):\n";
323+
echo "Static table: " . average($tableDurations) . " ns\n";
324+
echo "is_a() check: " . average($isADurations) . " ns\n";
325+
326+
if (average($isADurations) > 0) {
327+
echo "\nRatio (table / is_a): " . (average($tableDurations) / average($isADurations)) . "\n";
328+
}
329+
330+
echo "\nIgnore sink: " . (int) $GLOBALS['__bench_sink'] . "\n";

src/PhpParser/NodeTraverser/RectorNodeTraverser.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,16 @@ public function getVisitorsForNode(Node $node): array
6565
{
6666
$nodeClass = $node::class;
6767

68-
static $counter = 0;
68+
// static $counter = 0;
6969

7070
if (! isset($this->visitorsPerNodeClass[$nodeClass])) {
7171
$this->visitorsPerNodeClass[$nodeClass] = [];
7272
/** @var RectorInterface $visitor */
7373
foreach ($this->visitors as $visitor) {
7474
foreach ($visitor->getNodeTypes() as $nodeType) {
75-
++$counter;
75+
// ++$counter;
76+
77+
// dump($counter);
7678

7779
if (is_a($nodeClass, $nodeType, true)) {
7880
$this->visitorsPerNodeClass[$nodeClass][] = $visitor;

0 commit comments

Comments
 (0)