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.)
Bug Report
FunctionFirstClassCallableRectorconverts 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 callableCake\Utility\Inflector::underscore(...)is instead resolved relative to the current namespace anduseimports.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 fatalError: Class not foundat runtime.Minimal PHP Code Causing Issue
Config:
Result:
The rewritten
Cake\Utility\Inflector::underscore(...)now resolves toMy\App\Cake\Utility\Inflector::underscorein this file →Error: Class "My\App\Cake\Utility\Inflector" not found.Expected Behaviour
The transformation must preserve the original (global) name resolution. Any of:
\Cake\Utility\Inflector::underscore(...), oruse Cake\Utility\Inflector;import and use the short name, or(Found while running rector across a set of CakePHP plugins; reduced to the minimal case above.)