Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Jasper Rest Client Laravel Package

hboie/jasper-rest-client

hboie/jasper-rest-client is a PHP/Laravel client for the JasperReports REST API, providing a service provider and facade to generate and export reports and integrate JasperReports into Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require hboie/jasper-rest-client

If you're on Laravel < 5.5 (or the package doesn't auto-register), add the service provider and facade to config/app.php:

hboie\JasperRestClient\JasperRestClientServiceProvider::class,

'Jasper' => hboie\JasperRestClient\Facades\Jasper::class,

Where to look first

  • config/jasper-rest-client.php (publish with php artisan vendor:publish) — this is the only place connection details should live.
  • src/JasperRestClient.php — the single class that actually calls Jasper's REST endpoints. Read it end-to-end (it's small). The public method list is the API surface; there's no hidden magic.
  • The README's example for createClient + getReport — that's the entire happy path.

Configure

// .env
JASPER_HOST=https://reports.example.com
JASPER_PORT=8443
JASPER_USERNAME=jasperadmin
JASPER_PASSWORD=secret
JASPER_FORMAT=pdf
JASPER_VERIFY_SSL=true

First use case: render a report to PDF

The client is stateful — you instantiate (or createClient) once per request/credential set, then call the report endpoints against a repository URI (the path inside Jasper, /reports/my_folder/invoice):

use hboie\JasperRestClient\Facades\Jasper;

// One-time setup for this request; credentials/host come from config
Jasper::createClient(
    config('jasper-rest-client.host'),
    config('jasper-rest-client.port'),
    config('jasper-rest-client.username'),
    config('jasper-rest-client.password'),
    config('jasper-rest-client.format')
);

// Execute with parameters and get the rendered output
$pdf = Jasper::getReport(
    '/reports/invoices/monthly',
    ['customer_id' => 42, 'month' => '2024-06']
);

return response($pdf)
    ->header('Content-Type', 'application/pdf')
    ->header('Content-Disposition', 'inline; filename="invoice.pdf"');

The method returns the raw binary body (or a string for xml/json/csv/html), so stream or store it — never echo it into a Blade view.

Implementation Patterns

Wrap it in your own service, don't call the facade everywhere

The facade is a singleton; treating it as a global makes testing and multi-tenant use painful. One thin wrapper gives you a seam for fakes, retries, and logging:

namespace App\Services;

use hboie\JasperRestClient\Facades\Jasper;
use Illuminate\Support\Facades\Cache;

class ReportService
{
    public function __construct()
    {
        Jasper::createClient(
            config('jasper-rest-client.host'),
            config('jasper-rest-client.port'),
            config('jasper-rest-client.username'),
            config('jasper-rest-client.password'),
            config('jasper-rest-client.format')
        );
    }

    public function render(string $uri, array $params = [], string $format = 'pdf'): string
    {
        return Jasper::getReport($uri, $params, $format);
    }
}

Bind it in a provider and inject ReportService — now a test can swap it for a mock without touching Jasper at all.

Parameters are a flat associative array

Pass Jasper report parameter names (not PARAM_-prefixed) as keys. Values are stringified, so dates must be pre-formatted (Y-m-d) and booleans passed as 'true'/'false' if the JRXML declares them that way. Multi-select params expect an array under the same key — check the JRXML before betting on it.

Formats are per-call, not per-client

getReport($uri, $params, 'xlsx') overrides the default. Use this rather than re-instantiating the client; the connection is already authenticated.

Async / long reports

If the Jasper instance runs reports synchronously, big reports will blow your PHP max_execution_time and block the request. Two mitigations that fit this package:

  1. Queue the render into a job, write the bytes to storage/app/reports/{uuid}.pdf, and hand the user a signed download URL. The queue worker has its own timeout.
  2. Cache the output keyed on uri + params + format, because the same invoice/financial statement is usually requested repeatedly:
