Skip to content

PHP use imports stored as raw statement text; importers_of and impact-radius return 0 for PHP classes #574

Description

@stefanws0

code-review-graph version

2.3.6 (run as the MCP server). Also reproduced on the 2.3.2 CLI; the root cause
is present in both.

Operating system

macOS 15 (Darwin 24.5.0), Apple Silicon.

Python version

Python 3.14.4

AI platform

Claude Code (MCP server over stdio).

Output of code-review-graph status

From the minimal reproducer below:

Nodes: 6
Edges: 5
Files: 3
Languages: php

Steps to reproduce

  1. Create a minimal PSR-4 PHP project and build the graph:
mkdir -p repro/src/App/Domain/Entity repro/src/App/Service repro/src/App/Api
cd repro && git init -q
printf '<?php\nnamespace App\\Domain\\Entity;\nclass Job {}\n'                                   > src/App/Domain/Entity/Job.php
printf '<?php\nnamespace App\\Service;\nuse App\\Domain\\Entity\\Job;\nclass MatchService {}\n'  > src/App/Service/MatchService.php
printf '<?php\nnamespace App\\Api;\nuse App\\Domain\\Entity\\Job;\nclass JobController {}\n'      > src/App/Api/JobController.php
git add -A
code-review-graph build
  1. Ask for the importers of the Job class (via the MCP query_graph tool, or
    the package API):
from code_review_graph.tools.query import query_graph
query_graph("importers_of", "<abs>/src/App/Domain/Entity/Job.php", repo_root="<abs>")

Expected vs actual behavior

Expected: importers_of(Job.php) returns the two files that
use App\Domain\Entity\Job; (MatchService.php and JobController.php).
Equivalently, the IMPORTS_FROM edges should target the resolved path of
Job.php.

Actual: importers_of returns 0 results. Inspecting the graph shows the
IMPORTS_FROM edge targets are the raw use statement text instead of a
resolved file path or FQN:

src/App/Api/JobController.php      ->  use App\Domain\Entity\Job;
src/App/Service/MatchService.php   ->  use App\Domain\Entity\Job;

importers_of(Job.php)  ->  Found 0 result(s)

The same defect makes tests_for and inheritors_of return nothing for PHP
classes, hides PHP importers from the upstream side of get_impact_radius, and
(because resolve_bare_call_targets uses IMPORTS_FROM targets to disambiguate
cross-file CALLS) degrades PHP call resolution too. On a real Symfony/PHP
codebase this meant a core entity imported by 452 files showed 0 importers.

Additional context

Root cause: _extract_import in parser.py has a per-language branch for
Python, JS/TS, Go, Rust, Java, C#, Scala, Dart, Julia and others, but none for
PHP, so PHP use statements fall through to the raw-text fallback
(imports.append(text)) and store the entire statement as the edge target.
There is also no PHP case in _do_resolve_module (Java resolves import a.b.C;
to the class file; PHP needs the analogous mapping).

A fix is proposed in #573: a PHP branch in _extract_import (recording the FQN,
handling as aliases, grouped use A\{B, C}, and use function / use const)
plus a PHP branch in _do_resolve_module that resolves the FQN to an absolute
.php path by walking up from the importing file, mirroring the Java resolver.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions