-
Notifications
You must be signed in to change notification settings - Fork 0
Tables
Page::table() renders a data grid from an array (or any iterable) of associative rows. Columns are defined with Column value objects that describe which row key to read, a header label, a width policy, and optional alignment overrides. The renderer reuses the cell and image pipeline, supports automatic page-break with repeated header rows, zebra striping, configurable borders, and per-cell conditional styling.
use DragonOfMercy\PhpPdf\Color;
use DragonOfMercy\PhpPdf\Document;
use DragonOfMercy\PhpPdf\Image;
use DragonOfMercy\PhpPdf\Table\Cell;
use DragonOfMercy\PhpPdf\Table\CellStyle;
use DragonOfMercy\PhpPdf\Table\Column;
use DragonOfMercy\PhpPdf\Table\TableBorders;
use DragonOfMercy\PhpPdf\Table\TableStyle;
use DragonOfMercy\PhpPdf\TextAlign;
$doc = new Document();
$doc->setAutoPageBreak(true);
$page = $doc->addPage();
$page->setFont($doc->getFont('Helvetica'), 10);
// One fill column takes the remaining width; two fixed columns get exact mm.
$columns = [
Column::of('avatar', '')->width(14)->align(TextAlign::CENTER),
Column::of('name', 'Name')->fill(),
Column::of('role', 'Role')->width(40),
Column::of('salary', 'Salary')->width(32)->align(TextAlign::RIGHT),
];
// Scalars are shorthand for Cell::of($value). Cell::image() embeds a raster or SVG image.
$rows = [
['avatar' => Cell::image(Image::fromFile('avatar1.png'), w: 10), 'name' => 'Alice Martin', 'role' => 'Engineer', 'salary' => '95,000'],
['avatar' => Cell::image(Image::fromFile('avatar2.png'), w: 10), 'name' => 'Bob Nguyen', 'role' => 'Designer', 'salary' => '82,000'],
['avatar' => Cell::image(Image::fromFile('avatar3.png'), w: 10), 'name' => 'Carol Schmidt', 'role' => 'Manager', 'salary' => '110,000'],
];
$style = TableStyle::default()
->withHeader(fill: Color::gray(220), bold: true)
->withBorder(TableBorders::GRID)
->withZebra(Color::rgb(255, 255, 255), Color::gray(247))
->withCellStyle(function (mixed $value, array $row, Column $col): ?CellStyle {
if ($col->key === 'salary' && is_string($value) && (int) str_replace(',', '', $value) > 100000) {
return CellStyle::new()->withTextColor(Color::rgb(180, 0, 0))->withBold(true);
}
return null;
});
$result = $page->table($columns, $rows, x: 15, y: 30, width: 180, style: $style);
// $result->rowCount -- number of data rows rendered
// $result->pageCount -- number of pages the table spans
// $result->page -- last Page instance used (useful for further drawing)
// $result->x / ->y -- cursor position after the last row| Parameter | Type | Description |
|---|---|---|
$columns |
Column[] |
Column definitions (at least one required). |
$rows |
iterable |
Associative arrays. Scalar values become Cell::of() automatically. |
x |
float |
Left edge in the document unit (mm by default). |
y |
float |
Top edge in the document unit. |
width |
float |
Total table width. Fixed columns are subtracted first; fill columns share the remainder. |
style |
TableStyle |
Presentation and pagination config. Optional; defaults to TableStyle::default(). |
ln |
NextPosition |
Cursor advance after the table. Defaults to BELOW (cursor moves to the table's left edge, just under the last row) so content flows beneath it. Pass NONE to leave the cursor untouched. |
The method returns a TableResult with public properties rowCount, pageCount, page, x, and y. Regardless of ln, TableResult::$y always reports the position just below the last row, so you can anchor a totals block there explicitly.
Column::of(string $key, ?string $header = null) is the named constructor. Chain the following methods (each returns a new immutable Column):
| Method | Description |
|---|---|
->width(float $mm) |
Fixed width in the document unit. Mutually exclusive with ->fill(). |
->fill(int $weight = 1) |
Fill policy: takes a proportional share of the remaining width. Multiple fill columns divide by weight. Mutually exclusive with ->width(). |
->align(TextAlign $align) |
Default horizontal alignment for data cells in this column (LEFT, CENTER, RIGHT, JUSTIFY). JUSTIFY fills wrapped cells to the column width, leaving each cell's last line ragged - see Justified text. |
->verticalAlign(VerticalAlign $v) |
Default vertical alignment for data cells. |
->padding(CellPadding $p) |
Default padding override for every cell in this column. |
The $key property is public and readable in callbacks (e.g. $col->key === 'salary').
A row value may be:
-
Scalar - converted implicitly to
Cell::of((string) $value). -
Cell::of(string|\Stringable $text)- text cell with optional style overrides:->bold(),->align(),->verticalAlign(),->textColor(),->fill(),->padding(). -
Cell::image(Image|string $src, ?float $w = null, ?float $h = null)- image cell. Aspect ratio is preserved and the image is clamped to the inner cell box. Defaults toTextAlign::CENTER/VerticalAlign::MIDDLE. A plain path string is accepted as$src(equivalent toImage::fromFile($path)).
Cell::of('x')->colSpan($n) makes a data cell span $n adjacent columns to its
right. The merged cell is drawn once at the summed width of the covered columns;
its border and fill cover the whole span, with no internal column edges. Any
value present under a covered column's key in that row is ignored. Spanning never
changes pagination (the table still breaks row by row). $n must be >= 1, and
the span may not run past the last column (both throw PdfException).
$page->table(
columns: [
Column::of('a', 'Q1')->width(40)->align(TextAlign::RIGHT),
Column::of('b', 'Q2')->width(40)->align(TextAlign::RIGHT),
Column::of('c', 'Q3')->width(40)->align(TextAlign::RIGHT),
],
rows: [
['a' => '100', 'b' => '120', 'c' => '90'],
['a' => '110', 'b' => '130', 'c' => '95'],
['a' => Cell::of('Total year: 870')->colSpan(3)->bold()->align(TextAlign::CENTER)],
],
x: 20, y: 30, width: 120,
);TableStyle::withColumnGroups(ColumnGroup ...$groups) adds a band of group labels
above the per-column header row. Each ColumnGroup::of($label, $span) spans
$span columns; ColumnGroup::spacer($span = 1) is an unlabeled group that lets
the column header beneath it rise to fill both bands (use it for a standalone
leading column such as "Name"). The groups must cover every column exactly (the
sum of their spans must equal the column count, else PdfException). A group cell
inherits the header style and can override it with ->fill(), ->textColor(),
->bold(), and ->align(). The band repeats with the header on each continuation
page when withRepeatHeader(true) (the default) is set.
use DragonOfMercy\PhpPdf\Table\ColumnGroup;
$style = TableStyle::default()
->withHeader(fill: Color::gray(238), bold: true)
->withColumnGroups(
ColumnGroup::spacer(), // the "Name" column rises across both bands
ColumnGroup::of('Q1', 3)->fill(Color::gray(220)),
ColumnGroup::of('Q2', 3)->fill(Color::gray(220)),
);
// columns: name (40), jan, feb, mar, apr, may, jun (20 each) -> spacer(1) + 3 + 3 = 7 columnsBuild from TableStyle::default() and chain with* methods. All methods return a new immutable TableStyle.
| Method | Description |
|---|---|
->withHeader(?Color $fill, bool $bold, ?Color $textColor) |
Header row appearance. |
->withBorder(TableBorders $borders) |
Border preset (see below). |
->withBorderStyle(Border $b) |
Line appearance (width, color, style) applied to all drawn borders. |
->withZebra(Color $even, Color $odd) |
Alternate row fills. Even rows (0, 2, ...) use $even; odd rows use $odd. |
->withRepeatHeader(bool $repeat) |
Whether the header row is re-drawn at the top of each continuation page. Default: true. |
->withCellStyle(callable $fn) |
Per-cell conditional styling callback (see below). |
->withRowPadding(CellPadding $p) |
Default padding applied to every data cell. |
| Case | Effect |
|---|---|
GRID |
All cell edges drawn (default). |
HORIZONTAL |
Horizontal rules between rows only. |
HEADER_UNDERLINE |
Single rule under the header row only. |
NONE |
No borders. |
The callable receives (mixed $value, array $row, Column $col) and must return CellStyle|null. A non-null return overrides style for that cell only:
->withCellStyle(function (mixed $value, array $row, Column $col): ?CellStyle {
if ($col->key === 'status' && $value === 'overdue') {
return CellStyle::new()->withFill(Color::rgb(255, 220, 220))->withBold(true);
}
return null;
})CellStyle::new() is immutable and supports ->withTextColor(Color), ->withFill(Color), ->withBold(bool), and ->withAlign(TextAlign).
When Document::setAutoPageBreak(true) is active, a new page is started automatically whenever a row does not fit the remaining space. The header row is re-drawn on the new page unless ->withRepeatHeader(false) is set.
- No vertical cell spanning:
rowspanis not supported (horizontalcolSpanand grouped headers are - see above). - No content-derived auto-width: every column must have an explicit
->width()or->fill()policy.
MIT licensed. Source on GitHub - if phppdf helps you, you can buy me a coffee.