$key = 'jasper:' . md5($uri . serialize($params) . $format);

return Cache::remember($key, now()->addHour(), fn () =>
    $this->render($uri, $params, $format)
);

Never cache reports containing per-user data without the params (and ideally the user/tenant) in the key.

Uploading and managing report definitions

The resource methods (getResources, getResource, saveResource, deleteResource) let you do CI/CD for JRXML: ship .jrxml files with your app, and have a deployment command push them to Jasper:

foreach (glob(resource_path('jasper/*.jrxml')) as $file) {
    Jasper::saveResource('/reports/invoices/' . basename($file), file_get_contents($file));
}

This is the highest-value day-to-day use outside of rendering — it turns report design into versioned code instead of a web-UI artifact.

Integration tips

  • Put Jasper behind your app; expose /reports/{id} endpoints that proxy to the report so Jasper credentials and URIs never reach the browser.
  • Always set Content-Disposition from the format (inline for pdf/html, attachment for xlsx/csv).
  • Log the requested URI, params (minus PII), format, and response size/duration around every call — you'll need it when a report "looks wrong."

Gotchas and Tips

The client is stateful and the facade is a singleton. createClient sets host/credentials on the shared instance. Under Octane, queue workers, or any long-running process, credentials from one request/tenant can leak into the next. Call createClient at the start of every job/request (or resolve a fresh instance), and never store tenant credentials on the singleton across calls.

No structured exceptions. The underlying Guzzle call can return a non-2xx status and the method may hand you an HTML error page as if it were your PDF. Always validate output before sending it to a user:

$pdf = Jasper::getReport($uri, $params);

if (strlen($pdf) < 100 || str_starts_with($pdf, '<')) {
    throw new \RuntimeException('Jasper returned an error payload, not a PDF');
}

Checking the %PDF magic bytes is cheap and catches 90% of misconfiguration.

SSL and self-signed certs. Jasper installs are frequently on internal hosts with self-signed certs. JASPER_VERIFY_SSL=false is the quick fix and the wrong one for anything internet-facing — prefer installing the CA into the container's trust store. The port matters too: 8443 vs 8080 change the scheme/URL the client builds.

URI shape is the #1 source of 404s. Use the repository path (/reports/folder/name), not the full URL and not the display name with spaces. Trailing slashes and case both matter to Jasper. When in doubt, verify the path in the Jasper web UI's repository browser and copy it exactly.

Date and numeric formatting is Jasper's job, not PHP's. Passing 2024-06-01 when the parameter is a java.util.Date works, but timezones bite: Jasper uses the server's timezone. If a report is "off by a day" in production, that's the cause — pin the report's timezone in the JRXML, or pass a full ISO-8601 timestamp.

Watch the dependency floor. This package is effectively unmaintained (last release Jan 2022, no dependents, no stars). It pins a Guzzle version that can conflict with newer Laravel releases. If composer require fights you, prefer a ^ constraint bump in a fork over --ignore-platform-reqs, and be prepared to vendor the single JasperRestClient.php class into app/Support/ — it's small enough that forking it is cheap insurance.

License. LGPL-3.0 is unusual for a PHP package. Using it unmodified in a commercial app is generally fine, but if you fork and modify it, the modifications should stay under LGPL. Flag it to legal before shipping to a client, and don't copy code out of it into proprietary files.

Extension points are thin, by design. There's no middleware, no event hooks, no custom Guzzle handler injection. If you need retries, timeouts, or request logging, wrap the client (decorator/service class) rather than trying to configure it. A retry-once-on-timeout loop around render() in your ReportService covers the common flakiness of report servers restarting.

Debugging technique. Drop to a direct curl against the same endpoint with the same credentials when a call fails — it isolates whether the problem is Jasper (auth, parameter name, URI) or the client (URL building, SSL). If curl works and the package doesn't, compare the constructed URL character by character; it's almost always a scheme/port/trailing-slash issue.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle