GLPI 11: что изменилось и какие плагины сломались

Почему плагины ломаются в GLPI 11: изменения архитектуры (Symfony/Twig, public/, PHP 8.2, драйвер без сырого SQL), матрица совместимости и чек-лист тестирования, который мы проходим перед миграцией.

В этой статьеРазделов: 8

В GLPI 11 «плагин сломался» почти никогда не означает баг самого плагина: под ним изменился фундамент. При сопровождении клиентских сред сценарий повторяется: клиент ставит новое ядро, два-три плагина пропадают из меню, один валит экран с ошибкой 500, а php-errors.log остаётся раздражающе пустым. Это руководство отделяет то, что стало встроенным, от того, что действительно перестало работать, показывает фрагмент кода, из-за которого плагин не проходит в 11, и даёт чек-лист, который мы проходим перед тем, как назначить окно миграции.

Что изменилось в ядре и почему это ломает плагины

11 — это не новая тема поверх 10. Это замена движка. Каждое изменение ниже отменяет паттерн, которым плагины для GLPI 10 пользовались без лишних церемоний:

  • Фронтенд на Symfony + Twig. Старый процедурный поток (Html::header(), а затем echo HTML) уступил место контроллерам и шаблонам Twig. Плагин, который собирал страницу «вручную», теряет шапку, меню, а иногда и весь экран.
  • Приложение отдаётся из public/. Маршрутизацией занимается Symfony. Относительные include вида include("../../../inc/includes.php") зависят от рабочего каталога и перестают разрешаться.
  • Минимум PHP 8.2 со строгими сигнатурами. Унаследованному методу нужен совместимый return type. canCreate() без : bool выбрасывает «Declaration must be compatible», и плагин даже не загружается.
  • Драйвер БД отклоняет сырой SQL. Это самое болезненное. $DB->query() удалён, а $DB->request() со строковым литералом блокируется («Building and executing raw queries is prohibited»). Любой спрятанный запрос ломается при первом выполнении этого пути.
  • Флаги в hook.php приобрели каноническую форму. Маркеры вроде csrf_compliant обрабатываются ядром строже; если объявить их неправильно, плагин может не распознаваться как CSRF-compliant и получать 403 на POST.

Иначе говоря, «совместим с 11» — это не удача, а то, что автор портировал эти паттерны. Кто не портировал, тот ломается, и обычно ломается молча.

Плагины, ставшие частью ядра: переносите данные, а не переустанавливайте

Многие из самых популярных плагинов исчезли не из-за несовместимости, а потому что их функция вошла в ядро. Здесь задача не «найти версию для 11», а перенести данные во встроенный механизм:

  • FormCreator — стал встроенным модулем форм, новым движком. Прозрачного импорта нет; есть только инструмент перехода, и формы сами не переезжают.
  • GenericObject — пользовательские объекты теперь встроены. Пересмотрите каждый тип и сопоставьте его с активом или выпадающим списком ядра.
  • FusionInventory и дашборды плагинов — ещё с 10 их заменили GLPI Agent и встроенные дашборды. В 11 нет причин за них держаться.
  • Исходящие вебхуки — ядро теперь уведомляет внешние системы о событиях (создание заявки, смена статуса), покрывая часть того, что решалось плагином.

Матрица совместимости: почему падает каждый случай

Перед назначением окна мы в рамках сопровождения относим каждый установленный плагин к одной из этих строк. Решает не название, а техническая причина:

Плагин / случайТехническая причинаДействие до миграции
FormCreatorФункция стала встроенными формами (новый движок)Инвентаризировать и перенести формы, на которых держится обслуживание, — сами они не переносятся
GenericObjectПользовательские объекты теперь встроеныСопоставить каждый тип с активом или выпадающим списком ядра
FusionInventoryЗаменён GLPI Agent (с версии 10)Заранее перевести сбор данных на GLPI Agent
Плагин с сырым SQL / процедурным выводомИспользует $DB->query() или Html::header() — удалены/сломаныНужен портированный релиз; без него — отключить
Плагин без return type в can*()PHP 8.2 отвергает сигнатуру; плагин не загружаетсяДождаться версии от автора или вывести из эксплуатации
Fields, DataInjection, PDF, TagЕсть релиз для 11Обновлять после ядра, по одному, проверяя после каждого
NexToolСовместим с 10 и 11 (один и тот же портированный код)Обновить до версии для 11
Плагин без релизов с 2023 годаСкорее всего, не портированБизнес-решение: заменить или вывести из эксплуатации

Диагностика: измеряйте, а не решайте по памяти

Не классифицируйте на глаз. Соберите реальное состояние плагинов и просканируйте их исходники на паттерны, которые ядро 11 удалило:

# Estado real dos plugins (rode no GLPI 10 antes de migrar; somente leitura)
# state: 0=novo  1=ativo  2=nao instalado  3=a configurar
#        4=nao ativado  5=a limpar  6=nao atualizado
mysql -u glpi -p glpi -e \
  "SELECT name, directory, version, state FROM glpi_plugins ORDER BY state, name;"

# Varra o fonte dos plugins atras dos padroes que o core 11 removeu
cd /var/www/glpi/plugins
grep -rln '$DB->query('   . --include='*.php'   # driver de SQL cru removido no 11
grep -rln 'Html::header'  . --include='*.php'   # saida procedural pre-Twig
grep -rln 'includes.php'  . --include='*.php'   # include relativo fragil no roteamento Symfony

Типичная ошибка — доверять только отметке в маркетплейсе. Релиз с пометкой «GLPI 11» всё ещё может содержать сырой SQL в редко используемом пути, который выстрелит только тогда, когда откроют именно этот отчёт, через несколько недель после go-live.

Код, из-за которого плагин не проходит в 11

Если вы поддерживаете собственный плагин или должны оценить сторонний, имея исходники на руках, вот три паттерна, которые чаще всего встречаются при портировании кода с 10 на 11:

// GLPI 10 (funcionava): SQL cru direto no driver
$res = $DB->query("SELECT id FROM glpi_tickets WHERE status = 1");

// GLPI 11: $DB->query() foi REMOVIDO e request() com string crua e bloqueado.
// SELECT vira query builder por criterios:
$rows = $DB->request([
    'SELECT' => 'id',
    'FROM'   => 'glpi_tickets',
    'WHERE'  => ['status' => 1],
]);

// DDL / SQL literal inevitavel (ALTER, SHOW INDEX): use doQuery()
$DB->doQuery("ALTER TABLE glpi_plugin_x ADD COLUMN ativo TINYINT DEFAULT 0");

// PHP 8.2+: metodo herdado exige return type; sem ": bool" o plugin nem carrega
public static function canCreate(): bool
{
    return Session::haveRight('plugin_x', CREATE);
}

Обратите внимание, что изменился и алиас столбца: старый трюк с inline-обратными кавычками, который GLPI 10 терпел, теперь нужно заменить на 'name AS filho', потому что query builder в 11 корректно экранирует обратные кавычки.

Как проверить совместимость до окна

Никогда не обнаруживайте несовместимость в продакшене. Сценарий, который мы применяем на тестовом стенде:

  1. Клон продакшена. Поднимайте 11 на копии реальной базы, а не на демонстрационной. Плагин ломается на ваших данных, а не на чистых.
  2. Устанавливайте и активируйте через консоль. php bin/console glpi:plugin:install <diretorio> и glpi:plugin:activate <diretorio> от имени пользователя веб-сервера. Если плагин выполняет миграцию при install/init, она запустится именно здесь.
  3. Откройте каждую страницу front/ плагина. Недостаточно активировать без ошибок; движок Twig жалуется только тогда, когда экран действительно рендерится.
  4. Смотрите правильный лог. Сбой загрузки не становится фатальной ошибкой PHP — он превращается в glpi.ERROR: Error while loading plugin X в логе GLPI. Кто смотрит только php-errors.log, ошибочно решает, что «всё в порядке».
  5. Сбрасывайте OPcache между попытками. После замены файлов php-fpm всё ещё отдаёт старый байткод; перезагрузите процесс (например, kill -USR2 мастер-процессу php-fpm), а не только Apache.

Чему нас научило сопровождение

Самый коварный инцидент, который мы видели, проявился не во время самой миграции, а через несколько месяцев, при простом bump версии административного плагина. Он выполнял миграцию схемы внутри plugin_init — распространённый и на вид безобидный паттерн: блок выполняется только пока записанный schema_version меньше версии в setup.php. Скрытый баг в этом месте дремал до следующего bump, а когда наконец выполнился и выбросил исключение, GLPI 11 перехватил его в Plugin::load и сам отключил плагин. Для клиента симптом выглядел как «включается и выключается без видимой ошибки», а плагин скатывался в состояние 4 (не активирован, что выглядит нормально). С тех пор правило жёсткое: каждый bump плагина с миграцией в init требует smoke-теста активации после bump, а блок миграции всегда помещается в try/catch, причём schema_version записывается ВНЕ try — иначе он пытается снова на каждом запросе и уходит в цикл. Такие детали держит в мышечной памяти только тот, кто каждый день эксплуатирует GLPI клиентов.

Стоит ли мигрировать сейчас?

Если ваша матрица плагинов зелёная (портированный релиз для всех ключевых) и у вас есть тестовый стенд с клоном продакшена — да: 11 современнее, безопаснее и быстрее. Если вы зависите от плагина без версии для 11, решение становится бизнесовым: заменить встроенной функцией, сменить плагин или отложить. Чего делать нельзя, так это мигрировать вслепую и обнаруживать несовместимость на работающей среде.

Если у вашей команды нет окна, чтобы отрепетировать миграцию и спокойно классифицировать каждый плагин, NexTool проведёт обновление вашего GLPI с инвентаризацией плагинов, тестированием на клоне и отрепетированным откатом. Свяжитесь с нами по поводу поддержки и сопровождения GLPI.


Проверено командой NexTool Solutions.

Часто задаваемые вопросы

Потому что изменился фундамент: фронтенд на Symfony/Twig, приложение отдаётся из public/, PHP 8.2+ со строгими return types и драйвер, отклоняющий сырой SQL ($DB->query() удалён). Плагин для GLPI 10, зависящий от любого из этих паттернов, при загрузке на новом ядре выдаёт ошибку 500 или отключается при загрузке.

Не доверяйте только отметке в маркетплейсе. Проверьте на тестовом стенде с клоном продакшен-базы: установите и активируйте через консоль, откройте каждую страницу front/ плагина и проверьте glpi.ERROR, а не только php-errors.log. Релиз может прятать сырой SQL в редко используемом пути, который выстрелит только через несколько недель после go-live.

Не как плагин для создания форм. Его функция стала встроенным модулем форм — новым движком без прозрачного импорта. Для старых форм есть только инструмент перехода, и сами они не переезжают. Посчитайте, сколько у вас форм, прежде чем назначать миграцию.

Сбой загрузки плагина не становится фатальной ошибкой PHP. GLPI перехватывает исключение в Plugin::load и записывает его как 'glpi.ERROR: Error while loading plugin X' в собственный лог GLPI. Смотрите лог GLPI, а не только лог PHP — это самая частая ошибка при диагностике плагина, который «пропадает из меню».

Скорее всего, он выполняет миграцию схемы в plugin_init, которая запускается только пока записанный schema_version меньше версии в setup.php. Скрытый баг там дремлет до следующего bump; когда он срабатывает и выбрасывает исключение, GLPI 11 сам отключает плагин. Оберните миграцию в try/catch и записывайте schema_version вне try, иначе она будет повторяться на каждом запросе.

OPcache в php-fpm всё ещё отдаёт старый байткод, скомпилированный в памяти. Перезагрузите процесс php-fpm (например, kill -USR2 мастер-процессу php-fpm), а не только Apache. Это самая частая причина «исправления, которое не применилось» сразу после замены файлов плагина.

Нужна помощь?