ISWIN
Загрузка ...
Все статьи
1С-Битрикс и PHP 8 мин на чтение

Ошибка fread() в Битриксе: сайт отдаёт 500 после обновления PHP

Сайт открывается, но отвечает кодом 500, а страницы выпадают из поиска. Разбираем причину, разницу между PHP 7.4 и PHP 8 и безопасный способ исправления.

Сайт может открываться и выглядеть исправно, но сервер при этом отвечает кодом HTTP 500. Посетитель видит страницу, а поисковый робот получает сигнал о внутренней ошибке и со временем исключает адрес из поиска.

Одна из типичных причин после перехода с PHP 7.4 на PHP 8 — чтение пустого файла через fread(). В старой версии PHP такой вызов оставлял предупреждение в журнале и позволял скрипту продолжить работу. В PHP 8 он вызывает ValueError и останавливает выполнение. Разберём, как найти проблемный файл и исправить сам код, чтобы ошибка не вернулась.

Как проявляется ошибка

На первый взгляд всё работает: открывается главная, загружается каталог, видны изображения, оформляется заказ. Проблему обычно замечают позже по одному из признаков:

  • в Яндекс Вебмастере или Google Search Console появляются ошибки сервера;
  • страницы начинают выпадать из поиска;
  • проверка ответа сервера показывает 500 вместо 200;
  • в журнале PHP или 1С-Битрикс повторяется исключение из fread().

Так бывает, когда исключение возникает ближе к концу выполнения страницы, например при подключении подвала. Основная часть HTML к этому моменту уже сформирована, поэтому в браузере поломка может быть незаметна.

Точный текст ошибки

Запись в журнале выглядит примерно так:

UNCAUGHT_EXCEPTION - [ValueError]
fread(): Argument #2 ($length) must be greater than 0
/local/templates/ваш_шаблон/inc/svg-icons.php:15
#0: fread
    /local/templates/ваш_шаблон/inc/svg-icons.php:15
#1: require_once(string)
    /local/templates/ваш_шаблон/footer.php:292
#2: include_once(string)
    /bitrix/modules/main/include/epilog_before.php:93

Имена файлов и номера строк будут другими. Для диагностики важны ValueError, сообщение fread(): Argument #2 ($length) must be greater than 0 и первый путь внутри вашего шаблона. Если в цепочке есть footer.php, это объясняет, почему ошибка повторяется на всех страницах.

Путь к журналу зависит от настройки проекта и сервера. Сначала проверьте журнал исключений, указанный в .settings.php, затем журнал PHP или веб-сервера в панели хостинга.

Почему PHP 8 останавливает страницу

Ошибка уже была в коде, но PHP 7.4 позволял ей оставаться незаметной. Типовая конструкция в старом шаблоне выглядит так:

<?php
$in = fopen($file, 'rb');
while ($line = fread($in, filesize($file))) {
    // Обработка содержимого.
}

Второй аргумент fread() задаёт количество байт для чтения. Если файл пустой, filesize() возвращает 0, и функция получает недопустимую длину.

Мы воспроизвели этот вызов на двух локальных контурах с одинаковым файлом нулевого размера.

PHP 7.4: предупреждение, затем продолжение работы

Warning: fread(): Length parameter must be greater than 0
completed

Скрипт доходит до конца. Предупреждение остаётся в журнале, но страница продолжает выполняться.

PHP 8.4: необработанное исключение

Fatal error: Uncaught ValueError:
fread(): Argument #2 ($length) must be greater than 0
Stack trace:
#0 Command line code(1): fread(Resource id #5, 0)
#1 {main}

В PHP 8 некорректное значение аргумента приводит к ValueError. Это исключение означает, что тип аргумента подходит, но само значение недопустимо. Такое же поведение сохраняется в PHP 8.1, 8.2, 8.3 и 8.4.

Описание параметров функции есть в официальной документации fread(), а логика ValueError — в справочнике PHP по исключениям.

Откуда берётся пустой файл

Чаще всего это файл, который должен был сформироваться автоматически, но сборка завершилась с ошибкой. В нашем случае проблемой оказался _sprite_all.svg — общий спрайт иконок. Скрипт сборки оставил файл размером 0 байт.

Такая же ситуация встречается с другими артефактами:

  • SVG-спрайтами и наборами иконок;
  • собранными файлами CSS и JavaScript;
  • выгрузками и временными файлами после прерванной операции;
  • файлами кеша, созданными до сбоя.

Иногда защита от служебного файла в коде уже есть, но стоит после чтения:

while ($line = fread($in, filesize($file))) {
    $iconName = pathinfo($file, PATHINFO_FILENAME);
    if ($iconName === '_sprite_all') {
        continue;
    }
}

В PHP 8 выполнение до проверки не доходит: исключение возникает строкой выше.

Как исправить ошибку

Шаг 1. Найдите строку из журнала

Откройте последнюю запись об ошибке и найдите первый путь внутри /local/templates/. Журнал сразу укажет файл и строку с вызовом fread().

Шаг 2. Найдите файлы нулевого размера

При доступе по SSH выполните команду из корня сайта:

find local/templates -type f -size 0 -print

Если доступен только файловый менеджер или FTP, откройте каталог из журнала и отсортируйте файлы по размеру.

Шаг 3. Уберите проблемный файл

Сначала сделайте резервную копию каталога. Очевидный результат неудачной сборки можно удалить и затем собрать заново. Если назначение файла непонятно, безопаснее переименовать его, добавив к имени .bak. Так изменение легко вернуть.

После этого снова проверьте код ответа. Если причина была только в пустом файле, сервер перестанет отвечать кодом 500.

Шаг 4. Исправьте чтение файла

Удаление пустого файла устраняет проявление ошибки, но не мешает сборке создать такую же пустышку снова. Поэтому проверка должна стоять до вызова fread():

foreach ($files as $file) {
    $size = filesize($file);
    if ($size === false || $size === 0) {
        continue;
    }

    $in = fopen($file, 'rb');
    if ($in === false) {
        continue;
    }

    $line = fread($in, $size);
    fclose($in);

    // Обработка содержимого.
}

Если файл всё равно читается целиком, код можно упростить:

foreach ($files as $file) {
    $contents = file_get_contents($file);
    if ($contents === false || $contents === '') {
        continue;
    }

    // Обработка содержимого.
}

Для чтения файла целиком PHP рекомендует file_get_contents(). Строгое сравнение здесь важно: функция возвращает false при ошибке и пустую строку для пустого файла.

Как проверить результат

Обычного просмотра в браузере недостаточно: до исправления страница могла выглядеть нормально. Проверяйте именно код ответа.

Из терминала:

curl -s -o /dev/null -w "%{http_code}\n" https://ваш-сайт.ru/

В ответе должно быть 200. Проверьте не только главную, но и несколько внутренних страниц: каталог, карточку товара, контакты. После проверки снова откройте журнал и убедитесь, что новых записей с ValueError нет.

Когда нужен разработчик

Один очевидный артефакт сборки можно убрать самостоятельно. Помощь разработчика потребуется, если:

  • после обновления PHP появилось несколько разных исключений;
  • назначение пустого файла непонятно и его нельзя безопасно пересобрать;
  • после удаления файла сервер по-прежнему отвечает кодом 500;
  • не открывается весь сайт, включая административную часть;
  • нужно исправить шаблон и проверить остальные несовместимости с PHP 8.

Переход на новую версию PHP лучше сначала проводить на копии сайта. На ней нужно открыть ключевые страницы, выполнить основные сценарии и проверить журнал ошибок. Только после этого версию PHP стоит менять на рабочем сайте.