Security 26.200.0 не ставится на маке и Windows: фатал «Cannot redeclare class» прямо в апдейтере
Обновление безопасности
Закрыта уязвимость или ослабленная проверка прав. Ставить в первую очередь.
Обновление security 26.200.0 у нас не установилось вообще: апдейтер умирает с фаталом посреди своего скрипта. Если ваш прод на Linux — выдыхайте, вас это не касается. А вот если вы ставите обновления в локальной разработке на macOS или Windows (Docker с bind-mount проектной папки — это ровно тот случай) — есть неплохой шанс поймать то же самое. Разбираем, почему падает и что с этим делать.
Что происходит
Картина при установке через update:modules:
Installing security (26.200.0)... PHP Fatal error:
Cannot redeclare class Bitrix\Main\Mail\Internal\EventMessageTable
(previously declared in .../main/lib/Mail/Internal/EventMessageTable.php:33)
in .../main/lib/Mail/Internal/EventMessage.php on line 33
Апдейтер security добавляет новый почтовый шаблон USER_OTP_AUTH_CODE (письмо с одноразовым паролем) через старый добрый CEventMessage->Add(). Дальше цепочка такая:
CEventMessage->CheckFields()зовётCEventType::GetListEx(), а тот регистрирует в ORM runtime-поле со ссылкой наdata_type = 'Bitrix\Main\Mail\Internal\EventMessage'— имя сущности без суффиксаTable.- ORM делает
class_exists('...\EventMessage'), автозагрузчик по PSR-4 идёт за файломlib/Mail/Internal/EventMessage.php. - На Linux такого файла нет:
class_existsвернёт false, ORM спокойно допишетTableи продолжит. Никто ничего не замечает годами. - На регистронезависимой файловой системе (macOS APFS по умолчанию, Windows NTFS) путь
EventMessage.phpсхлопывается в легаси-файлeventmessage.php— старое lowercase-имя из тех времён, когда весь D7 лежал в нижнем регистре. Внутри него — тот же классEventMessageTable, который уже загружен из соседнегоEventMessageTable.php. PHP: «Cannot redeclare class», E_COMPILE_ERROR, занавес.
Соль в том, что в lib/Mail/Internal/ ядра сейчас живут обе версии файлов — старые lowercase (eventmessage.php, eventtype.php, …) и новые PascalCase (EventMessageTable.php, EventTypeTable.php, …). На Linux это два разных набора путей и всё легально. На маке и Windows это мина: любой class_exists по имени, которое случайно совпадёт со старым файлом, взводит её. Апдейтер security 26.200.0 — первый, кто на неё наступил.
Чем это грозит
- Обновление не устанавливается. Версия security остаётся старой, тег/файлы не обновляются. Повторный запуск падает точно так же — ошибка детерминированная.
- БД остаётся в полусостоянии. Фатал прилетает посреди апдейтер-скрипта: на нашей установке в
b_event_typeуже созданы типыUSER_OTP_AUTH_CODEиUSER_OTP_EMAIL_CONFIRM(у английских записей даже NAME не успел заполниться), а вb_event_message— ноль шаблонов: скрипт упал ровно на первомCEventMessage->Add(). После починки ФС обновление поставится поверх, но осиротевшие типы стоит проверить.
Что делать
- Прод на Linux — ничего, ставьте как обычно.
- Локальная разработка на маке/Windows — переносите код сайта на регистрозависимый том и обновляйтесь там. На маке это case-sensitive APFS-образ (Disk Utility → APFS (Case-sensitive)) или, для Docker, named volume вместо bind-mount проектной папки. Проверить свою ФС можно за секунду:
touch a A && ls— если файл один, вы в зоне риска. - Руками удалять «лишние» lowercase-файлы ядра не советуем: это правка ядра со всеми вытекающими при следующем обновлении.
Мелочи и находки
Легаси-файл и его PascalCase-близнец — не совсем копии: в новом EventMessageTable.php код тот же, но венгерские префиксы вычищены ($arReplaceTagsOne → $replaceTagsOne, $bOpenPhpTag → $openPhpTag). То есть ядро мигрирует файлы на PascalCase с косметикой, а старые файлы оставляет лежать рядом — совместимость для тех, кто require'ит их напрямую. На регистрозависимой ФС это честная стратегия; на регистронезависимой — как видим, не очень.
Разбор основан на воспроизведённом фатале при установке через update:modules на эталонной установке (main 26.700.0, macOS + Docker bind-mount) — у security-обновления, которое не установилось, диффа по понятным причинам нет.