Skip to content

fix(extract): link a PHP construction site to the class it constructs - #3178

Closed
rajanpanth wants to merge 1 commit into
Graphify-Labs:v8from
rajanpanth:fix/php-object-creation-edge
Closed

fix(extract): link a PHP construction site to the class it constructs#3178
rajanpanth wants to merge 1 commit into
Graphify-Labs:v8from
rajanpanth:fix/php-object-creation-edge

Conversation

@rajanpanth

Copy link
Copy Markdown
Contributor

Fixes #3115.

What

_PHP_CONFIG.call_types never listed object_creation_expression, so new Foo(...) never dispatched into walk_calls and a PHP method that only constructs a class — never calls a method on it — got no edge to it at all.

Why

Same gap Java closed in #1373 and C# closed in #2998, just not caught up for PHP yet. The issue's own numbers make the practical cost concrete: on a 615-file Symfony/DDD codebase, 505 new X( sites across 178 classes had no edge, and on message-bus code specifically ($bus->dispatch(new SomeCommand(...))), 0 of 22 dispatch sites linked to the command they construct — exactly the shape where construction is the control flow.

How

Added object_creation_expression to _PHP_CONFIG.call_types in extract.py.

That alone isn't sufficient, though — I checked how Java and C# handle the same node type in walk_calls (extractors/engine.py) before assuming the config change was the whole fix, since both needed a dedicated per-language branch there (their grammars expose the constructed type through a type field, which neither the generic nor either language's existing call-handling path reads by default). Confirmed empirically that tree-sitter-php's object_creation_expression is different again: it exposes no named fields at allnew, the class name, and the argument list are purely positional children (checked directly against the parsed tree, not just the grammar docs). So this needed its own branch: scan the node's children for a name or qualified_name type directly, keeping only the last namespace segment on a qualified name to match the existing scoped_call_expression/Java/C# convention for namespace-qualified names.

Test plan

  • Unit tests added/updated
  • Manual testing performed

Reproduced the issue's exact repro (Foo/Bar constructed positionally, Baz::create() as the working control) via the CLI directly: 11 edges before the fix (only the static call), 13 after (both construction sites now present), confirmed with the extraction cache cleared between runs so the "before" run wasn't reusing cached "after" output.

Added tests/test_php_object_creation.py, 6 cases: assignment position, argument position (the message-bus shape from the issue), namespace-qualified construction, and two regression guards confirming scoped_call_expression/member_call_expression still resolve correctly with the new branch in place. Reverting only the two source files (keeping the tests) fails 4 of 6 — the 2 regression-guard tests correctly still pass, since that code path is untouched.

Ran the full existing PHP-related suite (test_languages.py, test_php_type_resolution.py, test_multilang.py, test_csharp_object_creation.py as a sanity check on the analogous C# path) plus the new file: 47 passed, 4 skipped, no regressions. Ran the broader tests/ suite too; the only failure (test_extract_code_only_cli.py::test_mixed_repo_without_key_errors_and_points_at_code_only) reproduces identically on an unmodified checkout — it's an openai package availability difference in this environment, unrelated to this change.

ruff check clean on all three changed files. ruff format --check reports pre-existing (unmodified-baseline) formatting differences on the two source files unrelated to my diff, so I left formatting as-is rather than reformat unrelated code.

…Graphify-Labs#3115)

_PHP_CONFIG.call_types never listed object_creation_expression, so
new Foo(...) never dispatched into walk_calls and a method that only
constructed a class got no edge to it at all. Java has treated this as
a call since Graphify-Labs#1373, C# caught up in Graphify-Labs#2998; PHP had not.

Unlike Java and C#, whose object_creation_expression exposes the
constructed type through a type field, tree-sitter-php exposes no
named fields on this node at all: new, the class name, and the
argument list are purely positional. Reused neither existing branch
and instead scan the node's children directly for a name or
qualified_name child, keeping only the last namespace segment to
match the existing scoped_call_expression/Java/C# convention.

6 new tests cover assignment position, argument position (the
message-bus shape from the issue, where the construction never
appears in a declared position), namespace-qualified construction,
and regression guards for the two call shapes already in call_types.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Treats PHP new Foo(...) as a call, so a method that only constructs a class now gets a calls edge to it — covering the message-bus shape $bus->dispatch(new SomeCommand(...)) where construction is the control flow. Since tree-sitter-php exposes no named fields on object_creation_expression, _extract_generic gets a dedicated PHP branch that scans positional children for the name/qualified_name node and takes its last namespace segment. Existing static (Baz::create()) and member ($obj->work()) call resolution is unaffected.

Worth a look

  • PHP extraction now reports object construction as calls edgesgraphify/extract.py:1001 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • PHP constructor calls discard qualified namespaces before resolutiongraphify/extractors/engine.py:5303 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1923 functions depend on the 424 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 502 callers, 42 callees
  • new: _rebuild_code() — 98 callers, 50 callees
  • new: _extract_generic() — 18 callers, 24 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: extract_js() — 85 callers, 3 callees
  • new: dispatch_command() — 2 callers, 122 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 32 more — each is listed as a finding

Verification — 1923 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 1766 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 40 more finding(s) on lines outside this diff (see the check run).

@safishamsi

Copy link
Copy Markdown
Collaborator

Thanks @rajanpanth — we went with #3169 for the PHP object-creation edge (#3115), which shipped in v0.9.52 with the same calls-edge modeling as the C#/TS handlers plus an explicit self/static/parent guard. Your PR was functionally equivalent; closing as superseded, with appreciation. Release: https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.52

@safishamsi safishamsi closed this Aug 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PHP: new Foo(...) produces no edge — object_creation_expression missing from _PHP_CONFIG.call_types

2 participants