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

Parser Laravel Package

phug/parser

phug/parser is the PHP parser behind Phug, converting Pug/Jade template source into an abstract syntax tree. It tokenizes and parses Pug syntax—tags, attributes, expressions, mixins, includes—for the Phug rendering engine.

View on GitHub
Deep Wiki
Context7

Getting Started

phug/parser is the AST-building stage of the Phug (Pug/Jade for PHP) pipeline. It does not render anything — it turns Pug source into a tree of Phug\Parser\Node\* objects that the compiler then consumes. Install it directly only when you need the tree itself (linting, static analysis, custom transpilation, editor tooling):

composer require phug/parser

Note the dependency direction: the repository you're looking at (phug-php/reader) is the lexer, which phug/parser requires as phug/reader. You get both, and most token-level work happens in the reader's namespace (Phug\Lexer\Token\*), not the parser's.

Look first at, in this order:

  1. Phug\Parser — the entry point (parse(), create(), options).
  2. Phug\Parser\Node\* — the node vocabulary (DocumentNode, ElementNode, TextNode, AttributeNode, CodeNode, ConditionalNode, MixinNode, …).
  3. Phug\Lexer / Phug\Lexer\Token\* — what the parser is consuming.
  4. Phug\Parser\TokenHandler\* — one class per token type; this is where indentation, attributes, and blocks actually get assembled.

First use case — dump the tree for a snippet:

use Phug\Parser;

$parser = Parser::create();               // or new Parser($options)
$document = $parser->parse(
    "div.card\n  h1 Hello\n  p= \$name\n",
    'example.pug'                          // path is used for error locations
);

foreach ($document->getChildren() as $node) {
    printf("%-22s %s\n", get_class($node), $node->getName());
}

Once you can see the node classes and names, everything else is walking and matching.

Implementation Patterns

Parse-then-walk (the 90% case). Nodes are a plain traversable tree: getChildren(), getParent(), appendChild(), removeChild(), getName(), getValue(), plus getToken() on Phug\Parser\NodeInterface. A recursive walker is usually less code than any visitor abstraction:

function walk($node, callable $fn): void
{
    $fn($node);
    foreach ($node->getChildren() as $child) {
        walk($child, $fn);
    }
}

walk($document, function ($node) {
    if ($node instanceof \Phug\Parser\Node\ElementNode) {
        // $node->getName() === 'div', 'h1', ...
    }
});

Do the reader/lexer separately when you need token context. For syntax highlighting, lint rules, or "what did the indentation actually produce", run Phug\Lexer first and inspect Phug\Lexer\Token\IndentToken, NewlineToken, TagToken, AttributeStartToken, TextToken, etc. This is also the fastest way to debug a parser surprise — see whether the tokens are right before blaming the tree.

Stop at the AST deliberately. If your goal is output HTML you almost certainly want phug/phug (or phug/compiler) instead, because you'll be reimplementing the compiler. Legitimate cases to stay on the parser: template linting, extracting a list of used mixins/variables, generating a JSON outline for a CMS, or compiling Pug to a different target (Twig, JSX, Vue SFCs).

Mutate the tree before handing off. Nodes are mutable and parent links are maintained by the AST package, so an "import inliner" or "wrap everything in a layout" pass is just appendChild / removeChild at the right level. Do the mutation before compiling, and keep a reference to the DocumentNode — don't try to re-parent a node you've already detached without re-adding it.

Prefer Parser::create() for symmetry. The Phug ecosystem consistently exposes static create() factories that wire defaults; use them unless you're injecting a configured lexer/state in tests.

Test against fixtures, not strings-in-code. Keep .pug fixtures on disk and assert on a normalized dump (class + name + value per line). The tree shape is the contract; string-comparing var_export output will churn on every release.

Gotchas and Tips

  • Package/repo mismatch. If you browse to phug-php/reader looking for parser docs you'll land in the lexer. The Composer name is phug/parser; the lexer is a separate package and a separate namespace. Confirm what you actually have installed with composer show phug/parser phug/reader before reading source.
  • Tiny ecosystem, so pin exact versions. Single-digit stars and zero dependents means few eyes on edge cases and no community safety net. Use a caret constraint plus a committed composer.lock, and read the changelog before minor bumps — Phug has historically shipped behaviour changes in minor releases.
  • Whitespace is the grammar. Mixed tabs and spaces in one file, trailing blank lines, or a BOM at the top will produce confusing trees rather than clean errors. When something looks impossible, re-run through Phug\Lexer — indentation problems show up as wrong IndentToken counts, instantly.
  • Always pass the filename. parse($input, null) still works, but exception messages and node source locations lose their origin. Pass the real path even for in-memory strings ('stdin.pug' is fine).
  • Source locations are your debugging lever. Exceptions derive from the Phug exception hierarchy and carry line/offset info; node tokens carry the same. Building a line => node index up front turns "why did my rule fire here" into a one-liner.
  • Invisible wrapper nodes. Text is wrapped in TextNode, code in CodeNode, and blocks/indentation can introduce BlockNodes that aren't in your source. Filter by class explicitly instead of assuming getChildren() maps 1:1 to source lines.
  • Extension points are the token handlers. Custom syntax goes in as a token handler class registered via parser options rather than by subclassing the parser and overriding parse(). Check the installed version's Phug\Parser\TokenHandler\* for the exact interface before writing one — this is the least-stable surface of the package.
  • Don't cache the parser between documents. Instantiate per-parse (or at least reset state) rather than reusing one instance across a loop; internal state and parent links are per-parse.
  • Performance is rarely the parser. If a large template set feels slow, profile the compiler/renderer before optimizing the parse stage — parsing is cheap relative to code generation.
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.
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules