AST
Directives
Blade directives are the @name(...) constructs found throughout Blade templates. Forte distinguishes standalone directives from paired block directives that wrap content.
#Standalone Directives
A single directive occurrence such as @include ('header') or @csrf is parsed as a DirectiveNode.
You can inspect the directive name with nameText (normalized) or name (as it appeared in source):
1<?php
2
3use Forte\Ast\DirectiveNode;
4use Forte\Facades\Forte;
5
6$doc = Forte::parse("@include('partials.header')");
7
8$directive = $doc->find(
9 fn($n) => $n instanceof DirectiveNode
10);
11
12$directive->nameText(); // "include"
13$directive->name(); // "include"
The is method checks whether the directive name matches a given pattern. It supports wildcard matching:
1<?php
2
3$directive->is('include'); // true
4$directive->is('inc*'); // true
The arguments method returns the argument string including parentheses, or null if the directive has no arguments. The hasArguments method checks for their presence:
1<?php
2
3$directive->arguments(); // "('partials.header')"
4$directive->hasArguments(); // true
#Structure Roles
Structure roles describe how a directive participates in a block, whether it opens, closes, or branches within one. This is useful when building linters or rewriters that need to understand directive pairing. The role method returns a StructureRole enum value:
1<?php
2
3use Forte\Parser\Directives\StructureRole;
4
5$directive->role(); // StructureRole::Opening, Closing, Intermediate, Mixed, or None
The role values correspond to the directive's function:
Openingindicates the start of a block (e.g.@if,@foreach)Closingindicates the end of a block (e.g.@endif,@endforeach)Intermediateindicates a branch within a block (e.g.@else)Mixedindicates a directive that can both close and reopen a block (e.g.@elseif)Noneindicates a standalone directive that does not participate in a block (e.g.@csrf,@include)
Convenience methods are available for checking each role:
1<?php
2
3$directive->isOpening(); // bool
4$directive->isClosing(); // bool
5$directive->isIntermediate(); // bool
6$directive->isStandalone(); // bool (true when role is None)
#Block Directives
Paired directive structures like @if...@endif or @foreach...@endforeach are captured as DirectiveBlockNode. A block wraps its content and may contain intermediate directives such as @else.
The nameText and is methods work the same as on DirectiveNode:
1<?php
2
3use Forte\Ast\DirectiveBlockNode;
4
5$doc = Forte::parse('@if($show) Visible @else Hidden @endif');
6
7$block = $doc->find(
8 fn($n) => $n instanceof DirectiveBlockNode
9);
10
11$block->nameText(); // "if"
12$block->is('if'); // true
The arguments method returns the opening directive's argument string:
1<?php
2
3$block->arguments(); // "($show)"
#Structural Access
You can access the individual directives that make up a block. The startDirective method returns the opening directive, and endDirective returns the closing one:
1<?php
2
3$block->startDirective(); // ?DirectiveNode (@if)
4$block->endDirective(); // ?DirectiveNode (@endif)
To inspect branches within a block, use intermediateDirectives to iterate them or hasIntermediates to check for their presence:
1<?php
2
3$block->hasIntermediates(); // true
4
5foreach ($block->intermediateDirectives() as $intermediate) {
6 $intermediate->nameText(); // "else"
7}
#Type Checks
When filtering or routing logic based on block type, you can use the built-in type-check methods instead of string comparisons:
1<?php
2
3$block->isIf(); // bool
4$block->isForeach(); // bool
5$block->isForelse(); // bool
6$block->isFor(); // bool
7$block->isWhile(); // bool
8$block->isUnless(); // bool
9$block->isSwitch(); // bool
10$block->isSection(); // bool
11$block->isPush(); // bool
12$block->isOnce(); // bool
13$block->isVerbatim(); // bool
For custom directives not covered by the built-in checks, isDirectiveNamed performs a case-insensitive name comparison:
1<?php
2
3$block->isDirectiveNamed('can'); // bool
4$block->isDirectiveNamed('error'); // bool
#Finding Directives
The document provides dedicated methods for locating directives by name. The singular variants return the first match, while the plural variants return a LazyCollection:
1<?php
2
3// Standalone directives
4$doc->findDirectiveByName('include'); // ?DirectiveNode
5$doc->findDirectivesByName('include'); // LazyCollection<DirectiveNode>
6
7// Block directives
8$doc->findBlockDirectiveByName('if'); // ?DirectiveBlockNode
9$doc->findBlockDirectivesByName('if'); // LazyCollection<DirectiveBlockNode>
The document also exposes lazy collection properties that traverse the full tree:
1<?php
2
3$doc->directives; // LazyCollection of all DirectiveNode instances
4$doc->blockDirectives; // LazyCollection of all DirectiveBlockNode instances
Since these are Laravel lazy collections, you may filter, map, and count them:
1<?php
2
3// Find all @include directives
4$includes = $doc->directives->filter(fn($d) => $d->is('include'));
5
6// Find all @foreach blocks
7$foreachBlocks = $doc->blockDirectives->filter(fn($b) => $b->isForeach());
8
9// Count block directives
10$blockCount = $doc->blockDirectives->count();
#Working with Block Children
Block directives contain their children between the opening and closing directives. You can iterate over them with children or getChildren:
1<?php
2
3use Forte\Ast\EchoNode;
4
5$block = $doc->findBlockDirectiveByName('foreach');
6
7foreach ($block->children() as $child) {
8 // Each child node inside the @foreach...@endforeach
9}
10
11$children = $block->getChildren(); // array<Node>
You may combine block lookup with descendant traversal to find specific nodes inside a block. The following example collects all echo nodes inside @foreach blocks:
1<?php
2
3use Forte\Ast\EchoNode;
4
5$foreachEchoes = $doc->blockDirectives
6 ->filter(fn($b) => $b->isForeach())
7 ->flatMap(fn($b) => collect($b->getDescendants()))
8 ->filter(fn($n) => $n instanceof EchoNode);
You may also check whether a block contains specific node types:
1<?php
2
3use Forte\Ast\Components\ComponentNode;
4
5$block = $doc->findBlockDirectiveByName('if');
6
7$hasComponents = false;
8foreach ($block->descendants() as $desc) {
9 if ($desc instanceof ComponentNode) {
10 $hasComponents = true;
11 break;
12 }
13}
#Loop Variable Extraction
The LoopVariablesExtractor utility parses @foreach argument expressions to extract the loop variable and its alias. This is useful for building linters that check variable usage or refactoring tools that rename loop variables:
1<?php
2
3use Forte\Support\LoopVariablesExtractor;
4
5$extractor = new LoopVariablesExtractor;
6$vars = $extractor->extractDetails('$users as $user');
7
8$vars->source; // "$users as $user"
9$vars->variable; // "$users"
10$vars->alias; // "$user"
11$vars->isValid; // true
When the expression cannot be parsed, isValid returns false:
1<?php
2
3$vars = $extractor->extractDetails('invalid expression');
4
5$vars->isValid; // false
#See also
- Basic Nodes: Common node API shared by all node types
- Elements: HTML element nodes and attributes
- Traversal: Walk and query the document tree
- Rewriters: Transform directives using the visitor pattern