Parsing
Documents
The Document class is the entry point for working with Forte. It parses a Blade template into an AST and provides the interface for querying, traversing, and transforming the result.
#Parsing Templates
The Document::parse method accepts a template string and returns a Document instance:
1<?php
2
3use Forte\Ast\Document\Document;
4
5$doc = Document::parse('<div>{{ $name }}</div>');
The Forte facade provides a convenient alternative. The parseFile method reads a file from disk and sets the file path automatically:
1<?php
2
3use Forte\Facades\Forte;
4
5$doc = Forte::parse($template);
6$doc = Forte::parseFile('/path/to/template.blade.php');
#Parser Options
The parse method accepts an optional ParserOptions instance for configuring the parser. The withComponentPrefix method registers additional component prefixes. The withAllDirectives method enables accept-all mode, where any @word pattern is treated as a directive:
1<?php
2
3use Forte\Parser\ParserOptions;
4use Forte\Ast\Document\Document;
5
6$options = ParserOptions::make()
7 ->withComponentPrefix('my-')
8 ->withAllDirectives();
9
10$doc = Document::parse($template, $options);
You may also sync directives from Laravel's Blade compiler (this is done automatically when using the Forte facade):
1<?php
2
3$options = ParserOptions::make()
4 ->syncLaravelDirectives();
#Rendering
The render method converts the document back to a string. For unmodified documents, it reproduces the original source exactly. After rewriting, it produces the transformed output:
1<?php
2
3echo $doc->render();
Document implements Stringable, so you can use it directly in string contexts:
1<?php
2
3echo $doc;
4echo (string) $doc;
#Accessing Source
The source method returns the original template text. This always returns the original, even after rewriting:
1<?php
2
3$doc->source(); // string (original template)
The getText method extracts a substring by absolute byte offsets. The getTextBetween method extracts text between two nodes:
1<?php
2
3$doc->getText(0, 10); // first 10 bytes of source
4$doc->getTextBetween($nodeA, $nodeB); // text between two nodes
#Line Access
The getLines method splits the source into an array of lines. The getLine method returns a single line by 1-based line number:
1<?php
2
3$doc->getLines(); // array<string>
4$doc->getLine(1); // string (first line)
5$doc->getLineCount(); // int
The getLineExcerpt method returns a small window of lines around a given line number, which is useful for error reporting:
1<?php
2
3$excerpt = $doc->getLineExcerpt(5, 2); // lines 3-7, keyed by 1-based line number
#File Path
The setFilePath and getFilePath methods associate a file path with the document. Forte::parseFile sets this automatically:
1<?php
2
3$doc->setFilePath('/path/to/template.blade.php');
4$doc->getFilePath(); // "/path/to/template.blade.php" or null
#Iterating Children
The children method returns an iterable of the document's root-level nodes. The getChildren method returns the same nodes as an array:
1<?php
2
3foreach ($doc->children() as $child) {
4 // each root-level node
5}
6
7$children = $doc->getChildren(); // array<Node>
Document implements IteratorAggregate and Countable, so you can iterate and count directly:
1<?php
2
3foreach ($doc as $node) {
4 // same as $doc->children()
5}
6
7count($doc); // number of root-level nodes
The firstChild and lastChild methods return the first and last root-level nodes:
1<?php
2
3$doc->firstChild(); // ?Node
4$doc->lastChild(); // ?Node
The getChildAt method returns a root child by its zero-based index:
1<?php
2
3$doc->getChildAt(0); // ?Node (same as firstChild)
4$doc->getChildAt(2); // ?Node (third root child)
The childCount method returns the number of root-level children:
1<?php
2
3$doc->childCount(); // int
#Predicate-Based Child Access
The firstChildWhere method returns the first root child matching a callback. The firstChildOfType method filters by class:
1<?php
2
3use Forte\Ast\Elements\ElementNode;
4
5$firstDiv = $doc->firstChildWhere(
6 fn($n) => $n instanceof ElementNode && $n->is('div')
7);
8
9$firstElement = $doc->firstChildOfType(ElementNode::class);
The childrenOfType method returns an iterable of all root children matching a class. The getChildrenOfType method returns them as an array:
1<?php
2
3$elements = $doc->getChildrenOfType(ElementNode::class); // array<ElementNode>
#Node Collections
The getRootNodes method returns the root children as a NodeCollection, which extends Laravel's Collection with node-specific filtering:
1<?php
2
3$roots = $doc->getRootNodes(); // NodeCollection
4
5$roots->elements(); // ElementNode only
6$roots->directives(); // DirectiveNode only
7$roots->onLine(5); // nodes spanning line 5
8$roots->containingOffset(42); // nodes containing byte offset 42
#Content Checks
The filled method returns true if the document contains non-whitespace content. The blank method returns the inverse:
1<?php
2
3$doc->filled(); // true if document has non-whitespace content
4$doc->blank(); // true if empty or whitespace-only
#Walking the Tree
The walk method performs a depth-first traversal of the entire document, calling a callback for every node. It returns the document for chaining:
1<?php
2
3use Forte\Ast\Node;
4use Forte\Ast\Elements\ElementNode;
5
6$doc->walk(function (Node $node) {
7 if ($node instanceof ElementNode) {
8 echo $node->tagNameText() . "\n";
9 }
10});
You may chain walk with other operations:
1<?php
2
3$output = $doc->walk(function (Node $node) {
4 // inspect every node
5})->render();
#Finding and Querying Nodes
The Document class provides the same finder methods, lazy collection properties, and eager collection variants available on any node. For the complete reference, see Traversal.
Quick reference:
$doc->find(fn($n) => ...)/$doc->findAll(fn($n) => ...): predicate search$doc->findElementByName('div')/$doc->findDirectiveByName('include'): find nodes by name$doc->findNodeAtOffset(42)/$doc->findNodeAtPosition(3, 10): by-position finders$doc->elements,$doc->components,$doc->directives, ...:LazyCollectionaccessors$doc->getElements(),$doc->getComponents(), ...: eager NodeCollection variants
#Node Depth
The getNodeDepth method returns the nesting depth of any node. Root-level nodes have a depth of 0:
1<?php
2
3$depth = $doc->getNodeDepth($someNode); // 0 for root, 1 for first-level, etc.
#Rewriting
The document provides three ways to apply transformations. All three return a new Document instance with the changes applied.
The rewrite method creates a RewriteBuilder for declarative rewriting:
1<?php
2
3$newDoc = $doc->rewrite(function ($builder) {
4 $builder->xpath('//div')->remove();
5});
The rewriteWith method accepts a callback that receives a NodePath for each node:
1<?php
2
3use Forte\Rewriting\NodePath;
4
5$newDoc = $doc->rewriteWith(function (NodePath $path) {
6 // transform nodes
7});
The apply method accepts one or more AstRewriter instances:
1<?php
2
3$newDoc = $doc->apply(
4 $rewriterA,
5 $rewriterB
6);
#Diagnostics
The diagnostics method returns a DiagnosticBag containing any lexer or parser errors found during parsing:
1<?php
2
3$bag = $doc->diagnostics();
4
5$bag->hasErrors(); // bool
6$bag->hasWarnings(); // bool
7$bag->errors(); // Collection<Diagnostic>
8$bag->warnings(); // Collection<Diagnostic>
The hasErrors method on the document is a shorthand for checking if any errors were recorded:
1<?php
2
3if ($doc->hasErrors()) {
4 echo $doc->diagnostics()->format($doc->source());
5}
#Node Metadata
The document supports attaching arbitrary metadata and tags to nodes. This is useful for passing information between rewriter passes or for annotating nodes during analysis.
The setNodeData and getNodeData methods store and retrieve key-value metadata by node index:
1<?php
2
3$doc->setNodeData($node->index(), 'processed', true);
4$doc->getNodeData($node->index(), 'processed'); // true
5$doc->hasNodeData($node->index(), 'processed'); // true
6$doc->removeNodeData($node->index(), 'processed');
The tagNode and nodeHasTag methods provide a lightweight way to mark nodes without associated values:
1<?php
2
3$doc->tagNode($node->index(), 'needs-review');
4$doc->nodeHasTag($node->index(), 'needs-review'); // true
5$doc->untagNode($node->index(), 'needs-review');
The findNodesByTag method returns all nodes with a given tag:
1<?php
2
3$flagged = $doc->findNodesByTag('needs-review'); // array<Node>
#Fluent Conditionals
The when method executes a callback when a condition is truthy. You may provide an optional else branch:
1<?php
2
3$doc->when($shouldProcess, function (Document $doc) {
4 // process the document
5});
6
7$doc->when($condition,
8 fn($doc) => $doc->walk(...), // then
9 fn($doc) => ..., // else
10);
The unless method is the inverse, executing when the condition is falsy:
1<?php
2
3$doc->unless($isProduction, function (Document $doc) {
4 // debug processing
5});
The tap method executes a callback and returns the document, which is useful for side effects like logging:
1<?php
2
3$doc->tap(function (Document $doc) {
4 logger()->info('Document has ' . count($doc) . ' root nodes');
5});
#XPath Querying
The xpath method provides XPath-based querying through DOM mapping:
1<?php
2
3$wrapper = $doc->xpath('//div[@class="container"]');
4
5$wrapper->first(); // ?Node
6$wrapper->all(); // array<Node>
7$wrapper->count(); // int
#See also
- Parser Options: Configure the parser before parsing
- Directives Registry: Control which directives are recognized
- Traversal: Full finder methods, lazy collections, and tree navigation
- Basic Nodes: Node types and their APIs