Search by

omerkoseoglu / devextreme-data-symfony

omerkoseoglu

Symfony bundle for omerkoseoglu/devextreme-data: server-side DevExtreme data processing over Doctrine DBAL, ORM entities and arrays.

Package info

github.com/omerkoseoglu/devextreme-data-symfony

Type:symfony-bundle

pkg:composer/omerkoseoglu/devextreme-data-symfony

Statistics

Installs: 6

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-10-01 02:05 UTC

This package is auto-updated.

Last update: 2026-10-01 02:23:39 UTC


README

Packagist Version PHP Version CI Downloads License

Unofficial. This is an independent, community-maintained port. It is not affiliated with, endorsed by or supported by Developer Express Inc. "DevExtreme" and "DevExpress" are trademarks of Developer Express Inc.

Symfony bundle for omerkoseoglu/devextreme-data: answer DevExtreme widget requests (DataGrid, PivotGrid, SelectBox, ... with remoteOperations) from Doctrine DBAL queries, ORM entity mappings, tables or arrays. Filtering, sorting, paging, grouping and summaries run in the database.

Requires PHP 8.2+ and Symfony 7.4 or 8.x. Doctrine sources need a pdo_sqlite, pdo_mysql or pdo_pgsql DBAL driver.

Install

composer require omerkoseoglu/devextreme-data-symfony

Symfony Flex enables the bundle; otherwise add it to config/bundles.php:

DevExtreme\Data\Symfony\DevExtremeDataBundle::class => ['all' => true],

Optional config/packages/devextreme_data.yaml:

devextreme_data:
    normalize_dates: true   # compare ISO-8601 filter dates as wall-clock time in SQL
    max_take: ~             # cap rows per request (null = unlimited; leave null for PivotGrid endpoints)

Usage

use DevExtreme\Data\Symfony\DevExtremeLoader;
use DevExtreme\Data\Symfony\Doctrine\EntitySource;

#[Route('/api/orders')]
public function orders(DevExtremeLoader $loader, EntityManagerInterface $em): JsonResponse
{
    return $loader->response(EntitySource::for($em, Order::class, where: 'deleted_at IS NULL'));
}
const store = DevExpress.data.AspNet.createStore({ key: 'id', loadUrl: '/api/orders' });
new DevExpress.ui.dxDataGrid(el, { dataSource: store, remoteOperations: true });

$loader->load($source) returns the LoadResult object (JSON-serializable) instead. Parameters come from the query string, form fields or a JSON body. Malformed requests (bad JSON, mixed and/or, unknown field, ...) throw BadRequestHttpException, which Symfony renders as HTTP 400.

Controllers can also type-hint the parsed options:

public function load(LoadOptions $options): JsonResponse { /* $options->take, ->filter, ... */ }

Sources

EntitySource::for($em, Order::class) builds a source from the Doctrine mapping: table, mapped properties (embeddables become nested objects) and identifier. The client sees property names, never column names; owning many-to-one associations are exposed under the association name and hold the foreign key. Restrict what is visible with fields: ['id', 'total'].

DbalSource::forQueryBuilder($connection, $qb, columns: [...], primaryKey: ['id']) accepts any DBAL query builder, including joins, named parameters (:name), IN (:ids) arrays and DateTimeInterface values:

$qb = $connection->createQueryBuilder()
    ->select('o.id', 'o.total', 'c.name AS customer_name')
    ->from('orders', 'o')->join('o', 'customers', 'c', 'c.id = o.customer_id')
    ->where('o.tenant_id = :tenant')->setParameter('tenant', $tenantId);

$source = DbalSource::forQueryBuilder($connection, $qb,
    columns: ['id' => 'id', 'total' => 'total', 'customer.name' => 'customer_name', 'gross' => new Raw('total * 1.2')],
    primaryKey: ['id'],
);

columns is a whitelist (client field => output column of your query, or a trusted Raw expression); dotted names become nested JSON. Without it any plain column name of the query is accepted. Use it in production.

DbalSource::forTable($connection, 'orders', ...), arrays/iterables (handled in memory) and any DataSourceInterface work too.

Known limitations

  • Scalar rows, no hydration. No entities, lifecycle events or Doctrine type conversion: dates are strings, booleans are 0/1 on most drivers.
  • pdo_* drivers only (pdo_sqlite, pdo_mysql, pdo_pgsql). mysqli and other drivers throw a LogicException.
  • Queries bypass the DBAL layer. They run on the native PDO connection, so Doctrine's SQL logger, the Symfony profiler's database panel and DBAL middlewares do not see them.
  • Doctrine SQL filters are not applied (soft-delete, tenancy). Put such constraints into the DbalSource query or the where: / whereParams: arguments of EntitySource.
  • EntitySource exposes mapped fields and owning many-to-one associations (as the foreign key). Collections (one-to-many, many-to-many) are not exposed, and entities with inheritance mapping are rejected: use DbalSource.
  • The query builder is captured once when DbalSource::forQueryBuilder() is called. Array parameters are supported with named placeholders only (IN (:ids)).
  • Dates and timezones: filter dates are compared as wall-clock time and never converted; see the core package's known limitations (browser Date values reach the server as UTC).
  • Tested with Doctrine DBAL 4 and ORM 3 on Symfony 7.4 and 8.x (PHP 8.2+).
  • All filter values are bound parameters; field names are whitelisted (mapping or columns) or identifier-checked.

Development

composer install     # uses ../devextreme-php-data as a path repository
composer test

The tests boot a real Symfony kernel with an in-memory SQLite database and run the core package's SQL-vs-memory parity matrix against EntitySource, DbalSource::forTable and DbalSource::forQueryBuilder, plus HTTP-level behaviour.

MIT licensed.