127 lines
5.5 KiB
Markdown
127 lines
5.5 KiB
Markdown
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.
|