Skip to main content
tally/laravel backs a grid from a Laravel app. A grid class declares the columns, Route::tally() exposes it, and the adapter compiles filter, sort, group, aggregate, facet and page requests into SQL. A client connects with connectHttpSource exactly as it does to tally-server.
It requires PHP 8.4 and Laravel 13. The service provider is discovered automatically.

Grid

A grid extends Tally\Laravel\Grid and implements query() and columns().
app/Grids/AccountsGrid.php
Columns use the same shape as createSchema in tally-core and are sent to the client unchanged. Grids are resolved from the container, so the constructor can take dependencies. Every method below can be overridden:

Flags

A flag filter such as is:paying calls the closure of the same name with the query builder.
FlaggedAccountsGrid.php

Routes

Route::tally($uri, $grid) registers the four protocol routes under $uri. Wrap it in middleware like any other route.
routes/web.php
The schema route reports live: false, so the client opens no event stream. Every request is validated against the tally-protocol JSON Schema and then against the grid’s columns before any SQL runs. Invalid requests return 400 with { code, message, details }, where every detail names the path of a problem. See Protocol.

Semantics

The adapter passes the tally-protocol conformance suite on SQLite, Postgres and MySQL, so results match the in-memory engine:
  • Empty means NULL, and for text columns also ''. Empty values sort last in both directions and never match comparisons, except neq, notContains and notIn, which keep them.
  • Text comparisons are case insensitive through lower(). SQLite folds ASCII only.
  • Enum columns sort and facet in option order.
  • Date groups bucket to YYYY-MM-DD, ISO Monday weeks, YYYY-MM, YYYY-Qn or YYYY.
  • Search matches the stored value of searchable columns, not the formatted text.
  • Group order follows the sort on the group column when there is one, otherwise option or natural order, newest dates first.
  • Empty values form one group, NULL and '' alike, which sorts last and pages like any other group.

Dialects

A Dialect supplies the functions SQL engines disagree on: lower, text, float, floor, dayNumber, dateBucket, plus encode and decode for values crossing the driver. The dialect is picked from the connection driver: sqlite, pgsql, mysql and mariadb. Extend Tally\Laravel\Sql\Dialect for another engine and return it from dialect().

Mutations

Mutations run in one database transaction and return the canonical rows read back from the database. Deletes and updates are scoped by query(), so a grid never writes to rows it cannot read.
Writes go through the query builder, so model events, casts and timestamps do not run.
When the transaction fails, every mutation is rolled back, the exception is reported, and the response is { accepted: false, errors: [{ message: 'Mutation failed' }] }.

Without HTTP

The adapter runs without a route, for jobs, commands or your own controllers.

Testing

Test a grid with Laravel’s HTTP test helpers. After Route::tally('/api/accounts', AccountsGrid::class), a request that does not fit the columns answers with every problem:
tests/Feature/AccountsGridTest.php
The package’s own suite runs the tally-protocol conformance cases. The JSON Schema files in resources/schemas and the fixtures in tests/Fixtures/conformance.json are built from tally-protocol, so the PHP adapter is checked against the same cases and expected results as the TypeScript adapters, on every supported database: