PhpSpreadsheet: Excel из PHP — 9 примеров, мастерская и исходники
Девять настоящих Excel-книг из PHP: отчёты, формулы, ведущие нули, шаблоны, импорт и печать. Исследуйте ячейки в мастерской и скачивайте готовый проект.

Excel из PHP на практике: девять настоящих XLSX, формулы, импорт, типы данных и удобная мастерская с исходниками для Windows, Linux и VPS.
PhpSpreadsheet: когда PHP должен подготовить настоящий Excel
Менеджер нажимает «Выгрузить», получает аккуратную книгу с услугами и суммами, открывает её в Excel и продолжает работу. Сотрудник заполняет шаблон, возвращает файл в кабинет, а приложение объясняет, какие строки можно принять и где допущены ошибки. Такие задачи встречаются в CRM, магазинах, сервисных компаниях и внутренних системах чаще, чем кажется.
PhpSpreadsheet позволяет читать и создавать электронные таблицы средствами PHP. Сервер формирует листы, записывает значения, задаёт форматы, формулы и правила ввода, затем сохраняет XLSX. Установленный Microsoft Excel для этого кода не нужен. При этом библиотека не превращает PHP-страницу в полноценный онлайн-редактор Excel: интерфейс, права доступа, очередь экспорта и бизнес-правила остаются частью вашего приложения.
В этой публикации собраны девять настоящих книг и мастерская для исследования их содержимого. Вы сможете посмотреть тип ячейки, сравнить формулу с результатом, изучить ошибки импорта и скачать исходники. Используем PhpSpreadsheet 5.11.0, проверенную по официальному релизу и Packagist на 9 ноября 2026 года.
Где это применять: отчёты, обмен данными и шаблоны
Начните с конкретного результата, который нужен человеку. Для руководителя это может быть отчёт по проектам с общей выручкой и расходами; для менеджера — список незакрытых заявок; для бухгалтера — подготовленная выгрузка сумм; для администратора каталога — проверяемый шаблон массового редактирования. Назначение книги определяет столбцы, типы значений, правила проверки и способ доставки.
В интерфейсе удобно разделить фильтры и создание файла. Пользователь выбирает период, проект и состав столбцов, видит короткое описание будущего отчёта, затем запускает выгрузку. Если подготовка занимает заметное время, показывайте отдельную задачу со статусом и ссылкой на результат. Повторное нажатие не должно незаметно создавать несколько одинаковых тяжёлых операций.
Импорт устроен иначе: сначала анализ файла и понятный протокол, затем подтверждённое изменение данных. Полезно показать номер строки, проблемный столбец, исходное значение и ожидаемый формат. Сообщение «Ошибка файла» почти не помогает человеку исправить таблицу, а «Строка 7: количество должно быть целым от 1 до 50» сразу задаёт следующий шаг.
XLSX, CSV и HTML-таблица: выбираем подходящий результат
XLSX подходит, когда важны несколько листов, различие текста и чисел, формулы, оформление, списки выбора или печать. Внутри файла хранится структура книги, которую понимают офисные приложения. Преимущество хорошо видно на коде товара 000127: мы можем явно сохранить его текстом и не потерять значимые нули.
CSV удобен для простого обмена строками. Он легче читается обычными средствами PHP, но не содержит форматов ячеек, отдельных листов и правил проверки. Программа, открывающая CSV, самостоятельно решает, что считать датой, числом или формулой. Разделитель, кодировка, десятичный знак и трактовка опасных текстов должны быть частью договора между системами.
HTML-таблица нужна для просмотра в браузере. Не стоит выдавать HTML с расширением .xls и ожидать полноценную книгу: предупреждения о несоответствии формата только запутают пользователя. В мастерской предпросмотр и XLSX существуют отдельно: таблица помогает разобраться в данных, а скачанная книга содержит реальные настройки Excel.
Мини-мастерская: девять книг и инспектор ячеек
Выберите задачу в списке. Для отчёта попробуйте поиск «Сайт», затем переключите представление с отформатированного на исходное. В примере формул нажмите D2: ниже появятся сама формула, рассчитанный результат и числовой формат. В многолистовой книге выберите сводку и сравните её с исходными проектами.
Книги заранее созданы реальным PHP-кодом, сохранены в XLSX и повторно прочитаны библиотекой. Мастерская получает JSON именно из повторного чтения. Это исследовательский интерфейс: браузер не запускает PHP, не редактирует книгу и не отправляет ваш файл на сервер. Фильтры меняют только вид таблицы; кнопка скачивания всегда отдаёт полный выбранный XLSX.
Условные цвета, объединения, выпадающие списки и разбиение печати проверяйте в Excel или LibreOffice. Предпросмотр показывает данные и описание правил, а не воспроизводит весь офисный редактор. На телефоне широкая таблица прокручивается внутри рамки. Для работы с большим примером можно открыть мастерскую отдельной страницей.
Установка через Composer и требования к PHP
Библиотека устанавливается через Composer и подключается стандартным автозагрузчиком. Если вы впервые работаете с зависимостями PHP, сначала посмотрите наше практическое руководство по Composer: там разобраны install, update, lock-файл и устройство проекта. Для готовой мастерской нужен install по вложенному lock, а не создание нового набора версий.
У самой PhpSpreadsheet 5.11.0 минимальное требование — PHP 8.2. Однако конкретный lock нашего проекта включает zipstream-php 3.2.2, требующий PHP 8.3 x64. Поэтому скачиваемый проект рассчитан на PHP 8.3 и новее в пределах ветки 8. Это различие важно: требования одной библиотеки и всего разрешённого набора зависимостей могут не совпадать.
Понадобятся расширения XML, DOM, XMLReader, XMLWriter, SimpleXML, ZIP, GD, mbstring, а также стандартные ctype, fileinfo, filter, iconv, libxml и zlib. Команда проверки платформы даст точный список для вашего окружения. Не скрывайте отсутствующие расширения параметром ignore-platform-reqs: установка пройдёт дальше, но рабочая операция всё равно может завершиться ошибкой.
composer install --no-interaction --no-plugins --no-scripts --prefer-dist
composer check-platform-reqs
composer build
composer test
composer audit
Как устроена книга: Spreadsheet, лист, ячейка и Writer
Объект Spreadsheet представляет книгу целиком. У книги есть один или несколько листов, у листа — ячейки, строки, столбцы, фильтры и настройки печати. Writer превращает эту структуру в конкретный файл. Такой порядок позволяет сначала подготовить данные, проверить результат и только затем сохранить или отправить его пользователю.
В исходниках нашей мастерской класс Workshop отвечает за построение книг, команда build сохраняет их, а inspect читает сохранённый результат. Это помогает отделить ошибку подготовки от ошибки записи. Например, сумма может быть неверной ещё до появления файла, а требуемое правило проверки может потеряться на этапе настройки диапазона.
Минимальный пример ниже создаёт настоящий XLSX с одним листом. Запускайте его из каталога проекта, где Composer уже подготовил vendor/autoload.php. Каталог назначения должен существовать и быть доступен на запись. В рабочем приложении выбирайте путь сами, а не принимайте произвольное имя файла или каталог из параметров посетителя.
require __DIR__.'/vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
$book = new Spreadsheet();
$sheet = $book->getActiveSheet()->setTitle('Услуги');
$sheet->fromArray([['Услуга', 'Цена'], ['Аудит', 12000]], null, 'A1');
(new Xlsx($book))->save(__DIR__.'/report.xlsx');
$book->disconnectWorksheets();
Пример 1. Отчёт по услугам для менеджера
Первая книга похожа на привычную выгрузку из CRM: услуга, проект, цена, часы и статус. Цены остаются числами, поэтому сотрудник может сортировать их, складывать и использовать в своих формулах. Символ валюты добавляется форматом отображения, а не склеивается с исходным значением.
Шапка выделена цветом, первая строка закреплена, диапазон данных получил автофильтр. Это небольшие настройки, которые делают файл удобным после первой прокрутки. Если сотрудник ежедневно работает с отчётом, понятные названия столбцов и доступная фильтрация обычно полезнее большого количества декоративных цветов.
В мастерской найдите строки проекта «Сайт» и нажмите цену аудита. Исходное значение — число 12000, рядом показана отформатированная строка с рублями. Поиск здесь локальный и не изменяет файл. Для выгрузки конкретного проекта в настоящем кабинете фильтруйте записи на сервере до построения книги и записывайте выбранный период в заголовок отчёта.
$sheet->getStyle('C2:C7')->getNumberFormat()
->setFormatCode('#,##0.00 "₽"');
$sheet->freezePane('A2');
$sheet->setAutoFilter('A1:E7');
$sheet->getColumnDimension('A')->setWidth(25);
Пример 2. Ведущие нули, телефоны и длинные идентификаторы
Код клиента, номер договора и телефон обычно являются идентификаторами, даже если состоят из цифр. Их не нужно складывать или округлять. Потеря нуля в начале может превратить корректный код в другой объект, а запись длинного номера как числа способна потерять точность ещё до того, как пользователь заметит проблему.
Во втором примере мы явно задаём строковый тип для кода 000127, учебного телефона и двадцатизначного идентификатора. Дату, напротив, сохраняем как числовое значение Excel с форматом dd.mm.yyyy. Сумма тоже записана числом. Таким образом, внешний вид ячеек не подменяет их смысл.
Откройте A2 и D2 в инспекторе. У даты исходное значение выглядит как серийный номер — это ожидаемо. У кода исходная строка совпадает с видимым кодом, включая нули. При импорте заранее определите схему столбцов: нельзя надёжно восстановить исходный идентификатор, если чужая программа уже округлила его или отбросила значимые символы.
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Shared\Date;
$sheet->setCellValueExplicit('A2', '000127', DataType::TYPE_STRING);
$sheet->setCellValueExplicit('C2', '12345678901234567890', DataType::TYPE_STRING);
$sheet->setCellValue('D2', Date::PHPToExcel(
new DateTimeImmutable('2026-11-09T00:00:00+00:00')
));
$sheet->getStyle('D2')->getNumberFormat()->setFormatCode('dd.mm.yyyy');
Пример 3. Формулы, построчные суммы и общий итог
В третьей книге количество умножается на цену, затем SUM собирает общий итог. Для учебного набора он равен 44000. Дополнительная формула IF показывает «Согласовать», если итог превышает 40000. Такой файл можно передать менеджеру, чтобы он изменил количество и увидел обновлённую смету в офисном приложении.
Инспектор специально показывает три разных значения: исходную формулу, результат расчёта PHP и отформатированное представление. Это помогает диагностировать проблему. Если в ячейке виден текст формулы, проверьте тип; если формула верна, но результат неожиданный, проверьте диапазон и типы исходных ячеек.
В коде формулы записываются с английскими именами функций и запятыми между аргументами. Русский интерфейс Excel не означает, что в исходнике нужно писать СУММ. Для финансовых решений определите правила округления отдельно: отображение двух знаков после запятой не исправляет арифметику и не заменяет расчёт в минимальных денежных единицах или десятичной библиотеке.
$sheet->setCellValue('D2', '=B2*C2');
$sheet->setCellValue('D5', '=SUM(D2:D4)');
$sheet->setCellValue('D6', '=IF(D5>40000,"Согласовать","В пределах")');
$formula = $sheet->getCell('D2')->getValue();
$result = $sheet->getCell('D2')->getCalculatedValue();
Пример 4. Несколько листов и сводка по проектам
Не всегда удобно отправлять человеку несколько файлов. В одной книге можно разместить исходные проекты и отдельный лист со сводными показателями. Пользователь видит общие цифры, а при необходимости переходит к деталям, на которых они основаны. Это особенно полезно для регулярных управленческих отчётов.
Наш пример содержит лист «Проекты» и «Сводный отчёт». Сводка считает выручку 340000, расходы 215000 и разницу 125000 по данным первого листа. Это обычные межлистовые формулы, а не Excel PivotTable. Такая простая структура уже закрывает множество небольших отчётных задач и легко проверяется вручную.
Обратите внимание на кавычки вокруг имени листа в формуле. Для имён с пробелами они особенно важны. При построении отчёта из динамического набора проектов заранее задайте правила именования листов и обработайте повторения. Не используйте название клиента как единственное средство контроля доступа: содержимое всей книги должно быть разрешено получателю.
$projects = $book->getActiveSheet()->setTitle('Проекты');
$summary = $book->createSheet()->setTitle('Сводный отчёт');
$summary->setCellValue('B2', "=SUM('Проекты'!B2:B4)");
$summary->setCellValue('B3', "=SUM('Проекты'!C2:C4)");
$summary->setCellValue('B4', '=B2-B3');
Пример 5. Шаблон с выпадающим списком и проверкой количества
Иногда сотруднику нужно выдать не готовый отчёт, а форму для заполнения офлайн. В пятом примере статус выбирается из списка «Новая», «В работе», «Готово», а количество ограничено целыми значениями от одного до пятидесяти. Проверки настроены до десятой строки, поэтому действуют и в подготовленных пустых местах.
В мастерской нажмите C2 и B2, чтобы увидеть сохранённые правила. Для реального выбора статуса и проверки реакции офисного приложения скачайте XLSX. Веб-таблица не выдаёт себя за редактор: она показывает, что записано в книге, а взаимодействие со списком происходит в Excel или LibreOffice.
Проверка ввода помогает человеку, но не является серверной защитой. Правило можно удалить, обойти вставкой или потерять при конвертации. Когда файл возвращается в приложение, проверяйте всё повторно своим PHP-кодом. Для большого справочника используйте отдельный лист и диапазон допустимых значений, а короткий встроенный список оставьте для действительно небольшого набора вариантов.
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
$rule = $sheet->getCell('C2')->getDataValidation();
$rule->setType(DataValidation::TYPE_LIST)
->setErrorStyle(DataValidation::STYLE_STOP)
->setShowErrorMessage(true)->setShowDropDown(true)
->setFormula1('"Новая,В работе,Готово"');
Пример 6. Условное форматирование задолженности
Цвет полезен, когда помогает быстро заметить отклонение. В книге «Оплаты» задолженность больше нуля выделяется отдельно, а просрочка получает такое же предупреждение после семи дней. Сами суммы и сроки остаются числами. Пользователь может менять их, а офисное приложение применит сохранённое правило заново.
В инспекторе настроек показаны диапазоны B2:B5 и C2:C5, оператор сравнения и порог. Веб-предпросмотр намеренно не имитирует все особенности условного форматирования Excel. Скачайте книгу, измените задолженность на ноль и оцените поведение в программе, которой пользуется ваша команда.
Не делайте цвет единственным носителем смысла. Столбец «Дней просрочки», понятное название показателя и возможность сортировки остаются важными даже при красивом оформлении. Для реального отчёта стоит добавить дату формирования и определение долга: неоплаченный счёт, просроченная сумма и общая дебиторская задолженность могут означать разные вещи.
use PhpOffice\PhpSpreadsheet\Style\Conditional;
$rule = new Conditional();
$rule->setConditionType(Conditional::CONDITION_CELLIS)
->setOperatorType(Conditional::OPERATOR_GREATERTHAN)
->addCondition('0');
$rule->getStyle()->getFont()->getColor()->setARGB('FF9D1C1C');
$sheet->getStyle('B2:B5')->setConditionalStyles([$rule]);
Пример 7. Импорт с протоколом ошибок до записи в базу
В учебной книге намеренно есть одна допустимая и пять ошибочных строк: текст вместо количества, пустое название, повтор кода, отрицательная цена и формула в поле количества. PHP читает исходные значения и собирает сообщения. Ни одна операция не отправляет данные в базу: здесь демонстрируется именно этап проверки.
Откройте пример «Чтение и проверка импорта». Под инспектором находится настоящий протокол повторно прочитанного XLSX. Ошибка привязана к строке файла, поэтому её можно найти без пересчёта номеров вручную. Формула в C7 отклоняется независимо от того, какой результат она могла бы вернуть.
В реальном приложении сначала покажите предварительный результат: сколько строк корректно, какие ошибки найдены и что будет изменено. Затем применяйте выбранную политику — весь файл одной транзакцией или явно согласованный частичный импорт. Проверьте уникальность также в базе, добавьте защиту от повторной отправки и сохраните журнал результата. Внутрифайловая проверка дублей не заменяет эти действия.
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
$reader = new Xlsx();
$book = $reader->load(__DIR__.'/public/generated/import.xlsx');
$cell = $book->getActiveSheet()->getCell('C7');
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
throw new RuntimeException('В поле количества формулы запрещены');
}
$rawQuantity = $cell->getValue();
Пример 8. Как внешний текст не превратить в формулу
Название услуги может начинаться с равно, плюса или другого символа, который табличная программа трактует особым образом. Если выгрузка собирается из комментариев, названий и пользовательских полей, нельзя бездумно передавать каждую строку как доверенную формулу. Тип данных должен определяться схемой экспорта, а не первым символом внешнего текста.
В восьмой книге строки =2+2, +SUM(1,2), -1+2 и @SUM(A1:A2) записаны явно как текст. В B7 для сравнения находится формула, созданная самим разработчиком: она действительно рассчитывается в пять. Нажмите обе ячейки и сравните типы и значения. Так видно различие между внешним содержимым и разрешённой логикой отчёта.
Для CSV есть отдельная кнопка: опасным началам добавлен апостроф. Это изменяет представление и не даёт универсальной гарантии для любого табличного редактора и способа повторного сохранения. Для важных выгрузок лучше XLSX с явными типами и проверка в целевом приложении. Кавычки CSV экранируют структуру поля, но сами по себе не запрещают формулы.
use PhpOffice\PhpSpreadsheet\Cell\DataType;
$externalTitle = '=2+2';
$sheet->setCellValueExplicit('B2', $externalTitle, DataType::TYPE_STRING);
// Только формула, заданная логикой приложения:
$sheet->setCellValue('B7', '=SUM(2,3)');
Пример 9. Печатный лист, поля и повторяемая шапка
Последняя книга — учебный счёт с двумя услугами и итогом 25000. На ней явно написано, что документ не предназначен для оплаты. Пример нужен, чтобы показать область печати, ориентацию, масштаб по ширине, поля страницы, повторяемую строку заголовка и нижний колонтитул с номером страницы.
В мастерской видны значения и сохранённые параметры. Чтобы увидеть фактический макет, откройте предварительный просмотр печати в офисной программе. Шрифты, драйвер принтера и настройки конкретного приложения влияют на результат, поэтому наличие правильных свойств в XLSX ещё не подтверждает, что документ красиво поместился на лист.
Не растягивайте длинный отчёт на одну страницу по высоте автоматически: текст может стать нечитаемым. Часто разумнее ограничить ширину одной страницей, а высоту оставить автоматической. Для официального документа добавьте требуемые реквизиты и правила нумерации отдельно. Если нужен именно PDF, выберите и протестируйте отдельный путь рендеринга, а не считайте XLSX готовым печатным оригиналом.
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
$setup = $sheet->getPageSetup();
$setup->setPaperSize(PageSetup::PAPERSIZE_A4)
->setOrientation(PageSetup::ORIENTATION_PORTRAIT)
->setPrintArea('A1:D8')->setFitToWidth(1)->setFitToHeight(0)
->setRowsToRepeatAtTopByStartAndEnd(5, 5);
$sheet->getHeaderFooter()->setOddFooter('&RСтраница &P из &N');
Три способа прочитать ячейку и почему они дают разный результат
При чтении важно понимать, какой ответ нужен приложению. getValue возвращает исходное содержимое: число, строку или текст формулы. getCalculatedValue возвращает результат вычисления. getFormattedValue добавляет представление по числовому формату, например валюту или привычную дату. Это три разных задачи, и смешивать их в одном поле импорта опасно.
Для проверки количества удобнее исходное значение и тип ячейки. Для предварительного отчёта человеку может понадобиться форматированная строка. Для контроля собственной формулы — расчёт и сравнение с ожидаемой суммой. В мастерской эти представления доступны через переключатель, чтобы различие было видно на одном и том же сохранённом файле.
Режим readDataOnly экономит часть работы со стилями, но вместе с форматами вы теряете важные подсказки, например о датах. Не включайте его автоматически, если импорт различает дату и обычное число по оформлению. Лучше описать схему столбцов заранее и принимать только тот формат файла, который действительно поддерживает ваш бизнес-процесс.
$cell = $sheet->getCell('D2');
$raw = $cell->getValue();
$calculated = $cell->getCalculatedValue();
$display = $cell->getFormattedValue();
$type = $cell->getDataType();
Отдать XLSX из PHP по HTTP без повреждённого файла
В исходниках есть отдельный download.php. Он принимает только имя разрешённого учебного сценария, создаёт новую книгу текущим PHP-кодом, сохраняет во временный файл и отдаёт её с правильными заголовками. Это уже реальная серверная генерация, которую можно запустить локально отдельно от статической мастерской.
До бинарного содержимого не должно появиться HTML, пробелов, отладочного вывода или PHP-предупреждений. Иначе скачанный XLSX окажется повреждённым. В нашем примере ошибки уходят в журнал, временный файл удаляется в finally, имя скачивания выбирается из разрешённого списка. Для неизвестного сценария возвращается 400, для неподдерживаемого метода — 405.
В личном кабинете добавьте проверку сессии и прав именно на запрошенный отчёт. Не публикуйте конфиденциальную выгрузку по угадываемому URL и не принимайте произвольный путь в query-параметре. Для крупных отчётов удобнее фоновая задача и отдельное скачивание готового файла, чем длинный запрос, который пользователь может случайно повторить.
php -S 127.0.0.1:8163 -t http
# Открыть в браузере:
# http://127.0.0.1:8163/download.php?example=formulas
Windows: скачать, запустить и изменить свой первый отчёт
Распакуйте проект в отдельный каталог и откройте PowerShell внутри spreadsheet-project. Убедитесь, что команда php запускает ожидаемую версию, а php –ini показывает нужный конфигурационный файл. После установки зависимостей команда build заново создаст все книги, CSV и JSON. Команда test проверит сохранённый результат.
Запустите локальный HTTP-сервер и откройте адрес из примера. Не открывайте index.html двойным щелчком через file://: мастерская получает JSON по HTTP, и браузер ограничивает такой доступ для локальных файлов. Встроенный сервер привязан к 127.0.0.1, поэтому используется для знакомства на вашем компьютере.
Теперь измените цену или название услуги в соответствующем методе Workshop.php, повторите build и обновите страницу. Так вы проходите весь цикл от PHP-данных до скачиваемой книги. Если меняете учебную сумму намеренно, обновите ожидаемое значение теста с объяснением; проверка должна отражать новый результат, а не быть удалена ради успешного запуска.
php -v
php --ini
composer install --no-interaction --no-plugins --no-scripts --prefer-dist
composer check-platform-reqs
composer build
composer test
php -S 127.0.0.1:8162 -t public
Linux: подготовка окружения и запуск на сервере
Для Linux последовательность та же: подходящий PHP, расширения, Composer, сборка и проверки. Ниже приведён пример пакетов Ubuntu 24.04 с PHP 8.3. На другой системе названия и доступные версии уточняйте по репозиторию дистрибутива. Не переключайте PHP уже работающего сайта только ради демонстрации; используйте отдельное окружение проекта.
Composer запускайте от обычного пользователя, которому принадлежит каталог. Не передавайте весь проект в собственность веб-серверу и не решайте проблемы прав командой chmod 777. Для CLI-генерации достаточно права записи в конкретный каталог результатов и временное хранилище. Для статической публикации готовые книги вообще могут быть доступны веб-серверу только на чтение.
Если вы работаете по SSH, локальный сервер на 127.0.0.1 удалённой машины не откроется напрямую с ноутбука. Используйте SSH-туннель, как показано в README, или полноценный тестовый виртуальный хост с HTTPS. Не выставляйте встроенный PHP-сервер в интернет вместо настройки Nginx или Caddy.
sudo apt update
sudo apt install php8.3-cli php8.3-xml php8.3-mbstring php8.3-gd php8.3-zip unzip
php -v
composer install --no-interaction --no-plugins --no-scripts --prefer-dist
composer check-platform-reqs
composer build
composer test
php -S 127.0.0.1:8162 -t public
VPS: публикуем мастерскую и отделяем рабочие отчёты
Для нашей статической мастерской на VPS достаточно разместить каталог public и настроить обычную раздачу файлов через HTTPS. PHP и Composer на публичном сервере для просмотра не требуются: книги и JSON уже подготовлены. В архиве есть пример Caddy и фрагмент Nginx для существующего виртуального хоста.
Сначала собирайте новый релиз в отдельном каталоге, проверяйте JSON и скачивание XLSX, затем переключайте сайт на готовую версию. Сохранённый предыдущий каталог позволяет быстро откатиться. Перед перезагрузкой веб-сервера проверьте конфигурацию через caddy validate или nginx -t. В примере ниже домен и путь нужно заменить своими значениями.
Рабочие персональные отчёты должны храниться иначе: вне открытого document root, с проверкой прав при выдаче и понятным сроком удаления. Открытая папка generated подходит нашим вымышленным примерам. Она не становится подходящим хранилищем клиентских данных только потому, что файл имеет сложное имя. Разделяйте учебный стенд и производственный кабинет на уровне маршрутов и доступа.
workshop.example.com {
root * /srv/spreadsheet-project/public
encode zstd gzip
header X-Content-Type-Options nosniff
file_server
}
Большие таблицы: память, время и фоновая подготовка
Размер XLSX на диске плохо отражает объём памяти, который понадобится для работы. В книге могут быть многочисленные ячейки, стили и формулы, а XML хранится внутри ZIP. Поэтому ограничение загрузки в мегабайтах полезно, но его недостаточно для безопасного импорта. Дополнительно ограничивайте строки, столбцы, время обработки и ресурсы рабочего процесса.
Измеряйте расход памяти на типичных и крупных реальных наборах. Не оформляйте целые бесконечные столбцы, если отчёт содержит несколько тысяч строк. При чтении выбирайте нужные листы и диапазоны. Если задача представляет собой простой поток строк без формул и оформления, рассмотрите CSV: более сложный формат не всегда даёт пользу получателю.
PhpSpreadsheet поддерживает внешний PSR-16 cache для ячеек; его задают до создания листов. Записи этого кеша нельзя терять по TTL во время работы, поскольку они становятся частью текущей книги. Фоновая очередь полезна для длительных экспортов, но не отменяет ограничения: задаче нужны собственные лимиты, журнал ошибки и понятный статус для пользователя.
$started = microtime(true);
// Построение и сохранение отчёта выполняется здесь.
$seconds = microtime(true) - $started;
$peakMiB = memory_get_peak_usage(true) / 1024 / 1024;
error_log(sprintf('Export: %.2f s, peak %.1f MiB', $seconds, $peakMiB));
Ограничения расчёта, Excel и совместимость приложений
У библиотеки есть собственный механизм вычисления формул, но это не весь Microsoft Excel. Сложные функции, массивы, внешние ссылки, макросы и специфические особенности книги требуют отдельной проверки. Успешное сохранение файла ещё не доказывает, что любой офисный редактор рассчитает и покажет его одинаково.
Для нашей мастерской выбраны простые контролируемые формулы: умножение, SUM, IF и межлистовые ссылки. Их результаты проверяются после повторного открытия XLSX. Если вы переносите большой корпоративный шаблон, составьте перечень используемых функций и сравните ключевые показатели с эталонной книгой в целевом приложении.
Не запускайте расчёт произвольных формул из чужого файла ради проверки числового поля. В импорте нашей схемы формулы запрещены, а проверяются исходные значения. Если бизнес-процесс действительно требует формулы, задайте отдельный разрешённый сценарий, пределы сложности и способ проверки. Решение должно быть явным, а не случайным следствием того, какой getter оказался под рукой.
Обработка ошибок и пользовательский сценарий экспорта
Человек должен понимать, что произошло на каждом этапе. При успешном запросе покажите название отчёта, период, время подготовки и кнопку скачивания. При отсутствии данных объясните, что фильтры не дали строк, и предложите изменить условия. При техническом сбое сохраните выбранные параметры и дайте безопасный повтор, не стирая всю форму.
На сервере разделяйте ошибки входных данных, отсутствие доступа, нехватку ресурсов и неудачу сохранения. В журнал полезно записать идентификатор задачи и этап обработки, но не полный состав конфиденциального отчёта. Пользователю не нужны внутренние пути, пароль базы или трассировка исключения.
В локальном HTTP-примере неизвестное имя сценария отвергается до создания книги, а временный файл удаляется независимо от результата. Для реального кабинета проверьте обрыв соединения, заполненный диск, повторный запрос и истечение сессии. Даже хороший формат Excel не исправит неудобный сценарий, если готовый файл потерялся после ожидания или пользователь не понимает, закончилась ли операция.
Как проверить книгу перед передачей пользователю
Наши тесты открывают реально сохранённые XLSX и проверяют содержимое, а не только наличие файлов. Они контролируют ведущие нули, длинный идентификатор, числовую дату, результаты формул, межлистовой итог, правила ввода, условные диапазоны, протокол импорта, текстовый тип опасных строк и настройки печати. Отдельно сверяются контрольные суммы с JSON мастерской.
Для собственного отчёта добавьте проверки бизнес-смысла: количество выгруженных записей, границы периода, валюту, отсутствие чужих клиентов и соответствие итогов базе. Полезны случаи пустого набора, одного элемента, большого объёма и необычных символов. Тест, который только утверждает «файл существует», не заметит перепутанные столбцы и ошибочный итог.
Автоматическая проверка дополняется открытием в офисной программе. Посмотрите ширину столбцов, фильтры, работу списков и предварительный просмотр печати. Браузерная мобильная проверка относится к удобству мастерской; она не заменяет проверку Excel на конкретном телефоне. Явно различайте эти границы, если отчётом будут пользоваться сотрудники в разных приложениях.
composer test
php bin/workshop.php inspect formulas
composer audit
Частые проблемы: от повреждённого файла до пропавших нулей
Если Excel сообщает о повреждении, сначала проверьте, не попали ли перед ZIP-содержимым пробелы, HTML или предупреждение PHP. Убедитесь, что обработчик действительно отдал XLSX, а не страницу авторизации с расширением .xlsx. Смотрите HTTP-статус, MIME и журнал сервера, а не только имя скачанного файла.
Если код потерял ведущие нули, проверьте тип на всех этапах: база, PHP, запись ячейки и промежуточный CSV. Если дата выглядит числом, сравните числовой формат и режим чтения. Если итог не тот, исследуйте исходные значения и диапазон формулы; формат с двумя знаками после запятой не является доказательством правильного расчёта.
Если Composer жалуется на платформу, выясните, какой PHP запускается именно в этой консоли. На сервере CLI и PHP-FPM нередко отличаются версией и набором расширений. Если локально всё работает, а веб-запрос падает, проверьте также права временного каталога, ограничения памяти и длительности. Исправление должно соответствовать причине, а не сводиться к отключению проверок.
Исходники, развитие примера и официальная документация
В ZIP лежат PHP-класс, команда сборки, lock-файл, тесты, локальный HTTP-обработчик, готовые XLSX, JSON, статическая мастерская, инструкции для Windows и Linux и примеры размещения на VPS. Каталог vendor не упакован: Composer восстанавливает зависимости по зафиксированным версиям. Собственный код можно использовать и изменять по MIT; лицензии зависимостей приложены отдельно.
Для первого практического внедрения возьмите один отчёт с понятными исходными данными. Опишите схему столбцов, проверьте права, добавьте контрольный итог и выдайте файл небольшой группе пользователей. Их реальные вопросы о заголовках, фильтрах и печати подскажут следующий шаг точнее, чем заранее созданный универсальный экспорт на все случаи.
Развивать мастерскую можно постепенно: добавить серверный выбор периода, фоновую подготовку, историю выгрузок или предварительный просмотр импорта. Сохраняйте различие между данными, правилами проверки и представлением. Тогда одна и та же бизнес-логика сможет обслуживать кабинет, плановый отчёт и консольную задачу без копирования разных несовместимых вариантов.


