Data Providers & Row Mappers
Contracts and built-in implementations for data retrieval and row mapping
With AbstractDataTable, providers are resolved internally and row mapping is applied through a
pipeline. The usual extension points are:
createDataProvider()for custom providersmapRow()for domain-to-array mappingcreateRowMapper()when you need the built-in mapping/template/action pipeline instance
DataProviderInterface
use Pentiminax\UX\DataTables\DataTableRequest\DataTableRequest;
use Pentiminax\UX\DataTables\Model\DataTableResult;
interface DataProviderInterface
{
public function fetchData(DataTableRequest $request): DataTableResult;
}
Built-in providers:
| Provider | Use case |
|---|---|
DoctrineDataProvider | Doctrine ORM-backed datasets |
ArrayDataProvider | In-memory or preloaded arrays |
RowMapperInterface
interface RowMapperInterface
{
public function map(mixed $row): array;
}
Built-in mappers and processors:
| Mapper | Use case |
|---|---|
DefaultRowMapper | Default row-to-array mapping behavior |
RowProcessingPipeline | The pipeline AbstractDataTable builds: stages, then URL, template, and action resolution |
RowStageInterface
Each stage in RowProcessingPipeline implements RowStageInterface:
interface RowStageInterface
{
public function process(array $mappedRow, mixed $originalRow, array $columns): array;
}
Built-in stages applied by default (in order):
| Stage | Responsibility |
|---|---|
NormalizationStage | Dotted-path resolution, DateColumn formatting, Stringable casting |
IconColumnResolutionStage | Resolves each IconColumn state into row icon metadata (__ux_datatables_icons) |
BooleanSwitchMetadataStage | Records the row id behind every switch-rendered BooleanColumn (__ux_datatables_boolean_switches) |
After those stages, RowProcessingPipeline still resolves URL columns, renders TemplateColumn
cells via Twig, and resolves ActionColumn URLs into __ux_datatables_actions. That work runs
inside the pipeline rather than as separate RowStageInterface classes.
The stage list is assembled by the runtime factory and is final on the table:
AbstractDataTable::createRowMapper() cannot be overridden, and no service tag adds a stage. To
change how a row is mapped, override mapRow() — it is the base mapping every stage runs on top
of:
protected function mapRow(mixed $row): array
{
$mappedRow = parent::mapRow($row);
if (isset($mappedRow['title'])) {
$mappedRow['title'] = strtoupper($mappedRow['title']);
}
return $mappedRow;
}
To act after the pipeline instead — once template columns and action URLs are already resolved —
wrap the built mapper in a RowMapperInterface of your own and hand that to a provider built in
createDataProvider(). An anonymous class delegating to createRowMapper() is enough; it is also
the replacement for the closure-backed mapper the bundle used to ship:
use Pentiminax\UX\DataTables\Contracts\DataProviderInterface;
use Pentiminax\UX\DataTables\Contracts\RowMapperInterface;
use Pentiminax\UX\DataTables\DataProvider\ArrayDataProvider;
protected function createDataProvider(): ?DataProviderInterface
{
$inner = $this->createRowMapper();
$mapper = new class ($inner) implements RowMapperInterface {
public function __construct(private readonly RowMapperInterface $inner)
{
}
public function map(mixed $row): array
{
$mappedRow = $this->inner->map($row);
$mappedRow['title'] = strtoupper($mappedRow['title'] ?? '');
return $mappedRow;
}
};
return new ArrayDataProvider($this->rows, $mapper);
}
DataTableResult
new DataTableResult(
recordsTotal: 1000,
recordsFiltered: 150,
data: $rows,
);
When To Implement Custom Types
- custom domain filters with non-Doctrine backends
- APIs requiring specific output shape
- performance tuning with pre-mapped row payloads
Manual Provider Example
use Pentiminax\UX\DataTables\Contracts\DataProviderInterface;
use Pentiminax\UX\DataTables\DataProvider\ArrayDataProvider;
protected function createDataProvider(): ?DataProviderInterface
{
return new ArrayDataProvider($this->rows, $this->createRowMapper());
}
$this->createRowMapper() is the important part: it preserves the same mapping, template
rendering, and action-resolution behavior as the built-in Doctrine provider. setData() on
AbstractDataTable uses that same pipeline for inline rows.
Page Projection
projectPage() transforms a complete, already-paginated page of source entities, so a page can be
batch-enriched without an N+1. A server-side export has no page: it streams every filtered row, so
the projector is called once per batch instead, with a batch size unrelated to the DataTables page
length. Project each item from itself rather than from the batch it arrived in. See
Custom Exporters.