Column Summaries
Show totals, averages, minimums, maximums, and counts in the table footer
A column can show a figure in the table footer with summarize(). The summary covers every row
matching the current search and filters, not only the visible page.
Declaring Summaries
use App\Entity\Order;
use Pentiminax\UX\DataTables\Attribute\AsDataTable;
use Pentiminax\UX\DataTables\Column\DateColumn;
use Pentiminax\UX\DataTables\Column\MoneyColumn;
use Pentiminax\UX\DataTables\Column\NumberColumn;
use Pentiminax\UX\DataTables\Column\TextColumn;
use Pentiminax\UX\DataTables\Model\AbstractDataTable;
use Pentiminax\UX\DataTables\Model\DataTable;
use Pentiminax\UX\DataTables\Model\Summary\Average;
use Pentiminax\UX\DataTables\Model\Summary\Count;
use Pentiminax\UX\DataTables\Model\Summary\Max;
use Pentiminax\UX\DataTables\Model\Summary\Min;
use Pentiminax\UX\DataTables\Model\Summary\Sum;
#[AsDataTable(Order::class)]
final class OrdersDataTable extends AbstractDataTable
{
public function configureDataTable(DataTable $table): DataTable
{
return $table->serverSide();
}
public function configureColumns(): iterable
{
yield TextColumn::new('reference', 'Reference')->summarize(Count::new('Orders'));
yield MoneyColumn::new('total', 'Total')
->currency('EUR')
->summarize(Sum::new('Revenue'), Average::new());
yield NumberColumn::new('items', 'Items')->summarize(Min::new('Smallest'), Max::new('Largest'));
yield DateColumn::new('createdAt', 'Created')->summarize(Count::new('Dated'));
}
}
| Summarizer | Result | Column |
|---|---|---|
Sum | Total of the values | Numeric |
Average | Mean of the values | Numeric |
Min / Max | Smallest and largest value | Numeric |
Count | Number of non-null values (empty strings included) | Any |
Each summarizer takes an optional label (Sum::new('Revenue')). Without one, the translated default
is used (summary.sum, summary.avg, and so on, in the DataTables domain). A column holds one
summarizer per type.
Null values are ignored, as SQL does. MoneyColumn figures are formatted like the cells
of the column. summarize() throws an InvalidArgumentException for a TemplateColumn or
ActionColumn, and for Sum, Average, Min or Max on a non-numeric column (dates included:
they are formatted differently in SQL, PHP and the browser, so only Count applies).
How It Is Computed
- Server-side Doctrine tables run one extra aggregate query built from the same query as the
rows (
customizeQueryBuilder()scope, search, and filters) without pagination or ordering. The response gains asummarieskey only when a column declares a summary. The query matches the root entities first, so a to-many join or a search on a collection never inflates a total. - Client-side tables compute in the browser from the rows matching the current search, so a
virtual column works there. The browser reads the displayed values: rendering throws a
LogicExceptionwhen a column combinesformatValueUsing()withSum,Average,Min, orMax, because"1,234.50"is not a number. Make the table server-side or keepCountonly. ArrayDataProvideraggregates in memory.- API Platform tables cannot aggregate: rendering throws a
LogicExceptionwhen a column declares a summary. - Custom providers return the figures through the
summariesargument ofDataTableResult, shaped['columnName' => ['sum' => 120, 'count' => 4]].
On a server-side table, the summarized column must map a Doctrine field (or a relation path such as region.quota).
A virtual column assembled in mapRow(), or a path crossing a to-many association such as
notes.text, raises a LogicException on the first request, as does an entity with a composite
identifier. A scope with a HAVING clause binds the matching identifiers as one list, so keep such
scopes to a moderate number of rows.
Sums of DECIMAL columns are returned as floats, so very large totals or many decimals can lose
precision; format them with MoneyColumn or round in your own provider.
Summaries never include a column the user may not see (setPermission()).
Footer Markup and Styling
The bundle creates the <tfoot> when the table has none, with one th scope="col" cell per
column, and leaves columns without a summary empty. Labels and values are inserted as text.
Style it with the --dt-summary-surface, --dt-summary-border, --dt-summary-text, and
--dt-summary-text-muted CSS variables, which follow the filter palette and its dark mode.