pitoune/report-bundle
pitoune/report-bundle is a reporting package for PHP applications that helps you define report structures, generate report output, and add reusable reporting features to your project with minimal setup. It is suited for dashboards, admin panels, and scheduled report workflows.
First, the framing. pitoune/report-bundle is not a Laravel package. It is a read-only subtree split of Sylius/Bundle/ReportBundle from the Sylius e-commerce platform (Symfony). It contains the Report / ReportData entities, the report admin controller, form types, the data-fetcher and renderer registries, and the built-in fetchers/renderers. "Leveraging it day-to-day" realistically means one of two things:
Minimal steps for the Sylius case:
# The split lags the platform. Use the bundle that ships with Sylius instead.
composer require sylius/sylius
// config/bundles.php
return [
// ...
Sylius\Bundle\ReportBundle\SyliusReportBundle::class => ['all' => true],
];
If you want the split specifically, check the live constraint on Packagist rather than trusting the old ^0.18 pin — the project is now tagging 1.0.0-beta.* releases (current: v1.0.0-beta.2.1). A 1.0.0-beta tag is still a beta: it does not promise a stable API, and it does not by itself prove the Symfony/PHP floor has moved. Read the composer.json of the tag before requiring it:
composer require pitoune/report-bundle:^1.0@beta
Where to look first: the parent repository, Sylius/Sylius, under src/Sylius/Bundle/ReportBundle, plus its Model/, Entity/, and DependencyInjection/Compiler/ directories (the registry wiring is the part that actually matters). The Sylius docs page on reports describes the admin workflow.
First use case: create a report through the Sylius admin (/admin/reports) using the built-in data fetcher and table renderer, then open it. That gives you the full loop — Report config → fetcher → ReportData → renderer → HTML. Once that works, replace the fetcher with your own metric (sales by channel, stock turnover, refund rate). That custom fetcher is where 90% of real work lives. If your metric naturally produces a nested shape (channel → month → row), the renderer can now display it as-is (see below) instead of you flattening it first.
The report is configuration, not code. A Report row carries code, name, dataFetcher, renderer, and dataFetcherConfiguration (a form payload serialized into the entity). Reports are therefore data-driven: the admin form builds the configuration array, and the fetcher interprets it at render time.
Custom data fetcher — the core extension point:
use Sylius\Bundle\ReportBundle\Model\ReportInterface;
use Sylius\Bundle\ReportBundle\Model\ReportDataInterface;
use Sylius\Bundle\ReportBundle\DataFetcher\DataFetcherInterface;
final class RefundRateDataFetcher implements DataFetcherInterface
{
public function fetch(ReportInterface $report, array $configuration = []): ReportDataInterface
{
$from = $configuration['startDate'] ?? (new \DateTime('-30 days'))->format('Y-m-d');
$to = $configuration['endDate'] ?? (new \DateTime())->format('Y-m-d');
// Build rows from your repository; return a ReportData populated with
// data + labels, which the renderer consumes.
}
}
# services.yaml
App\Report\RefundRateDataFetcher:
tags: ['sylius.report_data_fetcher']
Nested data is now a first-class shape. v1.0.0-beta.2.1 adds rendering of multidimensional arrays. Practical consequence: your fetcher can return grouped data — e.g. ['Web' => ['2024-01' => [...], '2024-02' => [...]], 'Retail' => [...]] — and the table renderer will present it without you pre-flattening. Two rules still apply:
[row] shape. Keep a flatten() step (or a second, flat rendering path) for those consumers; the renderer's nested display is a presentation concern, not a data contract.Custom renderer — implement RendererInterface (render(ReportDataInterface $data): string) and tag it sylius.report_renderer. The bundle ships table and chart renderers; both emit HTML fragments, so add your own escaping if any label/column comes from user-controlled data. If you write your own renderer, handle nested arrays recursively or explicitly flatten on entry — the built-in renderer's new behaviour is not automatically inherited by custom implementations.
Admin form extension. Add the configuration fields your fetcher expects by extending the report form type from ReportType::class (or overriding the template that builds the fetcher-specific panel). Keep the resulting array shape stable — it is persisted.
Fetching outside the admin. For dashboards, cron exports, or API endpoints, resolve the fetcher and call it directly:
$report = $reportRepository->findOneBy(['code' => 'refund_rate']);
$data = $dataFetcherRegistry
->get($report->getDataFetcher())
->fetch($report, $report->getDataFetcherConfiguration());
Note that fetch() is synchronous and does no caching. For anything scheduled, wrap it in a console command, cache the resulting ReportData (or its CSV/JSON serialization), and stamp a generatedAt. Don't let a heavy aggregate query sit behind a page render.
Laravel integration. If the reporting need is Laravel-side, don't bridge the bundle — the container, Doctrine mappings, and admin assumptions don't translate. Instead use it as a spec: separate "data fetcher" and "renderer" concerns, persist report definitions as rows with a config column, and implement each metric as a query object plus a Blade/API presenter. That is what the bundle is doing, minus the Symfony baggage. Pair with maatwebsite/excel for exports and a scheduled command for materialization.
1.0.0-beta.* tags; the latest (v1.0.0-beta.2.1) is a one-line change ("display multidimensional arrays"). Beta numbering plus a single terse note means: read the diff, don't assume a maintained platform. Still treat it as reference source, not a drop-in dependency.Sylius/Sylius. Filing against this repo does nothing — including reporting bugs in the new nested-array rendering.composer.json: a 1.0.0-beta version number does not guarantee Symfony 5+/PHP 8. If it still resolves Symfony 2.8/3.x and Doctrine ORM 2.x, installing it in a PHP 8 / Laravel 10+ app will fail resolution or fatal on signatures (DataFetcherInterface, return types, deprecated Doctrine APIs).sylius.report_data_fetcher or sylius.report_renderer requires a cache clear/container rebuild. If your fetcher "doesn't exist" in the registry, this is almost always why.dataFetcherConfiguration is opaque and persisted. There is no migration story for it. Changing the admin form shape silently breaks previously saved reports; version your configuration keys and validate them defensively in fetch().ReportData looks, not how it is stored or exported. Anything that serializes ReportData (CSV, JSON API, chart JS) must be checked after upgrading — nested rows can silently become Array strings in naive exporters.doctrine.orm.resolve_target_entities or attribute overrides rather than editing vendor code, since you cannot patch a subtree split.SyliusReportBundle from the matching Sylius release rather than this split. On Laravel, build the fetcher/renderer split yourself with query objects, maatwebsite/excel for exports, and a scheduled command to precompute aggregates — you will get a maintained, testable version of the same pattern with a fraction of the integration risk.How can I help you explore Laravel packages today?