omerkoseoglu / devextreme-data-symfony
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
Requires
- php: ^8.2
- omerkoseoglu/devextreme-data: ^0.1 || dev-main
- symfony/config: ^7.4 || ^8.0
- symfony/dependency-injection: ^7.4 || ^8.0
- symfony/http-foundation: ^7.4 || ^8.0
- symfony/http-kernel: ^7.4 || ^8.0
Requires (Dev)
- doctrine/dbal: ^4.0
- doctrine/orm: ^3.3
- friendsofphp/php-cs-fixer: ^3.64
- phpunit/phpunit: ^10.5 || ^11.0 || ^12.0
- symfony/framework-bundle: ^7.4 || ^8.0
Suggests
- doctrine/dbal: Required for DbalSource (pdo_* drivers only)
- doctrine/orm: Required for EntitySource
Provides
None
Conflicts
None
Replaces
None
README
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/1on most drivers. pdo_*drivers only (pdo_sqlite,pdo_mysql,pdo_pgsql). mysqli and other drivers throw aLogicException.- 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
DbalSourcequery or thewhere:/whereParams:arguments ofEntitySource. EntitySourceexposes 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: useDbalSource.- 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
Datevalues 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.