Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Parser Laravel Package

nikic/php-parser

A PHP parser written in PHP that converts PHP 7/8 code into an abstract syntax tree, with tools for dumping, traversing, modifying, and regenerating code, plus name resolution and builders for static analysis and code generation.

View on GitHub
Deep Wiki
Context7

Getting Started

Verdict: nikic/php-parser is not a Laravel package you “use” in the request lifecycle; it’s low-level infrastructure for tools that read or rewrite PHP source. If you build custom static analysis, upgrade codemods, architecture tests, or code generators, it is the de facto standard and worth learning. If you only need opinionated refactoring, use Rector (which wraps this library) instead.

Install and pin the major version:

composer require nikic/php-parser:^5.9

v5 requires PHP >= 7.4 and parses PHP 7.0–8.4 (with newer syntax added in point releases). Laravel 11/12 apps already satisfy the runtime requirement.

Where to look first, in order:

  1. README quick start — parse, dump, traverse, print.
  2. doc/2_Usage_of_basic_components.markdown — the parser/traverser/printer triad.
  3. doc/component/Walking_the_AST.markdown — visitors, NodeFinder, short-circuiting.
  4. doc/component/Pretty_printing.markdown — especially formatting-preserving printing.
  5. doc/component/FAQ.markdown — parent/sibling references and other common confusion.

Fastest first use case: inspect a real file instead of writing code.

vendor/bin/php-parse --dump app/Models/User.php
vendor/bin/php-parse --resolve-names --with-positions app/Http/Controllers/UserController.php

Then do the same in an Artisan command or php artisan tinker:

use PhpParser\Node;
use PhpParser\NodeFinder;
use PhpParser\ParserFactory;

$code = file_get_contents(app_path('Http/Controllers/UserController.php'));

$parser = (new ParserFactory())->createForNewestSupportedVersion();
$ast = $parser->parse($code);

$staticCalls = (new NodeFinder())->findInstanceOf($ast, Node\Expr\StaticCall::class);

foreach ($staticCalls as $call) {
    echo $call->class->toString() . '::' . $call->name->toString() . PHP_EOL;
}

That is already enough to implement an architecture rule such as “controllers may not call DB:: directly.” Bind the parser once in a service provider if you use it in multiple commands:

$this->app->singleton(\PhpParser\Parser::class, fn () => (new \PhpParser\ParserFactory())
    ->createForNewestSupportedVersion());

Implementation Patterns

1. Choose the right tool: NodeFinder for reads, visitors for writes.

For one-off queries, NodeFinder is less ceremony:

$finder = new NodeFinder();
$classes = $finder->findInstanceOf($ast, Node\Stmt\Class_::class);
$first = $finder->findFirst($ast, fn (Node $n) => $n instanceof Node\Stmt\ClassMethod);

For anything structural or repeated, use NodeTraverser + NodeVisitorAbstract. Return values matter: from enterNode() return a Node to replace the current node, or NodeTraverser::DONT_TRAVERSE_CHILDREN; from leaveNode() return a Node, an array of nodes, NodeTraverser::REMOVE_NODE, or NodeTraverser::STOP_TRAVERSAL.

2. The standard codemod pipeline: parse → clone → modify → format-preserving print.

use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor;
use PhpParser\NodeVisitorAbstract;
use PhpParser\NodeVisitor\CloningVisitor;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter;

$parser = (new ParserFactory())->createForNewestSupportedVersion();

$oldStmts = $parser->parse($code);
$oldTokens = $parser->getTokens();

$traverser = new NodeTraverser(
    new CloningVisitor(), // keep $oldStmts intact for the diff printer
    new class extends NodeVisitorAbstract {
        public function enterNode(Node $node) {
            if ($node instanceof Node\Stmt\ClassMethod && $node->name->toString() === 'legacy') {
                $node->name = new Node\Identifier('modern');
            }
        }
    }
);

$newStmts = $traverser->traverse($oldStmts);

$code = (new PrettyPrinter\Standard())->printFormatPreserving(
    $newStmts,
    $oldStmts,
    $oldTokens
);

Formatting preservation is the difference between a reviewable one-line diff and a whole-file reformat. It only works when printing a modified version of the same AST you parsed; it cannot preserve formatting for nodes you constructed from scratch.

3. Run NameResolver before semantic analysis.

Without it, a Name node is exactly as written (Baz), including aliases. With it, you get resolved names:

use PhpParser\NodeVisitor\NameResolver;

$traverser = new NodeTraverser(
    new NameResolver(null, [
        'preserveOriginalNames' => true,
        'replaceNodes' => false,
    ])
);
$traverser->traverse($ast);

// $name->getAttribute('resolvedName') is a fully qualified Name

Use replaceNodes => false when you need both the original source spelling (for rewriting) and the resolved target (for matching). When generating new code from resolved names, emit Node\Name\FullyQualified so you don’t depend on existing use statements.

4. Generate code with BuilderFactory, not hand-built arrays.

use PhpParser\BuilderFactory;

$factory = new BuilderFactory();

$class = $factory->class('InvoiceAssembler')
    ->makeFinal()
    ->implement('App\Contracts\Assembler')
    ->addStmt(
        $factory->method('assemble')
            ->makePublic()
            ->addParam($factory->param('invoice'))
            ->setReturnType('array')
            ->addStmt(new Node\Stmt\Return_($factory->val([])))
    );

$stmts = $factory->namespace('App\Support')->addStmt($class)->getNode();
echo (new PrettyPrinter\Standard())->prettyPrintFile([$stmts]);

