langleyfoxall/eloquent-report-generator
Generate reports directly from your Laravel Eloquent models. Define columns and queries declaratively, then export results as tables or downloadable files with minimal boilerplate and clean, readable code.
The package turns an Eloquent query into a tabular report — you describe the model, the columns, and the query, and it handles rendering, column formatting, and (depending on config) export.
v1.1.0 is the release to start on, but note the version jump: the previous tag was v0.2.0, so v1.0.0 happened somewhere in between and almost certainly carried breaking changes. The changelog diff to read is compare/v0.2.0...v1.1.0, and PR #5 ("Upgrade") is the only listed change — read that PR before copying any snippet from an old blog post or the 0.x README, because pre-1.0 examples are the most likely thing to be stale.
Minimal steps:
composer require langleyfoxall/eloquent-report-generator:"^1.1" — use a caret constraint, not dev-master, so you actually get the 1.x API.src/ for the base report class and the column/field definition objects, then the package's own tests — with a thin changelog, the test suite is the most reliable documentation of the current API surface.php artisan vendor:publish --provider="LangleyFoxall\..." if the service provider offers them (check config/ in the package).Order::query()->with('customer') — define the columns, and render it at a single route behind your existing auth middleware. Resist wiring it into exports or scheduled output until you've confirmed the 1.x API holds up in your app.Report class per report. The idiomatic shape is one class per report, holding three concerns in one place: the base query, the column definitions, and any per-column formatting. Keep the query as a Builder (return Order::query(), not ->get()), so the package can still apply its own sorting, pagination, or chunking on top. If you return a hydrated collection you lose all of that.
Scope the query, don't filter after. Authorization belongs inside the base query — ->where('tenant_id', auth()->user()->tenant_id) — so every consumer of the report (view, export, queued job) inherits the restriction. Filtering in the view or in a post-query step is how reports leak other tenants' rows.
Eager-load aggressively. Columns that reach through a relation ($order->customer->name) are the number-one source of N+1 in report code, because a report renders every row. Add ->with([...]) for every dotted/relation column at definition time, and add Model::preventLazyLoading() in local/CI so a missed with() fails loudly instead of quietly issuing 500 queries.
Format at the column, not in the view. Keep money/date/enum formatting attached to the column definition so the Blade view, CSV export, and any JSON output all agree. If you find yourself writing number_format() in a template, that formatting belongs on the column.
Chunk for large reports. Anything that can exceed a few thousand rows should go through chunkById()/lazy() or the package's own pagination rather than a single ->get(). If you add a queued export, use chunkById (not chunk) — it's stable against rows being inserted or deleted mid-run, which matters on live tables.
Wrap in a route + policy, not a controller action. Reports are essentially a second read path into your data, so give them the same treatment as any other endpoint: a policy check, a named route, and a feature test asserting that user A cannot see user B's rows. That test is cheap and catches the scoping mistakes above.
Test against SQLite in memory. Reports are pure query + formatting, so a handful of factories and an assertion on the rendered output covers most of the risk without touching your real database.
composer.json ("^1.1") and commit the lock file. With no detailed changelog, an accidental minor bump is an unnecessary gamble on a package that just made a major version leap with a one-line release note.php artisan vendor:publish --force for the views only, and diff before committing.$query->toSql() with ->getBindings() before handing it to the package. Most "the report is broken" issues are a where that filtered everything out, not a rendering bug.—. Decide both conventions once, at the column level, rather than per report.App\Reports\BaseReport and put your house style (date format, money format, empty-value placeholder, tenant scoping) there — that single indirection makes the next major upgrade far cheaper.How can I help you explore Laravel packages today?