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.
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:
Phug\Parser — the entry point (parse(), create(), options).Phug\Parser\Node\* — the node vocabulary (DocumentNode, ElementNode, TextNode, AttributeNode, CodeNode, ConditionalNode, MixinNode, …).Phug\Lexer / Phug\Lexer\Token\* — what the parser is consuming.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.
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.
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.composer.lock, and read the changelog before minor bumps — Phug has historically shipped behaviour changes in minor releases.Phug\Lexer — indentation problems show up as wrong IndentToken counts, instantly.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).line => node index up front turns "why did my rule fire here" into a one-liner.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.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.How can I help you explore Laravel packages today?