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