Files
codespaces/.agents/designs/php-support.md
2026-06-22 08:12:55 +03:00

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 from use statements
  • calls-api -- implements/extends + trait usage + constructor DI
  • data-flow -- to modules with validation (Symfony Validator, Laravel)
  • refs -- entity-level cross-file references
  • calls (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

  • .php extension (excluding .blade.php)
  • vendor/ and var/ directories are skipped (composer deps, Symfony cache)
  • bootstrap/ and storage/ are NOT skipped (may contain real source)
  • composer.json defines PSR-4 autoload rules and LSP project boundaries

Fallback behavior

When tree-sitter-php is not installed:

  • parse_php_regex handles 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-php is added

When phpactor is not installed:

  • --lsp mode 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

  1. Given a Laravel/Symfony project with composer.json, when running build_belief_map.py, then (node ... php ...) facts are emitted for each .php file with correct PSR-4-resolved imports edges.

  2. Given a class implementing an interface, when parsing, then a calls-api edge connects them.

  3. Given a class using a trait (use TraitX;), when parsing, then a calls-api edge connects the class module to the trait module.

  4. Given a class with constructor type-hints, when parsing, then calls-api edges connect the class to each dependency module.

  5. Given PHP 8 attributes (#[AsService]), when parsing with tree-sitter-php, then the attribute name appears in :deco.

  6. Given composer.json with autoload.psr-4, when resolving imports, then use App\Service\Foo resolves to src/Service/Foo.php.

  7. Given tree-sitter-php is not installed, when parsing, then parse_php_regex produces visible entities (reduced precision).

  8. belief_search.py analyze <php-class-id> returns boundary/deps/rdeps without modifications to the query engine.