FastExcelWriter – Запись данных
На этой странице
FastExcelWriter – Запись данных
Построчная и прямая запись
writeIterable(iterable $rows, ?array $rowStyle = null) принимает массивы, генераторы и
объекты Traversable. Строки обрабатываются один раз в порядке обхода; ключи итератора игнорируются.
Метод передаёт каждую строку в writeRow(), как writeRows(), без накопления входных данных.
Каждая выдаваемая строка должна быть массивом значений ячеек. Пустой итератор ничего не записывает.
$rows = (function () {
for ($i = 1; $i <= 100000; ++$i) {
yield [$i, 'Item ' . $i];
}
})();
$sheet->writeIterable($rows, ['font-size' => 11]);
Исключения итератора передаются вызывающему коду; уже обработанные строки не откатываются.
Сигнатура существующего writeRows(array $rowArray, ...) не меняется.
В библиотеке есть два способа записи в XLSX-файл — последовательный (построчный) и прямой. При построчной записи ячейки записываются в файл сразу при переходе к следующей строке. После этого записать что-либо в ячейки предыдущих строк уже нельзя. Это позволяет писать быстро и экономить память.
Примечание: все функции записи класса Sheet используют построчный способ, а функции класса Area — прямую запись
$excel = Excel::create(['Sheet1']);
$sheet = $excel->sheet();
// сейчас внутренний указатель записи находится в "A1"
$sheet->writeCell('...');
// ^^^ записываем в A1, указатель перемещается в "B1"
$sheet->writeCell('...');
// ^^^ указатель находится в "C1"
$sheet->nextCell();
// ^^^ перемещаем указатель в "D1"
$sheet->nextCell();
// ^^^ указатель находится в "E1"
// можно записать в любую ячейку текущей строки
$sheet->writeTo('G1', '...');
// ^^^ записываем значение в G1, указатель перемещается в H1
$sheet->writeTo('F1', '...');
// ^^^ можно записать и в предыдущую ячейку текущей строки
// функция writeRow() всегда сбрасывает текущую строку в файл и записывает новые значения в следующую строку
$sheet->writeRow(['a', 'b', 'c']);
// ^^^ внутренний указатель перемещается на следующую строку 2
$sheet->cell('B2')->applyBgColor('#ccc');
// ^^^ можно менять ячейки строки 2 (это текущая строка)
// но нельзя записать в предыдущую строку
$sheet->cell('B1')->applyBgColor('#ccc');
// ^^^ ОШИБКА! этот код выбросит исключение, потому что B1 находится в предыдущей строке
Можно записать двумерный массив
// Данные будут записаны в ячейки диапазона E5:I8
$data = [
['', 'Q1', 'Q2', 'Q3', 'Q4'],
['2020', 111, 222, 333, 444],
['2021', 110, 220, 330, 440],
['2022', 100, 200, 300, 400],
];
$sheet->writeArrayTo('E5', $data);
Прямая запись в ячейки
При прямой записи вы сначала объявляете область записи (или несколько областей) и пишете в неё. В этом случае можно писать в произвольные ячейки внутри области — все они хранятся в памяти и записываются в файл при вызове функций Sheet::writeAreas() или Excel::save(). Этот способ более гибкий, но требует больше памяти и не рекомендуется для больших файлов.
$excel = Excel::create();
$sheet = $excel->sheet();
// Создаём область записи от A1 до максимальной колонки и максимальной строки
$area = $sheet->beginArea();
// пишем в строку 3
$area->setValue('C3', 'text 3');
// затем в строку 2
$area->setValue('B2', 'text 2');
// затем в строку 1
$area->setValue('A1', 'text 1');
// Закрываем и записываем все области
$sheet->writeAreas();
Можно определить любое количество областей, и они могут пересекаться
// Определяем область от D3 до максимальной колонки и максимальной строки
$area1 = $sheet->beginArea('D3');
// создаём область записи от B4 до F12
$area2 = $sheet->makeArea('B4:F12');
// областей может быть сколько угодно, и они могут пересекаться
$area3 = $sheet->makeArea('C6:G18');
// левая колонка этой области — D, первая строка — 3
$area1->writeRow([100, 101, 102]);
$area2->writeRow([200, 201, 202]);
// можно записать значение в любую ячейку области...
$area1->writeTo('H3', 'text');
// ...но попытка записи в ячейку вне области выбросит исключение
$area1->writeTo('B2', 'text');
// Закрываем и записываем все области
$sheet->writeAreas();
Можно записать двумерный массив
// Определяем область от D3 до максимальной колонки и максимальной строки
$area = $sheet->beginArea('D3');
// Но данные будут записаны в ячейки от E5 до I8
$array = [
['', 'Q1', 'Q2', 'Q3', 'Q4'],
['2020', 111, 222, 333, 444],
['2021', 110, 220, 330, 440],
['2022', 100, 200, 300, 400],
];
$area->writeArrayTo('E5', $data);
Запись значений в ячейки
Обычно значения записываются последовательно — ячейка за ячейкой, строка за строкой. Запись в ячейку перемещает внутренний указатель на следующую ячейку строки, запись строки перемещает указатель на первую ячейку следующей строки.
use \avadim\FastExcelWriter\Excel;
// Создаём книгу
$excel = Excel::create();
// Получаем лист, на который будем записывать данные
$sheet = $excel->sheet();
// Записываем данные ячейка за ячейкой (первая ячейка — A1)
// Записываем число в A1, указатель перемещается на следующую ячейку (B1)
$sheet->writeCell(123);
// Записываем строку в B1 (указатель в C1)
$sheet->writeCell('abc');
// Указатель перемещается на следующую ячейку (D1) без записи значения
$sheet->nextCell();
// Записываем в B3, указатель перемещается в C3. Указатель может двигаться только слева направо и сверху вниз
$sheet->writeTo('B3', 'value');
$sheet->writeTo('A4', 'value');
// Теперь запишем значение в B4 со стилем
$style = [
'format' => '#,##0.00',
'font-color' => '#ff0000',
'text-align' => 'center',
];
$sheet->writeCell(0.9, $style);
// этот код выбросит исключение, потому что C3 находится в предыдущей строке
$sheet->writeTo('C3', 'value');
Можно записать всю строку сразу
$excel = Excel::create();
$sheet = $excel->getSheet();
// Sheet::writeHeader(array header, ?array rowStyle)
// Sheet::writeRow(array row, ?array rowStyle)
// Sheet::nextRow()
// Записываем значения заголовков в текущую строку
$sheet->writeHeader(['title1', 'title2']);
// Записываем значения заголовков в текущую строку и задаём форматы колонок A и B
$sheet->writeHeader(['title1' => '@integer', 'title2' => '@date']);
$data = [
[184, '2022-01-23'],
[835, '1971-12-08'],
[760, '1997-05-11'],
];
foreach ($data as $rowData) {
$sheet->writeRow($rowData);
}
Также можно задать левую верхнюю ячейку записи
$excel = Excel::create();
$sheet = $excel->getSheet();
// Первая строка — 3, все строки начинаются с колонки B
$sheet->setTopLeftCell('B3');
// Записываем значения заголовков в текущую строку и задаём форматы колонок
$sheet->writeHeader(['title1' => '@integer', 'title2' => '@date'])->applyFontStyleBold();
$data = [
[184, '2022-01-23'],
[835, '1971-12-08'],
[760, '1997-05-11'],
];
foreach ($data as $rowData) {
$sheet->writeRow($rowData);
}
Ссылки в стиле R1C1
По умолчанию библиотека распознаёт адреса в стиле R1C1. Но это поведение можно изменить.
$sheet = $excel->sheet();
// Текущая строка и колонка + 1 => B1
$sheet->writeTo('RC1', 'TEST 1');
// Можно отключить распознавание адресов R1C1
$excel->setR1C1(false);
// Значение будет записано в ячейку RC1 (колонка RC, строка 1)
$sheet->writeTo('RC1', 'TEST 2');
Объединение ячеек
// Объединяем C4:E4, записываем значение в объединённые ячейки
$sheet->writeTo('C4:E4', 'other value');
// Записываем значение в ячейку
$sheet->writeTo('D1', 'Title');
$sheet->writeRow(['...']);
$sheet->writeRow(['...']);
$sheet->writeRow(['...']);
// Объединяем диапазон ячеек
$sheet->mergeCells('D1:F1');
Примечание: функция mergeCells() не записывает значения и стили, а задаёт свойства листа, поэтому её можно вызывать для предыдущих строк, когда все данные уже записаны.
При каждом вызове mergeCells() выполняется проверка, не пересекается ли указанный диапазон с другими объединёнными ячейками. Если генерируемый файл большой и объединяемых ячеек много, это может замедлить генерацию файла. Если вы уверены, что объединяемые ячейки не пересекаются, проверку можно отключить, передав -1 вторым аргументом.
$sheet->mergeCells('D1:F1', -1);
$sheet->mergeCells('D2:F2', -1);
$sheet->mergeCells('D3:F3', -1);
Форматы ячеек
Можно использовать простые и расширенные форматы. Пример задания формата для каждой ячейки:
$excel = Excel::create(['Formats']);
$sheet = $excel->sheet();
$sheet->writeCell(123456); // значение 123456 как целое число (по умолчанию)
$sheet->writeCell('123456'); // значение '123456' как строка (по умолчанию)
$sheet->writeCell(12.34); // число с плавающей точкой 12.34
$sheet->writeCell(12.34, ['format' => '@money']); // денежный формат
$sheet->writeCell(date('Y-m-d'), ['format' => '@date']); // формат даты
$sheet->writeCell(time(), ['format' => '@date']); // формат даты
Формат можно задать и для всех значений колонки.
$sheet = $excel->sheet();
// определяем имена и форматы колонок
$header = [
'created' => '@date',
'product_id' => '@integer',
'quantity' => '#,##0',
'amount' => '#,##0.00',
'description' => '@string',
'tax' => '[$$]#,##0.00;[RED]-[$$]#,##0.00',
];
$data = [
['2015-01-01', 873, 1, 44.00, 'misc', '=D2*0.05'],
['2015-01-12', 324, 2, 88.00, 'none', '=D3*0.15'],
];
$sheet->writeHeader($header);
foreach($data as $row) {
$sheet->writeRow($row );
}
$excel->save('formats.xlsx');
Простые форматы ячеек соответствуют следующим кодам форматов
| простой формат | код формата |
|---|---|
| @text | @ |
| @string | @ |
| @integer | 0 |
| @date | YYYY-MM-DD |
| @datetime | YYYY-MM-DD HH:MM:SS |
| @time | HH:MM:SS |
| @money | [$$]#,##0.00 |
Формулы
Формулы должны начинаться с '='. Если вы хотите записать формулу как текст, используйте обратный слеш. Установка локали позволяет использовать имена функций на национальном языке. В формулах можно использовать обе нотации — A1 и R1C1.
Необходимо соблюдать следующие правила:
- Формулы должны начинаться с '='
- Десятичный разделитель в числах с плавающей точкой — '.'
- Разделитель аргументов функций — ','
- Разделитель строк матрицы — ';'
use \avadim\FastExcelWriter\Excel;
$excel = Excel::create(['Formulas']);
$sheet = $excel->getSheet();
// Устанавливаем русскую локаль
$excel->setLocale('ru');
$headRow = [];
$sheet->writeRow([1, random_int(100, 999), '=RC[-1]*0.1']);
$sheet->writeRow([2, random_int(100, 999), '=RC[-1]*0.1']);
$sheet->writeRow([3, random_int(100, 999), '=RC[-1]*0.1']);
$totalRow = [
'Total',
'=SUM(B1:B3)', // английское имя функции
'=СУММ(C1:C3)', // можно использовать русское имя функции, потому что локаль 'ru'
];
$sheet->writeRow($totalRow);
$excel->save('formulas.xlsx');
Установка локали позволяет использовать имена функций на национальном языке.
$excel = Excel::create();
// Устанавливаем португальскую локаль
$excel->setLocale('pt');
$sheet = $excel->getSheet();
$sheet->writeTo('A1', '=SE(FALSO,1.23+A4,4.56+B3)');
Можно определить формулу для указанной колонки
$sheet->setColFormula('C', '=RC[-1]*0.1');
// Записываем значения только в колонки 'A' и 'B', формула в 'C' будет добавлена автоматически
$sheet->writeRow([100, 230]);
$sheet->writeRow([120, 560]);
$sheet->writeRow([130, 117]);
Важно! Библиотека не умеет предварительно вычислять значения формул. Она сохраняет формулы как есть, без вычисления. Но когда сохранённый файл открывается в Excel, тот пересчитывает все ячейки с формулами и показывает результаты. Есть только один способ сохранить вычисленные значения: выполнить вычисления в своём коде и сохранить их вместе с формулой
$a1 = 10;
$b1 = 30;
$a2 = 50;
$b2 = 70;
$sheet->writeRow([$a1, $b1]);
$sheet->writeRow([$a2, $b2]);
// формула и заранее вычисленный результат
$a3 = ['=A1+A2', $a1 + $a2];
$b3 = ['=B1+B2', $b1 + $b2];
$sheet->writeRow([$a3, $b3]);
Заранее вычисленным результатом может быть число, строка, логическое значение или значение ошибки
$sheet->writeRow([
['=IF(A1>0, "PASS", "FAIL")', 'PASS'], // строка
['=A1=A2', false], // логическое значение
['=A1/0', '#DIV/0!'], // ошибка
]);
Гиперссылки
URL можно вставлять как активные гиперссылки
// Записываем URL как обычную строку (не гиперссылку)
$sheet->writeCell('https://google.com');
// Записываем URL как активную гиперссылку
$sheet->writeCell('https://google.com', ['hyperlink' => true]);
// Записываем текст с активной гиперссылкой
$sheet->writeCell('Google', ['hyperlink' => 'https://google.com']);
Запись гиперссылки с помощью writeRow()
$rowValues = [
'text',
'http://google.com',
123456,
];
$rowStyle = [];
$cellStyles = [
[], // стиль первой ячейки,
['hyperlink' => true], // второй ячейки
[], // третьей ячейки
];
$sheet->writeRow($rowValues, $rowStyle, $cellStyles);
Вот другой способ
$cellStyles = [
'B' => ['hyperlink' => true],
];
Внутренние гиперссылки на другие листы
$sheet->writeCell('Internal link', ['hyperlink' => "'Sheet 1'!C7"]);
// Если имя листа не содержит пробелов, его можно писать без кавычек.
$sheet->writeCell('Internal link', ['hyperlink' => "Sheet1!C7"]);
Внешние гиперссылки на другую книгу
// Кавычки обязательны, даже если имена файла и листа не содержат пробелов.
$sheet->writeCell('Workbook link', ['hyperlink' => "'[other_file.xlsx]Sheet1'!C7"]);
Для совместимости с phpSpreadsheet можно использовать такой синтаксис
// если имя листа не содержит пробелов
$sheet->writeCell('Internal link', ['hyperlink' => "sheet://Sheet1!C7"]);
// Если имя листа с пробелами, нужны кавычки
$sheet->writeCell('Internal link', ['hyperlink' => "sheet://'Sheet 1'!C7"]);
Форматированный текст (Rich Text)
Записать форматированный текст в ячейку можно с помощью экземпляров RichText. Вот пример, который создаёт следующую форматированную строку:

$richText = new \avadim\FastExcelWriter\RichText\RichText();
$richText->addText('ATTENTION!')->setBold();
$richText->addText(' The product is reserved for ');
$richText->addText('5 days')->setUnderline()->setColor('red');
$sheet->writeCell($richText);
$sheet->writeTo('B2', $richText);
$sheet->writeRow(['plain text', $richText]);
Для форматирования фрагментов можно использовать следующие функции:
- setBold()
- setItalic()
- setUnderline()
- setFont($fontName)
- setSize($size)
- setColor($color)
Вот другой способ сделать то же самое
$richText = new RichText('ATTENTION! ', 'The product is reserved for ', '5 days');
$richText->fragment(0)->setBold();
$richText->fragment(2)->setUnderline()->setColor('f00');
$sheet->writeCell($richText);
Также для форматирования можно использовать простые теги
$richText = new \avadim\FastExcelWriter\RichText\RichText('<b>ATTENTION!</b> The product is reserved for <u><c=red>5 days</c></u>');
$sheet->writeTo('B2', $richText);
Можно использовать следующие теги:
| короткий тег | полный тег | пример |
|---|---|---|
| <b> | <bold> | <b>bold text</b> |
| <i> | <italic> | <i>italic text</i> |
| <u> | <underline> | <u>underline text</u> |
| <f=fontName> | <font=fontName> | <f="Times New Roman">text<f> |
| <s=fontSize> | <size=fontSize> | <s=18>text with size 18</s> |
| <c=fontColor> | <color=fontColor> | <c="#f0b5d4">colored text</c> |
Нижние и верхние индексы
addText() и RichTextFragment принимают буквальный текст без предварительного экранирования.
Библиотека экранирует специальные символы XML и кодирует управляющие символы XLSX при сохранении.
В setFont() также передаётся буквальное имя шрифта, в том числе с кавычками и амперсандами.
Конструктор и addTaggedText() обрабатывают поддерживаемые теги и декодируют XML-сущности один раз.
Для отображения буквального <b> в размеченном тексте используйте <b>; неизвестные теги
сохраняются как текст. Произвольный ввод, который нельзя интерпретировать как разметку,
передавайте через addText().
$text = new RichText();
$text->addText('A & B, x < y')->setFont('Arial')->setSize(10.5);
$text->addText(' regular')->setBold(false)->setItalic(false)->setStrike(false)->removeUnderline();
$text->addTaggedText(' <b>A & B</b>');
setBold($enabled = true), setItalic($enabled = true) и setStrike($enabled = true)
явно включают или выключают соответствующее начертание. removeUnderline() явно снимает
подчёркивание. Эти настройки переопределяют унаследованные свойства шрифта и доступны у
RichText и RichTextFragment. setSize(float $size) принимает положительные конечные значения,
включая дробные размеры в пунктах; также поддерживаются <s=10.5> и <size=10.5>.
Для зачёркивания используйте $text->addText('old')->setStrike() или <strike>old</strike> /
<del>old</del>. Эти теги поддерживают вложенность и сочетание с остальным форматированием.
setUnderline() и setUnderline(false) задают одинарное подчёркивание, setUnderline(true) —
двойное. Это относится и к RichText, и к RichTextFragment.
Теги <sub> и <sup> позволяют оформить часть текста ячейки как нижний или верхний индекс:
$sheet->writeRow([
new RichText('H<sub>2</sub>O'),
new RichText('m<sup>2</sup>'),
]);
Методы setSubscript(), setSuperscript() и setBaseline() доступны у RichText
(применяются к последнему добавленному фрагменту) и у RichTextFragment:
$text = new RichText();
$text->addText('H');
$text->addText('2')->setSubscript();
$text->addText('O');
$sheet->writeCell($text);
$text->fragment(1)->setSuperscript(); // Replace subscript with superscript
$text->fragment(1)->setBaseline(); // Restore normal text positioning
Новый обычный фрагмент создаётся без настройки индекса. Закрытие </sub> или </sup>
восстанавливает предыдущую настройку, в том числе при вложенных тегах. Индексы можно сочетать
с жирным начертанием, курсивом, цветом и размером шрифта. Размер и положение индекса определяет
Excel; вручную уменьшать шрифт не требуется. Изменения фрагментов учитываются при последующей
сериализации; уже записанные ячейки не обновляются.
Потоковый режим и память
Построчная запись — потоковая: как только вы переходите к следующей строке, текущая сбрасывается во временный файл листа. Поэтому память не зависит от количества строк — в замере между 50 000 и 500 000 записанных строк роста не было вообще.
Всё, что нельзя сбросить вместе со строкой, остаётся в памяти до Excel::save():
| хранится в памяти до сохранения | примерно | примечания |
|---|---|---|
ячейки Area |
0,3 КБ на ячейку | сбрасываются в Sheet::writeAreas() или Excel::save() |
| объединения ячеек | 0,5 КБ на объединение | |
| гиперссылки | 1 КБ на ссылку | не более 65 530 на лист (Sheet::MAX_HYPERLINKS) |
| заметки | 1 КБ на заметку | |
| правила проверки данных | 0,6 КБ на правило | одно правило может покрывать целый диапазон |
| правила условного форматирования | 0,5 КБ на правило | одно правило может покрывать целый диапазон |
| таблица общих строк | 0,5 КБ на уникальную строку | только при 'shared_string' => true |
| диаграммы | всё описание диаграммы | значения, переданные массивом, тоже хранятся |
Исключение — изображения: содержимое картинки сразу пишется во временный файл, в памяти остаются только её размеры и хеш, а одинаковые картинки попадают в файл лишь один раз.
Поэтому обычная выгрузка на миллионы строк почти ничего не стоит, а та же выгрузка, где каждая строка получает ещё и заметку, гиперссылку или собственное объединение, растёт линейно. Если упёрлись в лимит памяти, смотрите сначала в эту колонку: как правило, одно правило на диапазон заменяет тысячи правил на отдельные ячейки.
$conditional = Conditional::greaterThan(100, ['fill-color' => '#FFC7CE']);
// 100 000 правил в памяти
foreach ($rows as $i => $row) {
$sheet->writeRow($row);
$sheet->addConditionalFormatting('C' . ($i + 1), $conditional);
}
// тот же результат, одно правило
$sheet->addConditionalFormatting('C1:C100000', $conditional);
Когда нужна область (Area)
Построчная запись идёт только вперёд — уже сброшенную строку изменить нельзя. Используйте
Sheet::beginArea() / Sheet::makeArea(), когда нужно
- записать значение в ячейку уже записанной строки;
- применить стиль к ячейкам предыдущих строк;
- заполнять ячейки в произвольном порядке (например, собирать оглавление по ходу записи данных).
Ячейки области живут в памяти до её сброса, поэтому делайте области настолько маленькими, насколько позволяет задача — блок шапки или итоговая таблица, а не весь лист.
Ограничения Excel
Некоторые ограничения формата обойти нельзя — файл, который их нарушает, Excel не открывает:
| ограничение | значение | поведение библиотеки |
|---|---|---|
| символов в ячейке | 32 767 | более длинное значение обрезается — так же поступает сам Excel при вводе |
| гиперссылок на листе | 65 530 | следующая ссылка вызывает исключение (Sheet::MAX_HYPERLINKS) |
| значащих цифр числа | 15 | при auto_convert_number более длинная числовая строка остаётся текстом |
| строк / столбцов листа | 1 048 576 / XFD | адрес за их пределами отклоняется |