Add bounded ordered XFCP predecessor candidates and resolve literal parent calls through concrete PHP methods. Publish typed hierarchy edges, preserve priority ties as candidates, and version resolver projections independently from extraction caches. Validation: 471 Rust tests, release build, 58 Python plugin tests, and XenForo acceptance across 7,550 PHP files (99.1% complete, 57 CLI calls, zero failures, 10 XFCP occurrences, 8 parent calls, 4 Forum tie pairs, 25 User tie pairs). Co-authored-by: Codex <[email protected]>
9.7 KiB
PHP and XenForo navigation
PHP extraction
.php and .phtml map to php and use pinned tree-sitter-php 0.24.2.
Extraction records namespaces, classes, interfaces, traits, enums, functions,
methods, properties, constants, enum cases, parameters, variables, imports, and
identifier occurrences. Definition spans cover declaration nodes; occurrence
spans use original source bytes, including $ for PHP variables. Parse statuses
are complete, parse_error, invalid_encoding, incompatible_grammar,
cancelled, and depth_limit; parse errors retain safe observations but do not
establish confident links.
Definition names stay short. Class-like declarations use their namespace as the container, functions use their namespace, and members use the containing class's fully qualified name. Namespace-relative and imported type/import path occurrences use normalized fully qualified names without a leading slash; explicit absolute names keep their leading slash. Normalized names can differ from the text at the occurrence span.
Only reads of a unique, unambiguous parameter declared in the same function can
resolve. Assignment-bound variables remain candidates, even with one apparent
assignment, because the extractor does not establish branch visibility,
assignment order, or dynamic-variable behavior. Calls, imports, and type
references remain candidates or unresolved, even when one target is found,
including an absolute fully qualified type or import name matching exactly one
definition. The PHP and XenForo resolver can add bounded, source-derived
inheritance and literal parent::method() candidates as described below. These
candidates do not make extraction compiler-equivalent or prove runtime dispatch.
Trait-use references remain candidate or unresolved evidence; traits are not
expanded. A conditional XFCP helper declaration does not bind a generated
class. Cross-file lookup uses exact fully qualified names and does not guess
from a short class name.
XenForo add-on metadata
The metadata resolver reads addon.json and _data/class_extensions.xml from
indexed source content. It does not read live activation state from the XenForo
database or discover files outside the index. Add-on requirement occurrences
span the quoted key in addon.json's require object, not its version/value;
XML metadata occurrences span their attribute values. A class-extension
relationship cites the full <extension> element as its evidence span.
Known local add-on IDs in addon.json's require object produce
xenforo_addon_requirement_candidate edges from the requiring add-on to the
required local add-on. Unknown requirements, including PHP or XenForo framework
requirements without a local add-on target, remain observations without an
add-on edge.
Each class-extension declaration has a xenforo_class_extension definition
and xenforo_class_extension_candidate links to exact indexed base-class
candidates. Its to_class occurrence separately lists matching PHP class
definitions. Provenance records active, execute_order, XML source, and
runtime_xfcp_chain_unresolved. An inactive declaration remains metadata but
has no active chain edge. The XML owner's add-on can differ from the add-on
defining to_class; the resolver keeps those owners distinct. A
xenforo_addon_extension_dependency_candidate, when emitted, links the XML
owner to exact indexed base owners and requires unique implementation and base
owner evidence. It does not infer the PHP implementation's owner from the XML
or class-name prefixes. Malformed extension XML invalidates the whole file;
when its owner is known, a file-scoped inheritance_issue cites its XML range,
and valid declarations in other files remain usable.
These edges are navigation evidence, not proof that an add-on is installed or
enabled, that XenForo will apply the declaration, or that an extension chain
will execute in the recorded order. The source resolver treats registrations
that pass the indexed metadata checks as applicable while constructing
candidates; it cannot read runtime enabled-add-on state or database collation.
runtime_xfcp_chain_unresolved continues to mark the gap between source
candidates and the runtime chain.
Declared inheritance and literal parent calls
The inheritance resolver uses indexed PHP source and indexed XenForo registration source only. It does not query the XenForo database, load classes, run add-on code, or establish which add-ons are enabled. A candidate chain assumes the indexed registrations that pass their source checks apply; this is navigation evidence, not a claim about the live XFCP chain.
Ordinary PHP extends names use namespace/import resolution followed by exact
indexed class lookup. XF metadata alias mapping is separate: supported literal
alias hooks in indexed XF.php canonicalize only metadata base names; an absent
hook means identity, while a present unsupported hook blocks mapping. The
hooks are not run and aliases are not inferred. PHP import aliases are resolved
before matching an extends XFCP_* parent; metadata alias rules never rewrite
PHP imports. The raw XML to_class spelling defines extender identity and its
generated proxy spelling.
Framework extension declarations produce framework_parent_candidate edges
from an implementation class to its immediate predecessor candidate. Ordinary
PHP declarations produce php_extends_candidate edges. Duplicate actual PHP
class identities block framework/proxy targets and emit inheritance_issue;
metadata lookup can retain the matching class candidates. Reusing one actual
implementation under different raw proxy names blocks composition for that
base and creates no self-edges. Missing classes are omitted, never spliced into
a shortcut to a later ancestor.
Only XML active="0" and active="1" are recognized. Zero remains metadata
without an active edge; one is eligible source evidence; absent or other values
remain unknown and block composition. Negative execute_order is invalid and
blocks composition for that base. Strictly different valid numeric orders
establish order;
equal orders remain candidate alternatives marked by priority_tie, without a
database-collation or lexical tie-break. Missing, nonnumeric, or negative order
produces an inheritance_issue. If base alias mapping fails, independent raw
implementation evidence remains available but does not compose into a chain.
A literal parent::method() call can produce a php_parent_call_candidate to
the nearest ancestor's own concrete public or protected declaration of that
method. The search follows candidate framework and ordinary PHP parent edges.
It does not bind $this->method(), static::method(), dynamic calls, or other
general member references. It does not flatten trait methods or search
interfaces. A private or abstract method declaration, trait boundary, incomplete
or conditional class/method declaration, parse failure, missing parent source,
or ambiguous parent path is an inheritance_issue barrier; traversal does not
guess past it. Duplicate or reused proxy names and unknown extension active
state also produce issue evidence when anchored. Conditional or generated
runtime class definitions do not become source declarations.
The three parent-link kinds remain candidate evidence; naming a target class
does not establish the exact runtime chain. inheritance_issue reports
incomplete evidence, not an alternative parent. Expansion is bounded to 64
classes per chain including the base, 64 parent-call candidates, and 64 visited
classes per traversal including the caller. A 200,000-item derived-stage budget
covers bounded preparation and publication and is charged before retaining
edges and candidate IDs. ctx includes at most 100 graph edges within its
normal token budget, so a context response can end before the full candidate
graph. Reaching a bound or a source barrier is reported as incomplete evidence,
not as proof that no parent exists. A global derived-stage failure aborts the
staged publication, leaving the last complete publication available with no
partial expansion. Internal php_markers remain private and do not appear as
definitions, occurrences, relationships, or provenance. Public relationship
kinds and provenance, including runtime_xfcp_chain_unresolved, remain visible.
Use sym: with a short class name and file: to find a definition, then inspect
ctx or refs candidate details and read the source registrations.
Real checkout acceptance
The XenForo acceptance script reads the checkout in place and leaves its source
read-only. It exercises search, show, map, refs, and ctx across XenForo
core and add-on files, and reports aggregate PHP parse statuses. Index coverage
follows the normal scanner ignore policy and source bounds: ignored files are
not indexed or counted, so the report describes published PHP files rather than
every PHP file on disk. The gate requires at least 5,000 indexed PHP files, at
least 95% complete status, and complete status for its core and add-on fixture
files. Its JSON report includes the status counts and failed checks. Build the
binary separately. With --new-build, the runner scopes the system directory
and spool to the cache root, starts private root and system services with that
binary, waits for index publication, then routes CLI requests through its
private router. It stops and reaps only those two owned processes when the run
ends.
Run it with a dedicated cache under the disk-backed scratch root:
python3 evaluation/php_xenforo.py \
--binary target/release/trufflepig \
--xf-root /home/josh/Source/xf_kiwifarms \
--cache-root /home/josh/.cache/codex-tmp/xf-php-final \
--new-build
The script keeps its index and temporary files under the supplied cache root. The caller removes that dedicated cache directory after the run when its results are no longer needed.