hboie/jasper-report-bundle
Symfony bundle for generating reports with JasperReports. Compile JRXML templates and export to PDF, HTML, XLS, and other formats through a simple PHP service, with configurable JasperReports binaries and report parameters.
Reality check first: hboie/jasper-report-bundle is a Symfony bundle, not a Laravel package. It has no service provider, no facade, and no auto-discovery for Laravel. If you're in a Laravel app you have two realistic paths:
composer require jaspersoft/rest-client and wrap it in your own tiny service provider + singleton. You get 90% of what the bundle gives you (config + a configured client) in ~40 lines.Assuming you go with option 1 (or you're reading the bundle for reference), the mental model the bundle encodes is the same:
timeout configuration option, which the bundle maps onto the underlying HTTP client. Look at the bundle's DependencyInjection/Configuration.php to see the exact key names (host, port, username, password, organization, timeout, etc.) before copying them into config/services.php / a config/jasper.php.Jaspersoft\Client\Client. In Laravel, register your own singleton in AppServiceProvider and resolve credentials from config() / .env.$client = app(\Jaspersoft\Client\Client::class);
$pdf = $client->reportService()->runReport(
'/reports/invoices/monthly',
'pdf',
null, // output format is first arg in some versions; check your client version
null,
['customer_id' => 42]
);
return response($pdf)
->header('Content-Type', 'application/pdf')
->header('Content-Disposition', 'inline; filename="monthly.pdf"');
Where to look first: the repo's README and DependencyInjection/ folder, then the jaspersoft/rest-client source — the bundle is a thin config layer over that client, so the real API surface lives there. Also confirm which JasperReports Server version you're targeting; the REST v2 endpoints changed around 6.x/7.x.
Bind the client as a singleton, configure once. In AppServiceProvider::register():
$this->app->singleton(\Jaspersoft\Client\Client::class, function () {
$client = new \Jaspersoft\Client\Client(
config('jasper.host'),
config('jasper.port'),
config('jasper.username'),
config('jasper.password'),
config('jasper.org_id') // tenant
);
// Timeout support added in bundle v1.6 — mirror it here.
// Set per-request timeout on the underlying HTTP client / Guzzle handler.
$client->setTimeout(config('jasper.timeout', 60));
return $client;
});
Treat this like any other HTTP SDK binding — never new it inside controllers. Keep the timeout in config/jasper.php (defaulting from .env) so it is tunable per environment, matching how the bundle now exposes it.
Wrap it in a domain service. Don't let controllers touch Jaspersoft\Client\Client directly. A ReportService with methods like renderInvoice(int $id): string and renderPayslip(...) keeps parameter marshalling (dates, multi-select arrays, locale strings) in one place and gives you a seam for tests and caching.
Queue long-running reports. Jasper renders can take 5–60s. Pattern: dispatch a GenerateReportJob, store the output on Storage::disk('s3'), then poll an endpoint or broadcast completion. Avoid doing this on the request thread — you'll blow PHP max_execution_time before Jasper finishes. Set the client timeout slightly above your realistic worst-case render, not the other way around.
Stream downloads, don't buffer twice. For anything over a few hundred KB, use response()->streamDownload() with the raw binary from runReport, or write it straight to storage. The client returns the body as a string, so keep an eye on memory for large reports.
Validate and pre-format parameters before calling Jasper. Jasper is strict: date params usually want yyyy-MM-dd, multi-value params want arrays, and nulls vs. empty strings behave differently. Write a small ReportParameters value object and format in PHP, not in JRXML expressions.
Multi-tenant / multi-organization. Jasper's organization parameter scopes the login. If you have per-customer Jasper orgs, build a small factory that resolves the org from the authenticated user and instantiates a scoped client (with Cache::store('redis')->remember() on it if credentials rotate slowly). Don't share a single client across tenants.
Health check. Add a scheduled command that calls something cheap like fetching the repository root or a known resource, so Jasper outages surface before users hit the report page. Give it a short timeout (the new v1.6 timeout config is useful here — keep the health-check timeout well below the report timeout).
timeout option gives you control over the HTTP-level timeout, but Jasper's server-side report timeout is separate and still exists. Set both. Also note v1.6 only adds a single timeout value — if you need distinct connect_timeout vs. read timeout behavior, you still have to reach into the underlying HTTP client / Guzzle handler directly. Verify in vendor/ which timeout key the release actually reads and where it is applied.Jaspersoft\Client\Client method signatures (especially runReport's argument order for format vs. params) differ between 1.x/2.x branches. Read the actual source in vendor/ rather than relying on blog posts.j_spring_security_check; newer ones prefer /rest_v2/login with Basic auth. If you get 401s that "sometimes work," it's almost always session cookie handling — the client has an AuthenticationException path for this.verify => false; you'll need to reach into the client or bind a custom Guzzle handler. Prefer installing the CA cert over disabling verification..env), and be aware that setting it too low yields truncated/failed reports rather than clean errors — Jasper may still be rendering server-side."true" strings depending on JRXML expression use. Test each report's parameter contract once, then codify it in your ReportParameters class.Log::debug) by passing a handler to the client, and cross-check against Jasper's own jasperserver.log. The response body from a failing Jasper call is usually the real error message — don't swallow it behind a generic 500. Distinguish a timeout (client gave up) from a Jasper error (server returned a message).services.yaml, decorate the client with a decorator that adds logging or retry, and override Configuration.php derived config keys (including the new timeout) via hboie_jasper_report.yml. In Laravel your extension point is simpler: a JasperClientFactory interface bound to a concrete implementation, swapped in tests.JasperClientContract interface and return pre-baked PDF bytes from fixtures. Never hit a real Jasper server in your test suite — it's slow and stateful. Assert that your timeout config is actually plumbed through to the client in a unit test, since this is exactly the kind of setting that silently regresses.How can I help you explore Laravel packages today?