Yandex Metrika
sanches.free 1 просмотр

PHPExcel в 1С‑Битрикс при mbstring.func_overload=2: вынос генерации XLS в отдельный PHP‑CLI

Зачем вообще CLI, если всё уже на сервере

На типичной установке «1С‑Битрикс» в php.ini может стоять mbstring.func_overload = 2: так рекомендует документация продукта, чтобы однобайтовые функции вели себя предсказуемо в UTF‑8. Библиотека PHPExcel (и часть легаси‑кода вокруг неё) написана под классическую модель строк и при перегрузке часто ломается уже при инициализации — ошибка всплывает до того, как вы успеете построить книгу.

Официально PHPExcel давно заменён на PhpSpreadsheet; для новых задач разумнее сразу перейти на него и актуальный PHP. Если же нужно «поднять» существующий XLS‑вывод без полной переделки, рабочая схема из практики — не грузить PHPExcel в том же FPM/Apache‑процессе, что обслуживает сайт, а запустить короткий php в режиме CLI с отдельными настройками mbstring и передать данные файлом.

Идея решения

  1. Веб‑код собирает двумерный массив ячеек и сериализует его во временный файл (или пишет JSON — главное, чтобы формат согласовать с CLI‑скриптом).
  2. Через shell_exec/proc_open вызывается /usr/bin/php /path/to/array_to_excel.php /tmp/…; в CLI для этого воркера отключают overload или поднимают отдельный php.ini.
  3. Обратно читается готовый бинарный .xls из того же пути и отдаётся пользователю как вложение.

Так вы разводите «битриксовый» mbstring и «excel‑овский» PHPExcel по разным процессам.

Обертка в веб‑запросе

Ниже — аккуратная схема вызова: путь к бинарнику PHP и к скрипту передаются через escapeshellarg, чтобы не ломать кавычки в DOCUMENT_ROOT и исключить инъекцию в командную строку. Аргумент с временным файлом тоже экранируется.

final class ExcelConverterCli
{
    private string $file;

    public function __construct()
    {
        $this->file = (string)tempnam(sys_get_temp_dir(), 'xls_');
    }

    public function __destruct()
    {
        if (is_file($this->file)) {
            unlink($this->file);
        }
    }

    public function write(string $payload): self
    {
        if (file_put_contents($this->file, $payload, LOCK_EX) === false) {
            throw new RuntimeException('Cannot write temp file');
        }
        return $this;
    }

    public function toExcel(string $cliPhpBinary, string $workerScript): self
    {
        $cmd = sprintf(
            '%s -d mbstring.func_overload=0 %s %s',
            escapeshellarg($cliPhpBinary),
            escapeshellarg($workerScript),
            escapeshellarg($this->file)
        );
        $output = shell_exec($cmd . ' 2>&1');
        if ($output === null || !preg_match('/^FILE SAVED:/m', (string)$output)) {
            throw new RuntimeException('CLI conversion failed: ' . (string)$output);
        }
        return $this;
    }

    public function readBinary(): string
    {
        $data = file_get_contents($this->file);
        if ($data === false) {
            throw new RuntimeException('Cannot read result');
        }
        return $data;
    }
}

// Пример: $sheet — массив [row][col] => значение
$data = serialize($sheet);
$converter = new ExcelConverterCli();
$bin = $converter
    ->write($data)
    ->toExcel('/usr/bin/php', $_SERVER['DOCUMENT_ROOT'] . '/local/tools/array_to_excel.php')
    ->readBinary();

Ключевой момент — флаг -d mbstring.func_overload=0 для одноразового процесса без правки глобального php.ini. Путь к интерпретатору и каталогу инструментов уточните под свой хостинг.

Worker только из CLI

Скрипт, который подключает PHPExcel и пишет Excel5, должен выполняться только из командной строки: иначе его можно вызвать с браузера и использовать как точку входа.

if (PHP_SAPI !== 'cli') {
    http_response_code(403);
    exit('CLI only');
}

$file = $argv[1] ?? '';
if ($file === '' || !is_readable($file)) {
    fwrite(STDERR, "Usage: php array_to_excel.php <path>\n");
    exit(1);
}

require_once __DIR__ . '/PHPExcel/Classes/PHPExcel.php';
// … создание PHPExcel, заполнение ячеек из unserialize(file_get_contents($file)), сохранение Writer Excel5 …
// В конце: echo "\nFILE SAVED: {$file}";

Используйте unserialize только если содержимое файла формируется вашим кодом и недоступно для подмены снаружи; для недоверенных данных применяйте JSON и явную схему ячеек.

Что ещё проверить в проекте

  • Совместимость PHP: PHPExcel официально не поддерживает свежие мажорные версии; CLI‑воркер может потребовать отдельный интерпретатор (например, модульный PHP 7.x рядом с PHP 8 для веба).
  • Права и open_basedir: каталог с библиотекой и sys_get_temp_dir() должны быть доступны пользователю, от имени которого крутится FPM.
  • Нагрузка: каждый отчёт порождает новый процесс; для очередей больших выгрузок лучше вынести работу в агент или очередь задач.

Кратко

Перегрузка mbstring в веб‑процессе и PHPExcel в одном контексте часто несовместимы. Вынесение генерации в PHP‑CLI с явным mbstring.func_overload=0 и аккуратным экранированием аргументов даёт предсказуемый обход без смены глобальных настроек всего сайта; долгосрочно целесообразна миграция на PhpSpreadsheet.

Не хотите копаться сами?

Починю за 1-3 дня. Без предоплаты — оплата по результату.

15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии