Skip to content

[PHP] include/require(_once) not mapped to dependency edges — only namespace use; impact/callers miss the file-include graph #660

@atahan150

Description

@atahan150

What happened

On a procedural / script-style PHP codebase (the kind that organizes code with include/require rather than Composer/PSR-4 namespaces — plenty of legacy apps, plain-PHP sites, and older CMS themes still look like this), codegraph builds an excellent call graph but produces zero file→file dependency edges for include / require / include_once / require_once.

Concretely: a page file foo.php whose first line is include("../loader.php"); yields callers(loader.php) = 0 and no edge of any kind linking foo.php → loader.php. The same holds for require_once. Only namespace use statements become import/dependency edges.

The call graph itself is great here — callers/callees/impact correctly attribute top-level (file-scope) function calls to the file node, which is exactly right for procedural code. This report is only about the missing include/require dependency layer.

Why it matters

For namespaced codebases this is a non-issue: use covers the dependency graph. But for include-based PHP, the include statement is the dependency mechanism — it's how a file gains access to the functions/constants it calls. Without those edges:

  • impact/callers can't answer "if I change helpers.php, which files that include it (directly or transitively) are affected?" beyond the direct function-call edges.
  • The file-level architecture graph is missing its primary structural relationship for this style of code.

Root cause (source)

src/extraction/languages/php.ts:

importTypes: ['namespace_use_declaration'],

importTypes lists only namespace_use_declaration. The PHP tree-sitter grammar represents includes as include_expression, require_expression (and the _once variants), which appear in none of the captured node-type lists (importTypes, callTypes, variableTypes, …), and visitNode only special-cases const_declaration and trait use_declaration. So include/require nodes are simply never visited as dependency edges.

Suggested fix

Capture include_expression / require_expression (+ _once variants) and emit a file→file dependency/import edge, resolving the argument string to a path (relative to the including file / configured roots). Even resolving only static string literals (the common case) would cover the overwhelming majority of real include sites; dynamic includes (include $var) can be skipped.

Repro

  1. Two files in the same dir:
    • lib.php: <?php function greet() { return "hi"; }
    • page.php: <?php require_once("lib.php"); echo greet();
  2. Index the directory.
  3. callers lib.php → returns 0 (no include edge from page.php).
    callees page.php → shows the greet call edge, but no dependency edge to lib.php.

Environment

  • codegraph @colbymchenry/codegraph@0.9.9 (via npx -y)
  • Node v24.2.0
  • Windows 11 (x64)

Verified on a real ~650-file procedural PHP project: the call graph matched grep ground truth at 100% file-level coverage for a representative helper, while every include/require relationship was absent from the graph.

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