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

Symfony Jsend Bundle Laravel Package

tcloud.ax/symfony-jsend-bundle

Symfony JSend Bundle provides JSend-compliant JSON responses for Symfony applications, helping standardize API success, fail, and error payloads. Integrate consistent response envelopes, reduce custom serialization code, and keep controllers clean.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle, not a Laravel package — there is no ServiceProvider, no auto-discovery, and no config/app.php entry. If you're coming from Laravel, treat it as a reference implementation of the JSend spec that you either port or consume inside a Symfony service. For day-to-day use in a Symfony app:

  1. Install (GitLab-hosted, likely not on Packagist, so add a VCS repository first):
    "repositories": [{ "type": "vcs", "url": "https://gitlab.com/tcloud.ax/symfony-jsend-bundle" }],
    "require": { "tcloud.ax/symfony-jsend-bundle": "dev-main" }
    
  2. Register the bundle in config/bundles.php:
    Tcloud\SymfonyJsendBundle\SymfonyJsendBundle::class => ['all' => true],
    
  3. Where to look first: the bundle's src/ for the response/factory class and any EventListener or EventSubscriber (typically a kernel.exception listener). The README, if present, will show the canonical Jsend::success() style helper. Read the exception listener first — that's where the "magic" lives and where most surprises come from.
  4. First use case: return a JSend-shaped payload from one controller:
    return $this->json([
        'status' => 'success',
        'data'   => ['user' => $user->toArray()],
    ]);
    
    Then wire the exception listener so thrown exceptions become {"status":"error","message":"...","code":500} instead of Symfony's HTML error page.

Implementation Patterns

1. Three-bucket responses. JSend has exactly three shapes — enforce them with helpers rather than free-form $this->json():

  • success — 2xx, data holds the payload (object/array; null allowed).
  • fail — 4xx, data holds structured details (field → message map for validation).
  • error — 5xx, message is human-readable, code is a machine code, data is optional.
// success
['status' => 'success', 'data' => $dto]

// fail (validation)
['status' => 'fail', 'data' => ['email' => 'Invalid email']]

// error
['status' => 'error', 'message' => 'Upstream timeout', 'code' => 504]

2. Global exception listener. The highest-leverage integration is mapping domain exceptions to JSend shapes in one place:

class JsendExceptionListener {
    public function onKernelException(ExceptionEvent $event): void {
        $e = $event->getThrowable();
        [$status, $payload] = match (true) {
            $e instanceof ValidationException => [422, ['status'=>'fail','data'=>$e->errors()]],
            $e instanceof NotFoundHttpException => [404, ['status'=>'fail','data'=>null]],
            default => [500, ['status'=>'error','message'=>'Internal error','code'=>500]],
        };
        $event->setResponse(new JsonResponse($payload, $status));
    }
}

Register with a high priority so it wins over Symfony's default ErrorListener.

3. Validation via fail. Map Symfony Validator errors into the data object as field => message[]. Clients can then render forms directly from response.data without parsing a generic message.

4. Serializer wrapping. Normalize your entity/DTO with Symfony Serializer (respecting @Groups), then wrap the normalized array as data. Keep the JSend envelope outside the serializer to avoid polluting your DTOs with status fields.

5. Status code discipline. Set the HTTP status to match the JSend status: success → 200/201, fail → 400/401/403/404/422, error → 500+. Clients (and API gateways) key off both, but they must agree.

6. Testing assertions.

$client->request('GET', '/api/users/1');
$this->assertResponseIsSuccessful();
$this->assertJsonPath('status', 'success');
$this->assertJsonStructure(['status', 'data' => ['id', 'email']]);

7. Laravel translation. If you're actually on Laravel, don't fight the bundle — copy its three response shapes into a small JsendResponse trait or a package you control, and register a Handler::render override in app/Exceptions/Handler.php to emit the envelopes. The spec is trivial; the value is the discipline, not the code.

Gotchas and Tips

  • Not on Packagist / no stars / zero dependents. Expect a dev-main constraint, a GitLab auth token for CI, and no community fixes. Pin a commit hash (dev-main#abc1234) rather than a branch to survive force-pushes upstream.
  • Symfony ≠ Laravel. There's no auto-discovery, no facades, no config/jsend.php published by default. Budget an hour to read the source before assuming APIs from the README exist.
  • HTTP 200 + "status":"fail" confusion. Some teams return 200 for everything and rely on the body; others (correctly) match status codes. Pick one convention before frontend work starts — mismatches are the #1 JSend integration bug.
  • data type drift. JSend says data is an object/array/null, but nothing stops a controller returning a scalar. Add a response listener or a JsendFactory method with a typed signature so data is always arrayable.
  • Exception listener priority. Symfony's ErrorListener runs at -128. Register yours above it (e.g. -64) or every exception becomes a debug HTML page and the bundle never fires. Test with kernel.debug both on and off — behavior diverges.
  • Don't leak traces. The default error path must never serialize $e->getTrace(), file paths, or SQL. Log the full exception server-side and emit only a correlation code + safe message.
  • fail vs error semantics. Use fail only for client-caused problems (validation, missing auth, bad input). Using fail for a 500 hides real outages from monitoring that greps for status:error.
  • Content-Type. JSend assumes application/json. Ensure your listener sets it explicitly — Symfony's JsonResponse does, but a manually built Response with json_encode won't.
  • Nested envelopes. Don't let a legacy controller's {"data": {...}} get double-wrapped by the listener. Audit routes after installation; grep for existing json()/JsonResponse returns.
  • Extension points to know: the bundle's normalizer/response factory (subclass or decorate), and the kernel.exception / kernel.response event subscribers — those two events cover ~90% of custom JSend behavior (adding pagination, meta, or request-id fields).
  • Version-lock risk. With 0 dependents and no visible release tags, treat upgrades as breaking by default and run your API contract tests (or a JSON-schema assertion suite) on every bump.
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.
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
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource