Симптом. После апгрейда PHP мой phpMyAdmin на
Первая мысль — «битая сборка, переустановлю phpMyAdmin». Не угадал.
Почему это не баг phpMyAdmin. phpMyAdmin 5.2.3 (последний на сегодня) штатно везёт slim/psr7 1.4.2, а тот тянет psr/http-message 1.0 с нетипизированным
Кто настоящий виновник —
Ключевой момент: preload исполняется в мастере php-fpm при старте, и преложенные классы ложатся в общий OPcache, который наследует каждый воркер каждого пула этого мастера. Класс
PMA сам по себе цел, XenForo сам по себе цел — ломается их соседство в одном FPM-мастере.
Грабли, на которые тянет наступить. Очевидный фикс — «вынесу phpMyAdmin в отдельный FPM-пул». Не работает. preload — настройка уровня мастера (
Что реально решает. Развести их по разным мастерам. У меня уже был второй php-fpm (другой минорной версии) без этого preload — туда и увёл phpMyAdmin, поправив
Чужого 2.0-интерфейса в этом мастере нет → старый Uri компилируется против своего 1.0 → фатал уходит.
Чище — выкинуть phpMyAdmin вообще. Поставил Adminer: один PHP-файл, без Composer и без vendor, своего
Мораль. preload — мощная штука, но он протаскивает классы во все процессы своего мастера. Не прелоадь общие пакеты (
/line3/ начал отдавать 502. В логе nginx — фатал на ровном месте:
Код:
PHP Fatal error: Declaration of Slim\Psr7\Uri::withScheme($scheme) must be compatible with Psr\Http\Message\UriInterface::withScheme(string $scheme): Psr\Http\Message\UriInterface in .../vendor/slim/psr7/src/Uri.php
Первая мысль — «битая сборка, переустановлю phpMyAdmin». Не угадал.
Почему это не баг phpMyAdmin. phpMyAdmin 5.2.3 (последний на сегодня) штатно везёт slim/psr7 1.4.2, а тот тянет psr/http-message 1.0 с нетипизированным
withScheme($scheme). Внутри себя он консистентен и на голом PHP 8.5 работает нормально. Переустановка принесёт ровно тот же 1.4.2 и ровно тот же фатал. Лечить тут нечего — он жертва.Кто настоящий виновник —
opcache.preload. На том же сервере крутится XenForo, и под него у меня сделан preload, который прогревает весь его vendor — в том числе psr/http-message 2.0 с типизированным withScheme(string $scheme): UriInterface.Ключевой момент: preload исполняется в мастере php-fpm при старте, и преложенные классы ложатся в общий OPcache, который наследует каждый воркер каждого пула этого мастера. Класс
Psr\Http\Message\UriInterface уже определён по имени — поэтому когда phpMyAdmin компилирует свой старый Uri, его автолоадер для интерфейса даже не срабатывает: класс «уже есть». Только это чужой, типизированный 2.0. Старая нетипизированная реализация против него не проходит проверку сигнатур → Fatal.PMA сам по себе цел, XenForo сам по себе цел — ломается их соседство в одном FPM-мастере.
Грабли, на которые тянет наступить. Очевидный фикс — «вынесу phpMyAdmin в отдельный FPM-пул». Не работает. preload — настройка уровня мастера (
PHP_INI_SYSTEM), а не пула. Сколько пулов ни заводи под одним мастером, все они наследуют те же преложенные классы. Проверил руками: отдельный пул поднялся, запрос дошёл — и упал тем же самым фаталом.Что реально решает. Развести их по разным мастерам. У меня уже был второй php-fpm (другой минорной версии) без этого preload — туда и увёл phpMyAdmin, поправив
fastcgi_pass на его сокет:
NGINX:
location ^~ /line3/ {
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.4-fpm.sock; # мастер без preload XF
}
}
Чужого 2.0-интерфейса в этом мастере нет → старый Uri компилируется против своего 1.0 → фатал уходит.
Чище — выкинуть phpMyAdmin вообще. Поставил Adminer: один PHP-файл, без Composer и без vendor, своего
psr/http-message у него нет в принципе. Конфликтовать нечему — спокойно живёт рядом с XenForo на основном мастере, отдельный сокет под панель больше не нужен, а поверхность атаки в разы меньше. Доступ всё равно закрыл по IP в nginx — база смотрит в localhost, паролем тут не отделаешься.Мораль. preload — мощная штука, но он протаскивает классы во все процессы своего мастера. Не прелоадь общие пакеты (
psr/*, guzzle, symfony/*), если на том же мастере живёт что-то ещё: рано или поздно прилетит чужая мажорная версия того же интерфейса — и ты словишь этот фатал на пустом месте. Под мастером с preload держи только то приложение, ради которого preload и затевался. Всё остальное — на отдельный сокет или мастер.