omerkoseoglu / devextreme-data-laravel
Laravel integration for omerkoseoglu/devextreme-data: server-side DevExtreme data processing over Eloquent models, query builders and collections.
Package info
github.com/omerkoseoglu/devextreme-data-laravel
pkg:composer/omerkoseoglu/devextreme-data-laravel
Requires
- php: ^8.2
- illuminate/database: ^12.0 || ^13.0
- illuminate/http: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
- omerkoseoglu/devextreme-data: ^0.1 || dev-main
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- orchestra/testbench: ^10.0 || ^11.0
- phpunit/phpunit: ^10.5 || ^11.0 || ^12.0
Suggests
None
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.
Laravel integration for omerkoseoglu/devextreme-data: answer DevExtreme widget requests
(DataGrid, PivotGrid, SelectBox, ... with remoteOperations) straight from Eloquent models, relations,
query builders, collections or arrays. Filtering, sorting, paging, grouping and summaries run in the database.
Requires PHP 8.2+, Laravel 12 or 13. SQLite, MySQL/MariaDB and PostgreSQL connections.
Install
composer require omerkoseoglu/devextreme-data-laravel
The service provider and the DevExtreme facade are auto-discovered. Optional config:
php artisan vendor:publish --tag=devextreme-data-config
Usage
use DevExtreme\Data\Laravel\Facades\DevExtreme; Route::get('/api/orders', fn () => DevExtreme::response(Order::class)); // any builder, relation or constraint Route::get('/api/my-orders', fn (Request $r) => DevExtreme::response($r->user()->orders()->where('status', 'open')) );
const store = DevExpress.data.AspNet.createStore({ key: 'id', loadUrl: '/api/orders' }); new DevExpress.ui.dxDataGrid(el, { dataSource: store, remoteOperations: true });
DevExtreme::load($source) returns the LoadResult object (JSON-serializable) when you need to post-process it.
Sources: model class-string, Eloquent\Builder, Relation, Query\Builder, Collection, array, or any
DevExtreme\Data\Contracts\DataSourceInterface. Parameters are read from the query string, form or JSON body.
Malformed requests (bad JSON, mixed and/or, unknown field, ...) become HTTP 400 automatically.
How EloquentSource works
The builder is compiled to SQL — global scopes (soft deletes, tenancy), constraints and bindings included — and used as a derived table. DevExtreme operations are applied on top of it by the database. Consequences:
- Your scopes and constraints always apply; clients cannot escape them.
- Rows are returned as plain arrays: no model hydration, casts, accessors or
$appends/$hidden. Select what you expose (->select('id', 'total')). - By default the model key is used as the stable-sort tie-breaker.
Exposing only some fields / joins
Pass a whitelist mapping client field names to output columns of your query. Dotted names become nested JSON:
use DevExtreme\Data\Laravel\EloquentSource; $source = EloquentSource::for( Order::query() ->join('customers', 'customers.id', '=', 'orders.customer_id') ->select('orders.id', 'orders.total', 'customers.name as customer_name'), columns: ['id' => 'id', 'total' => 'total', 'customer.name' => 'customer_name'], ); Route::get('/api/orders', fn () => DevExtreme::response($source));
Without columns, any plain column name of the query is accepted (identifier-checked, values are always bound).
Use columns in production. Values may also be DB::raw() expressions: 'total' => DB::raw('amount * qty').
Configuration (config/devextreme-data.php)
| Key | Default | Meaning |
|---|---|---|
normalize_dates |
true |
Compare ISO-8601 filter dates as wall-clock time |
max_take |
null |
Cap rows per request (protects against "load everything"). Leave null for PivotGrid endpoints |
Request helper
$options = $request->devExtremeOptions(); // DevExtreme\Data\LoadOptions
Known limitations
- No hydration. Rows are plain arrays: Eloquent casts, accessors,
$appends,$hiddenand model events do not run. Dates come back as strings, booleans as0/1on most drivers. - Queries bypass Laravel's database layer.
EloquentSourceruns on the connection's PDO directly, soDB::listen, the query log, Telescope and Debugbar do not see these queries. Transactions opened on the write connection are not visible if you use read/write splitting (the read PDO is used). - The builder is captured once when
EloquentSource::for()is called; later changes to it are ignored. - Joins need explicit, unique column names (
->select('orders.id', 'customers.name as customer_name')), because the query is used as a derived table. - Drivers: SQLite, MySQL/MariaDB, PostgreSQL. SQL Server is not supported.
- 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). - Laravel 12 and 13 only (PHP 8.2+); older Laravel versions are not supported.
Development
composer install # uses ../devextreme-php-data as a path repository composer test
Tests run the core package's SQL-vs-memory parity matrix against EloquentSource, plus soft deletes, relations,
joins, bindings and HTTP-level behaviour (Orchestra Testbench, SQLite in memory).
MIT licensed.