Заполнение бланка счёта из XLSX-шаблона: обзор FastExcelTemplator

aVadim 11.08.2026 00:00

Знакомая задача: нужно сгенерировать счёт, акт или накладную. И не «табличку с данными», а именно фирменный бланк — с логотипом в углу, объединёнными ячейками шапки, рамками нужной толщины, денежным форматом с разделителями разрядов и формулой суммы внизу. Бухгалтер такой бланк уже сделал в Excel за пять минут. А теперь его просят «то же самое, но чтобы генерировалось из базы».

Почему наивные подходы буксуют

Первое, что приходит в голову джуну, — взять PhpSpreadsheet и собрать документ ячейка за ячейкой в коде. И тут начинается боль:

php
$sheet->getStyle('A1:F1')->getFont()->setBold(true)->setSize(14);
$sheet->getStyle('A5:F5')->getBorders()->getAllBorders()
    ->setBorderStyle(Border::BORDER_THIN);
$sheet->getStyle('E10')->getNumberFormat()->setFormatCode('#,##0.00');
$sheet->mergeCells('A1:D1');
// ...и так ещё сто строк, чтобы повторить то, что уже нарисовано в файле

Вы вручную переписываете кодом оформление, которое уже существует в готовом файле. Любая правка бланка («сделайте шапку синей», «добавьте колонку НДС») превращается в поиск нужных строк в PHP. А если файл большой — PhpSpreadsheet загружает всю книгу в объектную модель и легко упирается в память на десятках тысяч строк.

Второй подход — «открыть файл-образец и подменить в нём значения» — в PhpSpreadsheet тоже возможен, но это снова полная загрузка модели в память со всеми вытекающими.

Идея FastExcelTemplator: шаблон — это обычный XLSX

FastExcelTemplator переворачивает задачу. Оформление остаётся там, где ему место, — в самом файле Excel. Дизайнер или бухгалтер верстает бланк руками, расставляет плейсхолдеры вроде {{COMPANY}}, а код только подставляет данные.

Технически библиотека потоково читает шаблон XML-ридером и тут же переписывает его XML-райтером, по пути заменяя значения ячеек и вставляя строки данных. Она не загружает лист в объектную модель — идёт по нему сверху вниз. Отсюда низкое потребление памяти и высокая скорость даже на больших выгрузках.

Всё в статье проверено на версии 2.4.0.

Разбираем на счёте

Шаг 0. Готовим шаблон

В Excel рисуем бланк как нам нравится и оставляем плейсхолдеры:

  • в шапке — {{COMPANY}}, {{ADDRESS}}, Счёт от {{DATE}};
  • строка 7 — образец строки позиции: колонки A (номер), B (наименование), C (кол-во), D (цена), E (сумма). В ячейке E7 уже стоит формула =C7*D7;
  • внизу — {{TOTAL}} для итога.

Файл со всеми стилями, логотипом и рамками сохраняем как invoice-tpl.xlsx.

Шаг 1. Открываем шаблон и задаём замены

php
use avadim\FastExcelTemplator\Excel;

$excel = Excel::template('invoice-tpl.xlsx', 'invoice-out.xlsx');
$sheet = $excel->sheet();

// fill() — замена ЦЕЛОЙ ячейки: сработает, только если всё её
// значение равно ключу (ячейка целиком '{{COMPANY}}')
$sheet->fill([
    '{{COMPANY}}' => 'ООО «Ромашка»',
    '{{ADDRESS}}' => 'Москва, ул. Садовая, 1',
]);

// replace() — замена ПОДСТРОКИ: сработает даже внутри текста
// (ячейка 'Счёт от {{DATE}}' превратится в 'Счёт от 25.07.2026')
$sheet->replace([
    '{{DATE}}'  => date('d.m.Y'),
]);

Разница между fill() и replace() — самые частые грабли новичка, поэтому запомните сразу:

  • fill() заменяет значение, только если вся ячейка равна ключу. {{COMPANY}} — заменит; Компания: {{COMPANY}} — не тронет.
  • replace() ищет подстроку в любом месте текста ячейки.

Обе замены применяются ко всем ячейкам, которые библиотека пишет в вывод, — и к переносимым из шаблона, и к вставляемым.

Шаг 2. Переносим шапку

Копируем верх бланка (строки 1–6) из шаблона в вывод как есть:

php
$sheet->transferRowsUntil(6);

Шаг 3. Размножаем строки таблицы

Берём строку 7 как образец и вставляем по ней столько строк, сколько у нас позиций:

php
$rowTemplate = $sheet->getRowTemplate(7);

foreach ($positions as $item) {
    $sheet->insertRow($rowTemplate, [
        'A' => $item['num'],
        'B' => $item['name'],
        'C' => $item['qty'],
        'D' => $item['price'],
        // колонку E не трогаем — формула из шаблона встанет сама
    ]);
}

