phpstan/phpstan-php-parser
PHPStan extension for nikic/php-parser that adds precise type inference for AST nodes and visitors, improving static analysis of code that parses, traverses, or manipulates PHP source.
Install as a dev dependency alongside an existing PHPStan setup:
composer require --dev phpstan/phpstan-php-parser
Register the extension in phpstan.neon (or phpstan.neon.dist):
includes:
- vendor/phpstan/phpstan-php-parser/extension.neon
First thing to inspect after install: vendor/phpstan/phpstan-php-parser/extension.neon. That file lists every service it registers (stub files and dynamic return type extensions) — it tells you exactly which calls will get smarter analysis in your codebase.
The first use case that pays off is analyzing custom PHP-Parser visitors and Rector rules. Without this extension, PHPStan sees Node and Node[] almost everywhere and you get endless method_exists is not defined on PhpParser\Node complaints. With it loaded, node-typed properties, getType() calls, and NodeFinder return values resolve to the concrete subclasses you actually expect.
Skeleton sanity check before you build anything big:
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
if ($node instanceof MethodCall) {
// PHPStan now knows $node->var and $node->name
/** @var Node\Identifier|Node\Expr $name */
$name = $node->name;
}
Instanceof narrowing in visitors. The primary value-add. Inside enterNode(Node $node), write your instanceof chain normally; the extension's stubs give PHPStan accurate property types on the narrowed class — MethodCall::$name, ClassMethod::$stmts, FunctionLike::$params, etc. No more docblock noise.
NodeFinder generics. When the extension is active, NodeFinder::findInstanceOf() and findFirstInstanceOf() return the narrowed generic type you pass in:
/** @var MethodCall[] $calls */
$calls = (new NodeFinder())->findInstanceOf($ast, MethodCall::class);
Skip the extension and PHPStan degrades to Node[], forcing a manual @var or assert() in every call site.
Custom Rule classes. In PHPStan rules that walk ASTs from php-parser, use native language narrowing for parameters and local variables; the extension carries it through chained calls like $class->getMethod(...)->stmts.
Analyzing your own node subclasses. If you extend PhpParser\Node\Expr and register custom node handlers, the extension gives you correct parent-class property types but cannot know your subclass's specific properties. Pair with @property docblocks on your subclass.
Combining with Rector. If your project also uses rector/rector, both packages depend on nikic/php-parser; keep the versions aligned (see Gotchas). The extension's stubs are additive, so no conflict in analysis behavior — the version pin is the only real risk.
Test-time usage. In PHPUnit tests that build AST fixtures, add @var assertions against narrowed types and let phpstan analyse tests/ validate the fixture shapes. Useful when you write a visitor and want to confirm the fixture actually contains the nodes you expect.
Archived since 2021-12-29 — treat as frozen. No fixes will land, and nikic/php-parser has moved past 4.x. Its stubs describe PHP-Parser v4 node shapes; if your project depends on php-parser 4.10+ or any 5.x branch, some properties (ClassMethod::$flags, Expr\Closure::$attrGroups, etc.) will not be as precisely typed as you'd hope. Modern PHPStan (1.9+) ships internal php-parser stubs that partially overlap, so double-check whether you actually need this package at all before adding it.
nikic/php-parser version conflicts are the #1 failure mode. PHPStan itself bundles a specific php-parser range, and Rector bundles another. composer why nikic/php-parser before installing. If Composer wants to downgrade php-parser or Rector, back out — you'll break analysis of php-parser-using code elsewhere.
Silent activation failures. The extension only kicks in for files it's told to analyze. If narrowing isn't working:
extension.neon is actually included — run vendor/bin/phpstan analyse --debug and grep for the package name.vendor/bin/phpstan clear-result-cache.excludePaths / paths — the file must be in scope.getAttribute() returns mixed, always. No extension can help here; the attribute key space is user-defined. Always cast or narrow manually:
$loc = $node->getAttribute('startLine');
assert(is_int($loc));
Dynamic node types via factories aren't inferred. If you have a helper that returns a Node built from user config, PHPStan still sees Node. Add explicit return-type docblocks (@return Expr\BinaryOp) to your factory — the extension doesn't rewrite return types of your code.
No extension points for the extension itself. It's a fixed bundle of stubs + dynamic return types. If you need a narrower type (e.g. NodeFinder on a specific generic), the correct approach is to write your own PhpStan\Type\DynamicMethodReturnTypeExtension in a project-local extension rather than patching this one — you can't safely fork an archived package.
Bleeding-edge config is sometimes required. Older PHPStan releases needed:
includes:
- phar://phpstan.phar/conf/bleedingEdge.neon
This no longer matters on current PHPStan, but if you're pinned to pre-1.0, include it or narrowing silently regresses.
Debugging tip. When PHPStan reports a type you believe is wrong ("Method PhpParser\Node\Stmt\Class_::getMethods() not found"), check whether you're hitting a stub-defined property vs. a real one — run vendor/bin/phpstan dump-parameters then vendor/bin/phpstan describe-type 'PhpParser\Node\Stmt\Class_' on PHPStan 1.10+ to see what the extension actually contributed.
How can I help you explore Laravel packages today?