Skip to content

FunctionFirstClassCallableRector breaks namespace resolution for string class-method callables (working code becomes a fatal) #9767

@dereuromark

Description

@dereuromark

Bug Report

Subject Details
Rector version 2.4.4
PHP version 8.4.21

FunctionFirstClassCallableRector converts a string class-method callable into a first-class callable, but it does not account for PHP name-resolution rules.

A string callable such as 'Cake\Utility\Inflector::underscore' is always resolved from the global namespace at runtime. The generated first-class callable Cake\Utility\Inflector::underscore(...) is instead resolved relative to the current namespace and use imports.

In a namespaced file that has no matching import (and no leading backslash), the rewritten code therefore resolves to <CurrentNamespace>\Cake\Utility\Inflector, which does not exist — turning working code into a fatal Error: Class not found at runtime.

Minimal PHP Code Causing Issue

<?php
namespace My\App;

class Demo
{
    public function run(): array
    {
        // String callable: resolved from the global namespace at runtime -> works.
        return array_map('Cake\Utility\Inflector::underscore', ['FooBar', 'BazQux']);
    }
}

Config:

use Rector\CodingStyle\Rector\FuncCall\FunctionFirstClassCallableRector;
use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withRules([FunctionFirstClassCallableRector::class]);

Result:

-        return array_map('Cake\Utility\Inflector::underscore', ['FooBar', 'BazQux']);
+        return array_map(Cake\Utility\Inflector::underscore(...), ['FooBar', 'BazQux']);

The rewritten Cake\Utility\Inflector::underscore(...) now resolves to My\App\Cake\Utility\Inflector::underscore in this file → Error: Class "My\App\Cake\Utility\Inflector" not found.

Expected Behaviour

The transformation must preserve the original (global) name resolution. Any of:

  • emit a leading backslash: \Cake\Utility\Inflector::underscore(...), or
  • add a use Cake\Utility\Inflector; import and use the short name, or
  • skip the rule when the class referenced in the string callable is not imported / not resolvable in the current namespace.

(Found while running rector across a set of CakePHP plugins; reduced to the minimal case above.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions