A method called through a PHP fluent static factory — `ApiClient::for($c)->createOrder()`,
the canonical Laravel per-credential/per-tenant client idiom — produced no
`calls` edge: the receiver of `->createOrder` is the `Cls::for(...)` static
call, whose result type was never recovered, so the edge was dropped and
`codegraph_callers` returned nothing.
Same shape as the C++ singleton/factory fix (#645), reusing its return_type
column + the chained-call mechanism:
- Capture PHP return types (getReturnType): `: self` / `: static` / `$this`
stored as the `self` marker, a concrete `: Type` as its short name,
primitives/unions dropped.
- Encode the chained scoped-call receiver as `Cls::for().method` so the
resolver can split it (PHP-gated, in extractCall).
- New matchPhpCallChain: look up the factory's return type (`self` → the
factory's own class; concrete → that class), then resolve AND validate the
method on it — a wrong inference yields no edge, never a wrong one.
EXTRACTION_VERSION 4->5 (re-index to populate PHP return types + chained edges).
Validated on koel (1383 PHP files): node count identical (no explosion),
0 edges lost, +80 chained-call edges recovered; synthetic tests cover the
self-factory, concrete-return, namespace, decoy, and absent-method cases.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>