This is the sane way to write custom make: commands, scaffold DTOs, or generate migration-like classes.

5. Wire it into a Laravel command with a dry-run and a diff.

A practical day-to-day workflow:

  • Resolve targets with Symfony Finder (app_path('**/*.php')), excluding vendor/, storage/, and generated directories.
  • Parse file-by-file, not the whole codebase at once.
  • Run NameResolver and your visitors.
  • Print to a string, compare with the original; skip identical output to keep the tool idempotent.
  • Print a unified diff or write only with --write; require a clean git tree.
  • Log per-file parse errors and continue.

Also expose --dry-run and --path= options. Codemods that cannot be dry-run will not be trusted by your team.

6. Cache and serialize when scanning repeatedly.

ASTs are expensive to build. For repeated analysis, cache by file path + content hash. If you need to persist an AST, use the library’s JSON representation (json_encode($ast) / PhpParser\JsonDecoder) — but only after removing object-valued attributes such as parent, or you will create cycles. For most CI use cases, a fresh parse with opcache warm is simpler.

7. Constant evaluation is useful for config-like AST.

ConstExprEvaluator evaluates class constant initializers, default parameter values, and similar expressions. A closure handles unsupported nodes. Do not use it on untrusted code: historically it allowed function calls via the pipe operator, which was removed for security reasons.

Gotchas and Tips

Version/API churn is real. The 4.x → 5.x upgrade renamed nodes (Scalar\LNumber → Scalar\Int_, Scalar\DNumber → Scalar\Float_), removed Name::$parts (use getParts()/toString()), and changed the parser factory API. ParserFactory::create() is gone; use createForNewestSupportedVersion(), createForHostVersion(), or createForVersion(PhpVersion::fromString('8.2')). Pin the major version and read UPGRADE-5.0.md before a jump.

Parsing version ≠ running version. createForHostVersion() on PHP 8.2 will reject PHP 8.4 syntax in the files you analyze. createForNewestSupportedVersion() is usually what you want for analysis, even if your runtime is older. Conversely, configure the pretty printer’s target version if generated code must run on an older PHP.

Formatting-preserving printing has sharp edges. You must pass the original AST and the exact tokens from the same parse. You must clone before mutating, otherwise the “old” tree is already modified and the diff printer has nothing to compare. New nodes have no original formatting, so comments around them may move or disappear; expect approximate output near insertions. Catch printer exceptions and fall back to a full reformat only when acceptable.

Comments are attributes, not child nodes. Traversal does not enter comments. Use $node->getComments() / getDocComment(); modify with setDocComment(). If you rewrite a declaration node, copy the docblock explicitly if it should survive.

Parent references create cycles. ParentConnectingVisitor stores a parent attribute; that is invaluable for context-sensitive rules, but it will break json_encode() and can retain huge trees in memory. Strip parent attributes before serialization, and don’t keep ASTs alive longer than necessary.

Error handling: decide between fail-fast and recovery. By default the parser throws PhpParser\Error. For tools that must process partially broken repositories, pass new ErrorHandler\Collecting() to parse() and inspect getErrors(). Error recovery yields partial trees with placeholder error nodes; guard visitors against missing/null children and unexpected node shapes. getStartLine() is 1-based; startFilePos/endFilePos attributes are byte offsets suitable for editor integrations.

Don’t assume names are simple. Anonymous classes have Class_::$name === null. Methods/functions use Identifier, class references use Name. Param::$var is a Variable whose name is a string. Promoted constructor properties are represented through flags; use Param::isPromoted(), isPublic(), etc., rather than poking modifier bitmasks.

Pretty-printer precedence is a feature — don’t fight it. Build expression trees with real nodes; the printer inserts parentheses based on precedence. Hand-concatenating strings or stuffing raw code into scalar nodes will produce invalid precedence in edge cases. If you must preserve an exact literal, use the rawValue/shouldPrintRawValue attributes introduced in recent 5.x releases rather than abusing String_.

Performance: disable Xdebug for CLI analysis (often a 5–10× difference), reuse parser/printer instances, and process files individually. The traverser visits every node, so combine related checks in one visitor instead of running five passes. Use DONT_TRAVERSE_CHILDREN once you’ve matched a subtree you don’t care about.

Debug with the dumper, not var_dump. NodeDumper supports comments and positions:

echo (new NodeDumper(['dumpComments' => true, 'dumpPositions' => true]))->dump($ast);

Composer conflicts are common. PHPStan, Rector, and other dev tools also depend on this package; a stale php-parser constraint can silently force an old major. Run composer why nikic/php-parser before debugging “missing method” errors.

Security: parsing is generally safe, but never eval() parsed user input, and be conservative with constant-expression evaluation. Treat the parser as a source-code analysis tool, not a sandbox.

Extension points: custom NodeVisitor for rules, a subclass of PrettyPrinter\Standard for formatting tweaks, a custom ErrorHandler for diagnostics, and node attributes for carrying your own metadata. You cannot add new grammar productions without forking the parser, but attributes plus visitors cover most application-specific needs.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo
bernskioldmedia/laravel-harvest
bernskiold/laravel-data-scrubber
bernskiold/laravel-currency-converter
bernskiold/laravel-campaign-monitor
bernskioldmedia/laravel-autotranslate
siberfx/telegram-bot
roy-sandip/laravel-view-extended
gregsphotography/site-builder-core
roy-sandip/laravel-view
anodyne/blade-fluentui-icons
webx-ui/module-catalog-collections