Querying

Document Queries

Forte provides typed query methods for common AST nodes. These methods return Laravel LazyCollection instances, accept useful name filters, and avoid repeating manual tree predicates throughout an analyzer.

#Quick Start

Element queries can select one tag, several tag names, the first match, or an element with an exact static ID:

<?php
use Forte\Facades\Forte;
$doc = Forte::parse(
'<main><form id="signup"></form><form id="search"></form></main>'
);
$doc->queryElements('form')->count(); // 2
$doc->firstElement('main')->tagNameText(); // "main"
$doc->hasElement(['video', 'form']); // true
$doc->elementById('search')->tagNameText(); // "form"

queryElements matches exact tag names case-insensitively. It returns ordinary HTML elements and excludes ComponentNode instances, which have their own query method.

#Indexed Element Lookups

Exact string queries use document-level indexes for repeated lookups. Use elementsGroupedByName when you need independently addressable results for several tag names:

<?php
use Forte\Facades\Forte;
$doc = Forte::parse('<img><script></script><img>');
$groups = $doc->elementsGroupedByName(['img', 'script', 'video']);
count($groups['img']); // 2
count($groups['script']); // 1
count($groups['video']); // 0

Every requested key is present in the returned array, even when the document has no matching element.

elementById considers only a fully static id value. Its lookup is case-sensitive and keeps browser ID and IDREF semantics, so a bound or interpolated ID does not become a guessed match.

#Components

Component queries accept exact names and wildcard patterns. Include the full component prefix when matching a specific component:

<?php
use Forte\Facades\Forte;
$doc = Forte::parse('<x-alert /><livewire:search /><div></div>');
$doc->queryComponents()->count(); // 2
$doc->queryComponents(['x-alert', 'livewire:*'])->count(); // 2
$doc->findComponentByName('x-alert')->getComponentName(); // "alert"

queryComponents returns ComponentNode instances. The older findComponentByName and findComponentsByName helpers remain available and use the same full-name matching contract.

#Blade Directives

Standalone and paired directives use separate queries because their AST node types and structure differ:

<?php
use Forte\Facades\Forte;
$doc = Forte::parse('@csrf @if ($ready)<p>Ready</p>@endif');
$doc->queryDirectives('csrf')->count(); // 1
$doc->queryBlockDirectives('if')->count(); // 1
$doc->findDirectiveByName('csrf')->nameText(); // "csrf"
$doc->findBlockDirectiveByName('if')->nameText(); // "if"

queryDirectives excludes directive nodes that belong to a DirectiveBlockNode. Use queryBlockDirectives for paired structures such as @if, @foreach, and @section.

Both methods accept one name, an array of names, or null for every matching node type.

#Other Typed Collections

The remaining typed helpers cover comments, echoes, PHP, and text. Use lazy methods while filtering or mapping, and the corresponding get... method when you need a materialized NodeCollection.

Lazy method Materialized method Nodes
queryComments() getComments() HTML and Blade comments
queryHtmlComments() getHtmlComments() HTML comments
queryBladeComments() getBladeComments() Blade comments
echoes() getEchoes() All Blade echoes
escapedEchoes() getEscapedEchoes() Escaped {{ ... }} echoes
rawEchoes() getRawEchoes() Raw {!! ... !!} echoes
tripleEchoes() getTripleEchoes() Legacy triple echoes
queryPhpBlocks() getPhpBlocks() @php ... @endphp blocks
queryPhpTags() getPhpTags() PHP tags
queryTextNodes() getText() Text nodes

Magic properties such as $doc->elements, $doc->components, and $doc->directives remain available. Prefer the named query methods when you need filters because they make the selection contract explicit.

#Relationship Queries

Nodes expose typed ancestor and descendant helpers for queries scoped to one subtree. They save you from rebuilding parent loops or filtering an entire document:

<?php
use Forte\Facades\Forte;
$doc = Forte::parse(
'<main><section><a href="/docs">Documentation</a></section></main>'
);
$link = $doc->firstElement('a');
$main = $doc->firstElement('main');
$link->closestElement('section')->tagNameText(); // "section"
$link->hasAncestorElement('main'); // true
$main->descendantElements(['section', 'a'])->count(); // 2
$main->firstDescendantElement('a')->tagNameText(); // "a"

Use closestDirectiveBlock and hasAncestorDirective for paired Blade directives. Predicate-based variants include closest, hasAncestorWhere, firstDescendantWhere, and hasDescendantWhere.

Pass TraversalOptions::deep() when a relationship query must include internal tag-name, attribute, or slot nodes. Normal traversal includes authored child content without those internal implementation nodes.

#Choosing a Query API

Use typed document queries for common node families and exact names. Use Traversal when you need arbitrary predicates, depth control, or internal nodes.

XPath Queries are useful for structural relationships that are concise as one expression. The Selection API is intended for declarative mutation rather than read-only inspection.

#See Also

Continue with these related Forte guides:

  • Traversal: Walk and filter the complete AST
  • Elements: Inspect element and attribute semantics
  • Directives: Work with standalone and paired Blade directives
  • XPath Queries: Query structural relationships with XPath