Ключи массива — это буквы колонок. Каждая вставленная строка получает стили, формат чисел и объединённые ячейки из строки-образца.

Отдельно про формулу. В шаблоне в E7 стоит =C7*D7. При захвате строки-образца библиотека переводит формулу в относительную (RC) запись, поэтому при вставке она пересчитывается на номер целевой строки: в первой позиции получится =C7*D7, в следующей — =C8*D8 и так далее. Вам не нужно генерировать формулы строками в PHP — достаточно один раз поставить её в шаблоне.

Шаг 4. Итог, остаток бланка и сохранение

php
// Итоговую сумму подставим как обычную замену целой ячейки
$sheet->fill(['{{TOTAL}}' => array_sum(array_column($positions, 'total'))]);

// Переносим оставшиеся строки шаблона (подвал, реквизиты, подпись)
$sheet->transferRows();

$excel->save();

Готово. На выходе — тот же бланк, что нарисовал бухгалтер, но с данными из базы.

Что переносится «бесплатно»

Главная ценность подхода в том, что всё, чего вы не трогали, остаётся нетронутым. Библиотека сохраняет копию исходного XLSX и вставляет в неё только перезаписанные листы, поэтому в выводе уцелевают:

  • стили ячеек — шрифты, цвета, заливки, рамки, форматы чисел и дат;
  • объединённые ячейки;
  • изображения (логотип) и примечания;
  • параметры печати, автофильтр, закрепление областей.

Всё это — без единой строчки кода про оформление.

Не только бланки: правка существующих файлов

Тот же движок умеет не вставлять, а модифицировать строки на лету. Метод rows() читает каждую строку, отдаёт её в колбэк и пишет результат:

php
$sheet->rows(function ($sourceRowNum, $targetRowNum, $rowData) {
    // пропустить строку заголовка
    if ($sourceRowNum === 1) {
        return null;             // null — пропустить строку
    }
    if ($rowData->getValue('A') === 'STOP') {
        return false;            // false — прекратить обработку
    }
    // изменить значение ячейки
    $rowData->setValue('C', $rowData->getValue('C') * 1.2);
    return $rowData;             // вернуть изменённую строку
});
$excel->save();

В колбэке доступны и более тонкие операции над строкой: appendCell() — добавить ячейку в конец (со стилями соседа), cloneCell('A', 'E') — скопировать ячейку в другую колонку, removeCells(['B', 'D']) — убрать лишние столбцы.

Отдать файл в браузер

Кроме save() в файл, готовый документ можно сразу отправить пользователю на скачивание или в поток вывода:

php
$excel->download('Счёт №128.xlsx');  // заголовки + отдача файла в браузер
// output() — псевдоним download() с тем же поведением
$excel->output('Счёт №128.xlsx');

Честные границы

FastExcelTemplator — инструмент под конкретный класс задач, и важно понимать, чего он не делает:

  • Только формат XLSX (Office 2007+). Шаблон в старом бинарном .xls не подойдёт — весь механизм построен вокруг структуры XLSX (ZIP из XML-частей). Если исходник в .xls, сначала пересохраните его в .xlsx.
  • Движение только вперёд. Библиотека идёт по листу сверху вниз и пишет вывод потоково. Вернуться и поправить уже записанную строку нельзя — это цена низкого потребления памяти.
  • Формулы библиотека не вычисляет. Она записывает формулу как текст (и корректно её перебазирует), а сам результат посчитает Excel при открытии файла. В самом XLSX кэшированного значения формулы не будет, пока его не откроют.
  • Нужен готовый шаблон. Это шаблонизатор: чтобы что-то подставить, в файле должны быть плейсхолдеры, а чтобы размножить строку — строка-образец. Строить таблицу с нуля целиком из кода — это задача для родственного fast-excel-writer.
  • Это не про чтение данных. Если нужно вытащить данные из чужого Excel, а не сгенерировать свой, смотрите в сторону fast-excel-reader.

Кому это подходит

FastExcelTemplator — ваш инструмент, если:

  • документ имеет фиксированный фирменный вид, который проще нарисовать в Excel, чем описывать кодом: счета, акты, накладные, КП, договоры-приложения;
  • в таблице есть повторяющиеся строки с одинаковым оформлением: реестры, спецификации, ведомости;
  • нужно массово генерировать однотипные файлы из одного бланка (тысячи счетов в цикле) при скромной памяти;
  • оформлением владеет не программист (бухгалтер, аналитик), и хочется, чтобы правки бланка не требовали релиза кода.

Если же вам нужно построить произвольную таблицу целиком из данных — берите fast-excel-writer, а для чтения — fast-excel-reader. Вместе они закрывают весь цикл работы с Excel в PHP.

Комментарии (0)