Skip to content

PHP: static call through an import alias resolves to the wrong same-named method #1545

Description

@justin81527

PHP: static call through an import alias resolves to the wrong same-named method

Version: 1.5.0 · Language: PHP (Laravel)

Summary

For a static call made through an import alias — Alias::method() where
use Some\Fqn\Klass as Alias; — the receiver is not used to disambiguate.
Resolution falls back to matching the method name alone, so when two classes
define the same method name the call is attributed to the wrong class,
silently.

extractPHPImports already captures the alias correctly, so this looks like the
mapping simply isn't consulted when resolving scoped_call_expression.

Reproduction

// app/Services/SettleService.php
namespace App\Services;
class SettleService {
    public static function getSettlesToExcel($stores, $selected, $dayRange, $id) { /* … */ }
}

// app/Repositories/SettleRepository.php
namespace App\Repositories;
class SettleRepository {
    public static function getSettlesToExcel($storeIds, $dayRange) { /* … */ }
}

// app/Http/Controllers/Backend/SettleController.php
namespace App\Http\Controllers\Backend;
use App\Services\SettleService as Settle;          // ← alias

class SettleController extends Controller {
    public function excel($stores, $startDay = null, $endDay = null, $id = null) {
        return Settle::getSettlesToExcel($stores, request()->_SELECTED, [$startDay, $endDay], $id);
    }                //  ↑ the SERVICE
}

Expected

SettleController::excel is a caller of App\Services\SettleService::getSettlesToExcel.

Actual

$ codegraph impact getSettlesToExcel

Impact: "App\Services::SettleService::getSettlesToExcel (app/Services/SettleService.php:46)" affects 1 symbols
  app/Services/SettleService.php:  getSettlesToExcel:46            ← no callers found

Impact: "App\Repositories::SettleRepository::getSettlesToExcel (app/Repositories/SettleRepository.php:105)" affects 3 symbols
  app/Repositories/SettleRepository.php:  getSettlesToExcel:105
  app/Http/Controllers/Backend/SettleController.php:  excel:52     ← WRONG CLASS
  routes/web.php:  GET /excel/{startDay?}/{endDay?}/{id?}:149
$ codegraph callers getSettlesToExcel --file app/Services/SettleService.php
No callers found for "getSettlesToExcel"
$ codegraph node excel --file app/Http/Controllers/Backend/SettleController.php
Calls → getSettlesToExcel (app/Repositories/SettleRepository.php:105)   ← WRONG CLASS

What already works

extractPHPImports (src/resolution/import-resolver.ts) handles the alias:

const useRegex = /use\s+([\w\\]+)(?:\s+as\s+(\w+))?;/g;
// use App\Services\SettleService as Settle;
//   → { localName: 'Settle',
//       exportedName: 'SettleService',
//       source: 'App\\Services\\SettleService' }

So the binding Settle → App\Services\SettleService is available. The gap seems
to be that a scoped_call_expression's receiver isn't resolved through that
mapping before name-matching the method.

(Separately, src/extraction/languages/php.ts's namespace_use_clause branch
reads only the qualified_name child and never the namespace_aliasing_clause,
so the import node it emits carries no alias either — may or may not be
relevant depending on which path feeds resolution.)

Why this matters for PHP/Laravel

use App\Services\XxxService as Xxx; is an extremely common Laravel convention,
and Service/Repository layers routinely share method names.

Measured on one real Laravel 5.7 project (281 files, 1,986 nodes, 2,626 edges):

  • 27 method names defined in both the Service and the Repository layer
    create, update, delete, list, find, completed, received,
    getSettles, updateSettle, addSettle, getQuantity, getSumData, …
  • alias imports throughout the controllers (Order ×7, Store/Settle/Diet ×5 each)

Effectively every Controller→Service edge is attributed to the Repository.

The failure is quiet: explore's verbatim source is unaffected and looks right,
so only the graph edges (impact / callers / callees, and explore's Flow
section when it picks the wrong same-named node) are wrong — with no warning that
the layer attribution is a guess.

Suggested direction

When resolving Receiver::method() (and $obj->method() where the receiver type
is known), resolve Receiver through the file's ImportMapping.localName first
and constrain the method lookup to that class; fall back to name matching only if
the receiver can't be resolved. Emitting multiple candidates (as the docs describe
for ambiguous calls) would already be better than silently choosing one.

Environment

  • codegraph 1.5.0, macOS 26.5.2 (arm64)
  • PHP 8 syntax, Laravel 5.7, 281 files indexed

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