Summary
When building a graph on a large PHP codebase (~4,400 PHP files, 33K functions, 3K classes), the parser produces only 58 CALLS edges from PHP files. Method calls ($this->method(), $obj->method()), static calls (Class::method()), and plain function calls (sqlQuery(...), xl(...)) are not being captured.
CONTAINS and IMPORTS_FROM edges work well (36K and 12K respectively), so the file/class/function structure is there — but without call edges, blast-radius analysis is effectively disabled for PHP.
Reproduction
git clone https://github.com/openemr/openemr.git
cd openemr
uvx code-review-graph build
sqlite3 .code-review-graph/graph.db "SELECT kind, COUNT(*) FROM edges WHERE file_path LIKE '%.php' GROUP BY kind ORDER BY COUNT(*) DESC;"
Output:
CONTAINS|36770
IMPORTS_FROM|12401
CALLS|58
The 58 CALLS edges that do exist are all global function calls with a \ prefix — \dirname(), \is_array(), \is_null(), etc. Calls without the leading backslash, method calls, and static calls are all missing.
Expected
PHP call extraction should capture at minimum:
- Plain function calls:
sqlQuery(...), xl(...), text(...)
- Method calls:
$this->execute(...), $service->search(...)
- Static method calls:
QueryUtils::fetchRecords(...), EncounterService::create(...)
- Scoped calls:
parent::__construct(...), self::factory(...)
Tree-sitter's PHP grammar exposes these as function_call_expression, member_call_expression, scoped_call_expression, and nullsafe_member_call_expression node types, so the AST data is available.
Environment
- code-review-graph installed via
uvx (latest from PyPI as of 2026-04-15)
- Target repo: openemr/openemr at commit
a7a3f0a (4,770 files, 64,600 nodes)
- macOS, Python 3.12
Context
OpenEMR is a large open-source healthcare EHR. The PHP call extraction gap is the main blocker — the structural (CONTAINS) and import (IMPORTS_FROM) edges are already useful, but blast-radius analysis needs CALLS edges to be practical.
Related issues
Summary
When building a graph on a large PHP codebase (~4,400 PHP files, 33K functions, 3K classes), the parser produces only 58 CALLS edges from PHP files. Method calls (
$this->method(),$obj->method()), static calls (Class::method()), and plain function calls (sqlQuery(...),xl(...)) are not being captured.CONTAINS and IMPORTS_FROM edges work well (36K and 12K respectively), so the file/class/function structure is there — but without call edges, blast-radius analysis is effectively disabled for PHP.
Reproduction
Output:
The 58 CALLS edges that do exist are all global function calls with a
\prefix —\dirname(),\is_array(),\is_null(), etc. Calls without the leading backslash, method calls, and static calls are all missing.Expected
PHP call extraction should capture at minimum:
sqlQuery(...),xl(...),text(...)$this->execute(...),$service->search(...)QueryUtils::fetchRecords(...),EncounterService::create(...)parent::__construct(...),self::factory(...)Tree-sitter's PHP grammar exposes these as
function_call_expression,member_call_expression,scoped_call_expression, andnullsafe_member_call_expressionnode types, so the AST data is available.Environment
uvx(latest from PyPI as of 2026-04-15)a7a3f0a(4,770 files, 64,600 nodes)Context
OpenEMR is a large open-source healthcare EHR. The PHP call extraction gap is the main blocker — the structural (CONTAINS) and import (IMPORTS_FROM) edges are already useful, but blast-radius analysis needs CALLS edges to be practical.
Related issues