Summary
On real-world PHP codebases, code-review-graph emits virtually no CALLS edges (e.g. 18 edges across 26,372 PHP functions in a ~6,900-file repo). As a result, all change-impact features that depend on the call graph — get_impact_radius_tool, detect_changes_tool, get_affected_flows_tool, get_bridge_nodes_tool, flow detection — return empty or near-empty results on PHP repos.
Root cause
Two independent gaps in parser.py:
- scoped_call_expression is missing from PHP's call-type list
parser.py:278:
"php": ["function_call_expression", "member_call_expression"],
PHP's Foo::bar() parses as scoped_call_expression. It's never iterated on, so static calls (extremely common in PHP — self::, static::, parent::, ClassName::) never produce edges.
-
_get_call_name has no PHP branch and the generic code doesn't match PHP's AST
parser.py:3659 — the generic fallbacks:
- first.type in ("identifier", "simple_identifier") → PHP uses name, not identifier.
- member_types tuple: ("attribute", "member_expression", "field_expression", "selector_expression", "navigation_expression") → PHP's member_call_expression doesn't wrap the receiver in any of these; the receiver is a direct variable_name child and the method name is a sibling name child.
So _get_call_name returns None for:
- function_call_expression (first child is name)
- member_call_expression (first child is variable_name, method lives in a top-level name child)
- scoped_call_expression (would return None too, but never gets there)
Reproduction
<?php
namespace App;
class Foo {
public function bar() {
$this->baz(); // member_call_expression
self::qux(); // scoped_call_expression
Foo::stat(); // scoped_call_expression
myFunc(); // function_call_expression
}
}
Actual tree-sitter-php AST (verified with tree-sitter-php 0.24+):
function_call_expression
├── name "myFunc" ← first child is `name`, NOT `identifier`
└── arguments
member_call_expression
├── variable_name ($this) ← first child; not in member_types
├── ->
├── name "baz" ← method name is a top-level `name` child
└── arguments
scoped_call_expression
├── relative_scope (self) | name "Foo"
├── ::
├── name "qux" ← method name
└── arguments
After running code-review-graph build on a PHP repo:
Nodes by kind: Class: 6671, File: 6871, Function: 26372
Edges by kind: CALLS: 18, CONTAINS: 33043, IMPORTS_FROM: 43324, REFERENCES: 1
The 18 CALLS edges come from JS files in the repo, not PHP.
Expected
CALLS edges should number in the tens or hundreds of thousands for a codebase this size. Flow detection and impact-radius queries should produce meaningful results.
Suggested fix sketch
Two changes in parser.py:
- Extend the PHP entry (~line 278):
"php": ["function_call_expression", "member_call_expression", "scoped_call_expression"],
- Add a PHP-specific branch early in _get_call_name (~line 3659). tree-sitter-php exposes named fields — function on function_call_expression, name on member_call_expression and scoped_call_expression — so node.child_by_field_name("name") / child_by_field_name("function") resolves the target cleanly without having to iterate children. Decode the name node's text; for scoped_call_expression you may also want to join the scope (node.child_by_field_name("scope")) and name to produce a qualified target, so _resolve_call_target can pick it up via the existing namespace_use_declaration import map.
- Worth verifying that _resolve_call_target correctly maps bare class names (from Foo::bar) through the PHP namespace_use_declaration import map into fully-qualified \App...\Foo::bar targets.
Impact if fixed
Unlocks the primary value prop of the tool (token-efficient, impact-aware code review) for PHP codebases, which are a large fraction of real-world Laravel / Symfony / WordPress users.
Environment
- code-review-graph (latest via uvx, Python 3.14)
- tree-sitter-php current PyPI release
- Target: ~6,900-file PHP monorepo (Laravel-style DDD)
- OS: macOS (Darwin 24.5)
Summary
On real-world PHP codebases, code-review-graph emits virtually no CALLS edges (e.g. 18 edges across 26,372 PHP functions in a ~6,900-file repo). As a result, all change-impact features that depend on the call graph — get_impact_radius_tool, detect_changes_tool, get_affected_flows_tool, get_bridge_nodes_tool, flow detection — return empty or near-empty results on PHP repos.
Root cause
Two independent gaps in parser.py:
parser.py:278:
PHP's Foo::bar() parses as scoped_call_expression. It's never iterated on, so static calls (extremely common in PHP — self::, static::, parent::, ClassName::) never produce edges.
_get_call_name has no PHP branch and the generic code doesn't match PHP's AST
parser.py:3659 — the generic fallbacks:
So _get_call_name returns None for:
Reproduction
Actual tree-sitter-php AST (verified with tree-sitter-php 0.24+):
After running code-review-graph build on a PHP repo:
The 18 CALLS edges come from JS files in the repo, not PHP.
Expected
CALLS edges should number in the tens or hundreds of thousands for a codebase this size. Flow detection and impact-radius queries should produce meaningful results.
Suggested fix sketch
Two changes in parser.py:
Impact if fixed
Unlocks the primary value prop of the tool (token-efficient, impact-aware code review) for PHP codebases, which are a large fraction of real-world Laravel / Symfony / WordPress users.
Environment