5.5 KiB
PHP Language Support for Belief Map
Context and motivation
The belief map previously supported Python and TypeScript/TSX. PHP projects
(Laravel, Symfony, legacy codebases) had no graph representation, meaning
agents querying the belief map would get zero results for .php files --
even in monorepos containing both TS and PHP services.
This design adds PHP as a first-class language alongside Python and TS, covering the same entity/edge model: classes, interfaces, traits, enums, imports, heritage, trait usage, and constructor dependency injection.
Implementation approach
tree-sitter-php is the primary parser, mirroring the TypeScript approach.
A regex fallback (parse_php_regex) ensures PHP files remain visible even
when tree-sitter-php is not installed, at the cost of reduced precision.
PSR-4 autoload resolution reads composer.json (autoload.psr-4 and
autoload-dev.psr-4) to map FQCNs in use statements to file paths. This
mirrors the TypeScript tsconfig.json path-alias resolver. When no
composer.json is found, a walk-up heuristic converts namespace separators
to path separators (like the Python resolver).
Constructor DI extraction mirrors the NestJS heuristic from the TS
parser: typed __construct parameters with PascalCase type-hints are
treated as service dependencies, producing calls-api edges. This works
for Symfony autowiring and Laravel service container injection.
Trait usage (use TraitX; inside a class body) is a PHP-specific
relationship with no direct equivalent in Python or TS. Traits are
registered as abstract providers (like interfaces), and classes that use
them get calls-api edges to the trait's defining module.
LSP enrichment uses phpactor language-server (JSON-RPC over stdio),
the open-source PHP language server. It integrates with the existing
LspClient infrastructure without changes to the transport layer.
Supported entities
| PHP construct | Entity kind | sexp tag | Notes |
|---|---|---|---|
class Foo |
class | cls |
abstract modifier, heritage |
interface Foo |
interface | ifc |
auto-abstract |
trait Foo |
trait | trt |
auto-abstract; usable via use |
enum Foo (PHP 8.1) |
enum | enm |
backed enums (: string/int) |
function foo() |
function | fn |
top-level functions only |
#[Attribute] |
decorator | :deco |
PHP 8 attributes on classes/fns |
class { use TraitX; } |
trait usage | edge | calls-api to trait module |
__construct(Foo $f) |
DI dependency | edge | calls-api to service module |
Edge types for PHP
Same model as other languages:
imports-- resolved via PSR-4 fromusestatementscalls-api-- implements/extends + trait usage + constructor DIdata-flow-- to modules with validation (Symfony Validator, Laravel)refs-- entity-level cross-file referencescalls(LSP only) -- precise function-to-function call edges
Skipped / out of scope (v1)
- Blade templates (
.blade.php) -- skipped like.d.ts - Anonymous classes (
new class { ... }) -- no name, no ID - Trait conflict resolution (
insteadof/as) -- edges on all used traits - Dynamic imports (
include,require) -- not tracked - Runtime service discovery -- no container introspection
use function/use const-- tracked as imports but not as entity refs
File discovery
.phpextension (excluding.blade.php)vendor/andvar/directories are skipped (composer deps, Symfony cache)bootstrap/andstorage/are NOT skipped (may contain real source)composer.jsondefines PSR-4 autoload rules and LSP project boundaries
Fallback behavior
When tree-sitter-php is not installed:
parse_php_regexhandles classes, interfaces, traits, enums (with heritage)- No PHP 8 attribute extraction
- No constructor DI extraction
- Method extraction is best-effort (regex within a window)
- Trait usage inside class bodies IS detected (regex)
- The graph is still usable; precision improves when
tree-sitter-phpis added
When phpactor is not installed:
--lspmode skips PHP projects with a warning- AST-based edges (imports, calls-api, refs) are unaffected
- Only
calls(LSP call-hierarchy) edges are missing
Acceptance criteria
-
Given a Laravel/Symfony project with
composer.json, when runningbuild_belief_map.py, then(node ... php ...)facts are emitted for each.phpfile with correct PSR-4-resolvedimportsedges. -
Given a class implementing an interface, when parsing, then a
calls-apiedge connects them. -
Given a class using a trait (
use TraitX;), when parsing, then acalls-apiedge connects the class module to the trait module. -
Given a class with constructor type-hints, when parsing, then
calls-apiedges connect the class to each dependency module. -
Given PHP 8 attributes (
#[AsService]), when parsing with tree-sitter-php, then the attribute name appears in:deco. -
Given
composer.jsonwithautoload.psr-4, when resolving imports, thenuse App\Service\Fooresolves tosrc/Service/Foo.php. -
Given
tree-sitter-phpis not installed, when parsing, thenparse_php_regexproduces visible entities (reduced precision). -
belief_search.py analyze <php-class-id>returns boundary/deps/rdeps without modifications to the query engine.