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 ` returns boundary/deps/rdeps without modifications to the query engine.