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 и передать данные файлом.
Идея решения
- Веб‑код собирает двумерный массив ячеек и сериализует его во временный файл (или пишет JSON — главное, чтобы формат согласовать с CLI‑скриптом).
- Через
shell_exec/proc_openвызывается/usr/bin/php /path/to/array_to_excel.php /tmp/…; в CLI для этого воркера отключают overload или поднимают отдельныйphp.ini. - Обратно читается готовый бинарный
.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 дней гарантии