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):

<?php
use Forte\Ast\DirectiveNode;
use Forte\Facades\Forte;
$doc = Forte::parse("@include('partials.header')");
$directive = $doc->find(
fn($n) => $n instanceof DirectiveNode
);
$directive->nameText(); // "include"
$directive->name(); // "include"

The is method checks whether the directive name matches a given pattern. It supports wildcard matching:

<?php
$directive->is('include'); // true
$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:

<?php
$directive->arguments(); // "('partials.header')"
$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:

<?php
use Forte\Parser\Directives\StructureRole;
$directive->role(); // StructureRole::Opening, Closing, Intermediate, Mixed, or None

The role values correspond to the directive's function:

  • Opening indicates the start of a block (e.g. @if, @foreach)
  • Closing indicates the end of a block (e.g. @endif, @endforeach)
  • Intermediate indicates a branch within a block (e.g. @else)
  • Mixed indicates a directive that can both close and reopen a block (e.g. @elseif)
  • None indicates a standalone directive that does not participate in a block (e.g. @csrf, @include)

Convenience methods are available for checking each role:

<?php
$directive->isOpening(); // bool
$directive->isClosing(); // bool
$directive->isIntermediate(); // bool
$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:

<?php
use Forte\Ast\DirectiveBlockNode;
$doc = Forte::parse('@if($show) Visible @else Hidden @endif');
$block = $doc->find(
fn($n) => $n instanceof DirectiveBlockNode
);
$block->nameText(); // "if"
$block->is('if'); // true

The arguments method returns the opening directive's argument string:

<?php
$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:

<?php
$block->startDirective(); // ?DirectiveNode (@if)
$block->endDirective(); // ?DirectiveNode (@endif)

To inspect branches within a block, use intermediateDirectives to iterate them or hasIntermediates to check for their presence:

<?php
$block->hasIntermediates(); // true
foreach ($block->intermediateDirectives() as $intermediate) {
$intermediate->nameText(); // "else"
}

#Type Checks

When filtering or routing logic based on block type, you can use the built-in type-check methods instead of string comparisons:

<?php
$block->isIf(); // bool
$block->isForeach(); // bool
$block->isForelse(); // bool
$block->isFor(); // bool
$block->isWhile(); // bool
$block->isUnless(); // bool
$block->isSwitch(); // bool
$block->isSection(); // bool
$block->isPush(); // bool
$block->isOnce(); // bool
$block->isVerbatim(); // bool

For custom directives not covered by the built-in checks, isDirectiveNamed performs a case-insensitive name comparison:

<?php
$block->isDirectiveNamed('can'); // bool
$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:

<?php
// Standalone directives
$doc->findDirectiveByName('include'); // ?DirectiveNode
$doc->findDirectivesByName('include'); // LazyCollection<DirectiveNode>
// Block directives
$doc->findBlockDirectiveByName('if'); // ?DirectiveBlockNode
$doc->findBlockDirectivesByName('if'); // LazyCollection<DirectiveBlockNode>

The document also exposes lazy collection properties that traverse the full tree:

<?php
$doc->directives; // LazyCollection of all DirectiveNode instances
$doc->blockDirectives; // LazyCollection of all DirectiveBlockNode instances

Since these are Laravel lazy collections, you may filter, map, and count them:

<?php
// Find all @include directives
$includes = $doc->directives->filter(fn($d) => $d->is('include'));
// Find all @foreach blocks
$foreachBlocks = $doc->blockDirectives->filter(fn($b) => $b->isForeach());
// Count block directives
$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:

<?php
use Forte\Ast\EchoNode;
$block = $doc->findBlockDirectiveByName('foreach');
foreach ($block->children() as $child) {
// Each child node inside the @foreach...@endforeach
}
$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:

<?php
use Forte\Ast\EchoNode;
$foreachEchoes = $doc->blockDirectives
->filter(fn($b) => $b->isForeach())
->flatMap(fn($b) => collect($b->getDescendants()))
->filter(fn($n) => $n instanceof EchoNode);

You may also check whether a block contains specific node types:

<?php
use Forte\Ast\Components\ComponentNode;
$block = $doc->findBlockDirectiveByName('if');
$hasComponents = false;
foreach ($block->descendants() as $desc) {
if ($desc instanceof ComponentNode) {
$hasComponents = true;
break;
}
}

#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:

<?php
use Forte\Support\LoopVariablesExtractor;
$extractor = new LoopVariablesExtractor;
$vars = $extractor->extractDetails('$users as $user');
$vars->source; // "$users as $user"
$vars->variable; // "$users"
$vars->alias; // "$user"
$vars->isValid; // true

When the expression cannot be parsed, isValid returns false:

<?php
$vars = $extractor->extractDetails('invalid expression');
$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