Type to search columns, filters, options, and extensions.

to navigate · Enter to open · Esc to close

Documentation

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 providers
  • mapRow() for domain-to-array mapping
  • createRowMapper() 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:

ProviderUse case
DoctrineDataProviderDoctrine ORM-backed datasets
ArrayDataProviderIn-memory or preloaded arrays

RowMapperInterface

interface RowMapperInterface
{
    public function map(mixed $row): array;
}

Built-in mappers and processors:

MapperUse case
DefaultRowMapperDefault row-to-array mapping behavior
RowProcessingPipelineThe 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):

StageResponsibility
NormalizationStageDotted-path resolution, DateColumn formatting, Stringable casting
IconColumnResolutionStageResolves each IconColumn state into row icon metadata (__ux_datatables_icons)
BooleanSwitchMetadataStageRecords 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.