Repository navigation
System error: "'\self' is an invalid class name40 after upgrading #9493
Copy link
Copy link
Closed
Labels
Description
Bug Report
| Subject | Details |
|---|---|
| Rector version where I was upgrading from | 2.2.3 |
| Rector version where the issue was found | 2.2.7 |
| Rector version where I'm pinpointing the error | 2.2.5 |
After upgrading to this version, I'm getting a big bunch of errors after processing some files, I've uploaded them as an attachment.
Minimal PHP Code Causing Issue
It's a propietary codebase, but I casually found an open source script that reports the exact same error:
<?php
namespace Grav\Plugin;
use Grav\Common\Cache;
use Grav\Common\Plugin;
use Grav\Common\Uri;
class ProblemsPlugin extends Plugin
{
protected $results = [];
protected $check;
/**
* @return array
*/
public static function getSubscribedEvents()
{
return [
'onPluginsInitialized' => ['onPluginsInitialized', 100001],
'onFatalException' => ['onFatalException', 0],
];
}
public function onFatalException()
{
if ($this->isAdmin()) {
$this->active = false;
return;
}
// Run through potential issues
if ($this->problemChecker()) {
$this->renderProblems();
}
}
public function onPluginsInitialized()
{
if ($this->isAdmin()) {
$this->active = false;
return;
}
/** @var Cache $cache */
$cache = $this->grav['cache'];
$validated_prefix = 'problem-check-';
$this->check = CACHE_DIR.$validated_prefix.$cache->getKey();
if (!file_exists($this->check)) {
// If no issues remain, save a state file in the cache
if (!$this->problemChecker()) {
// delete any existing validated files
foreach (new \GlobIterator(CACHE_DIR.$validated_prefix.'*') as $fileInfo) {
@unlink($fileInfo->getPathname());
}
// create a file in the cache dir so it only runs on cache changes
touch($this->check);
} else {
$this->renderProblems();
}
}
}
protected function renderProblems()
{
$theme = 'antimatter';
/** @var Uri $uri */
$uri = $this->grav['uri'];
$baseUrlRelative = $uri->rootUrl(false);
$themeUrl = $baseUrlRelative.'/'.USER_PATH.basename(THEMES_DIR).'/'.$theme;
$problemsUrl = $baseUrlRelative.'/user/plugins/problems';
$html = file_get_contents(__DIR__.'/html/problems.html');
/**
* Process the results, ignore the statuses passed as $ignore_status.
*
* @param $results
* @param $ignore_status
*/
$processResults = function ($results, $ignore_status) {
$problems = '';
foreach ($results as $key => $result) {
if ('files' == $key || 'apache' == $key || 'execute' == $key) {
foreach ($result as $key_text => $value_text) {
foreach ($value_text as $status => $text) {
if ($status == $ignore_status) {
continue;
}
$problems .= $this->getListRow($status, '<b>'.$key_text.'</b> '.$text);
}
}
} else {
foreach ($result as $status => $text) {
if ($status == $ignore_status) {
continue;
}
$problems .= $this->getListRow($status, $text);
}
}
}
return $problems;
};
// First render the errors
$problems = $processResults($this->results, 'success');
// Then render the successful checks
$problems .= $processResults($this->results, 'error');
$html = str_replace('%%BASE_URL%%', $baseUrlRelative, $html);
$html = str_replace('%%THEME_URL%%', $themeUrl, $html);
$html = str_replace('%%PROBLEMS_URL%%', $problemsUrl, $html);
$html = str_replace('%%PROBLEMS%%', $problems, $html);
echo $html;
http_response_code(500);
exit;
}
protected function getListRow($status, $text)
{
if ('error' == $status) {
$icon = 'fa-times';
} elseif ('info' == $status) {
$icon = 'fa-info';
} else {
$icon = 'fa-check';
}
$output = "\n";
$output .= '<li class="'.$status.' clearfix"><i class="fa fa-fw '.$icon.'"></i><p>'.$text.'</p></li>';
return $output;
}
protected function problemChecker()
{
$min_php_version = \defined('GRAV_PHP_MIN') ? GRAV_PHP_MIN : '5.4.0';
$problems_found = false;
$essential_files = [
'cache' => true,
'logs' => true,
'images' => true,
'assets' => true,
'system' => false,
'user/data' => true,
'user/pages' => false,
'user/config' => false,
'user/plugins/error' => false,
'user/plugins' => false,
'user/themes' => false,
'vendor' => false,
];
if (version_compare(GRAV_VERSION, '0.9.27', '>=')) {
$essential_files['backup'] = true;
$backup_folder = ROOT_DIR.'backup';
// try to create backup folder if missing
if (!file_exists($backup_folder)) {
@mkdir($backup_folder, 0770);
}
}
if (version_compare(GRAV_VERSION, '1.1.4', '>=')) {
$essential_files['tmp'] = true;
$tmp_folder = ROOT_DIR.'tmp';
// try to create tmp folder if missing
if (!file_exists($tmp_folder)) {
@mkdir($tmp_folder, 0770);
}
}
// Perform some Apache checks
if (str_contains(php_sapi_name(), 'apache')) {
$require_apache_modules = ['mod_rewrite'];
$apache_modules = apache_get_modules();
$apache_status = [];
foreach ($require_apache_modules as $module) {
if (\in_array($module, $apache_modules)) {
$apache_module_adjective = ' Apache module is enabled';
$apache_module_status = 'success';
} else {
$problems_found = true;
$apache_module_adjective = ' Apache module is not installed or enabled';
$apache_module_status = 'error';
}
$apache_status[$module] = [$apache_module_status => $apache_module_adjective];
}
if (\sizeof($apache_status) > 0) {
$this->results['apache'] = $apache_status;
}
}
// Check PHP version
if (version_compare(phpversion(), $min_php_version, '<')) {
$problems_found = true;
$php_version_adjective = 'lower';
$php_version_status = 'error';
} else {
$php_version_adjective = 'greater';
$php_version_status = 'success';
}
$this->results['php'] = [$php_version_status => 'Your PHP version ('.phpversion().') is '.$php_version_adjective.' than the minimum required: <b>'.$min_php_version.'</b> - <a href="http://getgrav.org/blog/changing-php-requirements-to-5.5">Additional Information</a>'];
// Check for GD library
if (\defined('GD_VERSION') && \function_exists('gd_info')) {
$gd_adjective = '';
$gd_status = 'success';
} else {
$problems_found = true;
$gd_adjective = 'not ';
$gd_status = 'error';
}
$this->results['gd'] = [$gd_status => 'PHP GD (Image Manipulation Library) is '.$gd_adjective.'installed'];
// Check for PHP CURL library
if (\function_exists('curl_version')) {
$curl_adjective = '';
$curl_status = 'success';
} else {
$problems_found = true;
$curl_adjective = 'not ';
$curl_status = 'error';
}
$this->results['curl'] = [$curl_status => 'PHP Curl (Data Transfer Library) is '.$curl_adjective.'installed'];
// Check for PHP Open SSL library
if (\extension_loaded('openssl') && \defined('OPENSSL_VERSION_TEXT')) {
$ssl_adjective = '';
$ssl_status = 'success';
} else {
$problems_found = true;
$ssl_adjective = 'not ';
$ssl_status = 'error';
}
$this->results['ssl'] = [$ssl_status => 'PHP OpenSSL (Secure Sockets Library) is '.$ssl_adjective.'installed'];
// Check for PHP XML library
if (\extension_loaded('xml')) {
$xml_adjective = '';
$xml_status = 'success';
} else {
$problems_found = true;
$xml_adjective = 'not ';
$xml_status = 'error';
}
$this->results['xml'] = [$xml_status => 'PHP XML Library is '.$xml_adjective.'installed'];
// Check for PHP MbString library
if (\extension_loaded('mbstring')) {
$mbstring_adjective = '';
$mbstring_status = 'success';
} else {
$problems_found = true;
$mbstring_adjective = 'not ';
$mbstring_status = 'error';
}
$this->results['mbstring'] = [$mbstring_status => 'PHP Mbstring (Multibyte String Library) is '.$mbstring_adjective.'installed'];
// Check Exif if enabled
if ($this->grav['config']->get('system.media.auto_metadata_exif')) {
if (\extension_loaded('exif')) {
$exif_adjective = '';
$exif_status = 'success';
} else {
$problems_found = true;
$exif_adjective = 'not ';
$exif_status = 'error';
}
$this->results['exif'] = [$exif_status => 'PHP Exif (Exchangeable Image File Format) is '.$exif_adjective.'installed'];
}
// Check for PHP Zip library
if (\extension_loaded('zip')) {
$zip_adjective = '';
$zip_status = 'success';
} else {
$problems_found = true;
$zip_adjective = 'not ';
$zip_status = 'error';
}
$this->results['zip'] = [$zip_status => 'PHP Zip extension is '.$zip_adjective.'installed'];
// Check for essential files & perms
$file_problems = [];
foreach ($essential_files as $file => $check_writable) {
$file_path = ROOT_DIR.$file;
$is_dir = false;
if (!file_exists($file_path)) {
$problems_found = true;
$file_status = 'error';
$file_adjective = 'does not exist';
} else {
$file_status = 'success';
$file_adjective = 'exists';
$is_writeable = is_writable($file_path);
$is_dir = is_dir($file_path);
if ($check_writable) {
if (!$is_writeable) {
$file_status = 'error';
$problems_found = true;
$file_adjective .= ' but is <b class="underline">not writeable</b>';
} else {
$file_adjective .= ' and <b class="underline">is writeable</b>';
}
}
}
$file_problems[$file_path] = [$file_status => $file_adjective];
}
if (\sizeof($file_problems) > 0) {
$this->results['files'] = $file_problems;
}
return $problems_found;
}
}And here's our current configuration, it's a bit messy as we are enabling rules one by one:
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\SetList;
return RectorConfig::configure()
// ->withParallel()
->withCache(__DIR__.'/var/cache/rector')
->withPaths([
__DIR__ . '/inc',
__DIR__ . '/migrations',
__DIR__ . '/public',
__DIR__ . '/src',
__DIR__ . '/tests',
__DIR__ . '/utils',
])
->withPHPStanConfigs([
__DIR__ . '/phpstan.dist.neon',
])
->withRules([
// Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector::class,
// Utils\Rector\Rector\LegacyModelArrayAccessToGetterRector::class,
Utils\Rector\Rector\LegacyModelGetSportEventIdRector::class,
])
// ->withPhpSets()
->withTypeCoverageLevel(6)
->withDeadCodeLevel(1)
// ->withCodeQualityLevel(0)
// ->withCodingStyleLevel(0)
->withSets([
SetList::PHP_81,
])
->withComposerBased(
twig: true,
doctrine: true,
phpunit: true,
symfony: true,
)
->withAttributesSets(
symfony: false,
doctrine: true,
phpunit: false,
fosRest: false,
jms: false,
sensiolabs: false,
all: false,
symfonyRoute: false,
symfonyValidator: false,
)
->withPreparedSets(
deadCode: false,
codeQuality: false,
codingStyle: false,
typeDeclarations: false,
typeDeclarationDocblocks: false,
privatization: false,
naming: false,
instanceOf: false,
earlyReturn: false,
strictBooleans: false,
carbon: false,
rectorPreset: false,
// phpunitCodeQuality: true,
doctrineCodeQuality: true,
// symfonyCodeQuality: true,
symfonyConfigs: true,
)
->withSkip([
// \Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector::class,
// \Rector\Php81\Rector\Class_\MyCLabsClassToEnumRector::class,
// \Rector\Php81\Rector\MethodCall\MyCLabsMethodCallToEnumConstRector::class,
// \Rector\Php81\Rector\New_\MyCLabsConstructorCallToEnumFromRector::class,
\Rector\Php81\Rector\Property\ReadOnlyPropertyRector::class,
\Rector\Php81\Rector\Class_\SpatieEnumClassToEnumRector::class,
\Rector\Php81\Rector\MethodCall\SpatieEnumMethodCallToEnumConstRector::class,
\Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector::class,
\Rector\Php81\Rector\Array_\FirstClassCallableRector::class,
\Rector\Php81\Rector\MethodCall\RemoveReflectionSetAccessibleCallsRector::class,
])
;Expected Behaviour
No errors 😄
Reactions are currently unavailable
Activity
samsonasik commentedon Nov 15, 2025
Could you create reproducible error at https://getrector.com/demo ? Thank you.
devnix commentedon Nov 15, 2025
I've not been able to reproduce it within the 100 lines limit. Creating a local reproducer with this file isolated doesn't seem to report any error :/
samsonasik commentedon Nov 15, 2025
How about create simple reproducible repository we can clone and verify?
devnix commentedon Nov 15, 2025
That's what I've tried to do but the error in this file isolated is not getting replicated, and the codebase that's failing is proprietary.
I will keep trying to pinpoint the problem to different files in isolation in order to create a reproducer that I can work on in order to be shareable, but I fear that's going to be a complex problem involving rules applied to other files.
samsonasik commentedon Nov 16, 2025
probably on this PR related:
devnix commentedon Nov 16, 2025
It's very hard to track it down. If I start skipping directories to isolate the files that report this error it works. I'm disabling rulesets one by one to see if I can narrow the problem to a specific rule.
devnix commentedon Nov 17, 2025
I was wondering, is there any other way to get a better stacktrace?
samsonasik commentedon Nov 17, 2025
If it goes to deep
phpstan.phar, that seems hard to track unfortunatellydevnix commentedon Nov 17, 2025
After several attempts of running rector from source and load phpstan from source too and not getting a good stacktrace, I've attempted this, since I felt like the original exception is being masked:
This is the first part of the dumped error:
and this is the dumped node
and happened while scanning a specific file, which contains something like
at the line 40, after performing a conversion from
I guess here could be a reproducer, reporting a more understandable error: https://getrector.com/demo/c661b304-59b3-4bdf-b9c8-29b473cc8754
It looks indeed like it's not valid PHP: https://3v4l.org/lR7Qg
Removing the
\character leads to a correct Rector output!samsonasik commentedon Nov 17, 2025
That seems already error in php level, so rector can't handle it since php-parser confirm, I think using search -> replace with IDE will make it works :)
samsonasik commentedon Nov 17, 2025
I am going to close it, as it already error too early before it scanned per your provided test https://3v4l.org/lR7Qg
I think you can use
php-parallel-linttool to verify your code base first https://github.com/php-parallel-lint/PHP-Parallel-LintThank you for understanding.
devnix commentedon Nov 17, 2025
\selfwas generated by theClosureFromCallableToFirstClassCallableRectorrule.Looking at your fix at rectorphp/rector-phpunit#587, I've created a reproducer and fixed that specific case: #7632
github-actions commentedon Apr 19, 2026
This issue has been automatically locked because it has been closed for 150 days. Please open a new issue if you have a similar problem.