Skip to content
Dragon edited this page Jun 4, 2026 · 4 revisions

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.

Basic usage

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

Page::table() parameters

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

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').

Cell types

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 to TextAlign::CENTER / VerticalAlign::MIDDLE. A plain path string is accepted as $src (equivalent to Image::fromFile($path)).

TableStyle

Build 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.

TableBorders

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.

Conditional cell styling (withCellStyle)

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).

Automatic page-break and header repeat

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.

Limitations

  • No cell spanning: colspan and rowspan are not supported.
  • No content-derived auto-width: every column must have an explicit ->width() or ->fill() policy.

See also

Clone this wiki locally