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

phptcloud/symfony-jsend-bundle

Symfony bundle for JSend-compliant JSON responses. Standardize API output with consistent status, data, message and code fields, making client-side handling simpler. Integrates easily into Symfony controllers and services.

View on GitHub
Deep Wiki
Context7

Getting Started

Install via Composer:

composer require phptcloud/symfony-jsend-bundle

If you are not on Symfony Flex, register the bundle manually in config/bundles.php:

return [
    // ...
    PHPTCloud\JsSendBundle\JsSendBundle::class => ['all' => true],
];

Look first at the bundle's DependencyInjection/Configuration.php and Resources/config/services.yml in the installed package (typically under vendor/phptcloud/symfony-jsend-bundle/). As of v3.0.2 the services file auto-wires JsendStandard\Interfaces\ResponseFactoryInterface, so the single most useful thing you can do on day one is confirm the alias is present and that you can type-hint the interface in a controller constructor. Note the namespace: the interface lives in JsendStandard\Interfaces, not under PHPTCloud\JsSendBundle — type-hint the interface, not a concrete bundle class, or autowiring will miss.

Because this is a small package (14 stars, single-maintainer), read the source rather than trusting blog posts — the public API is small enough to scan in a few minutes, and the interface/service wiring is now the part most worth verifying.

The first use case is returning a controller response in JSend shape. Either inject the factory:

use JsendStandard\Interfaces\ResponseFactoryInterface;

public function __construct(private ResponseFactoryInterface $jsend) {}

#[Route('/api/users/{id}', methods: ['GET'])]
public function show(User $user): Response
{
    return $this->jsend->success([
        'id'   => $user->getId(),
        'name' => $user->getName(),
    ]);
}

or, if you prefer the static shortcut, use the bundle's response class:

use PHPTCloud\JsSendBundle\Response\JsSendResponse;

return JsSendResponse::success([
    'id'   => $user->getId(),
    'name' => $user->getName(),
]);

That yields {"status":"success","data":{...}} with a 200. For failures use fail([...]) (client error, data is field-level detail) and error($message, $code, $data) (server error / processing exception). If the exact helper names differ in your installed version, inspect the interface and JsSendResponse.php — the JSend spec requires the status key to be one of success, fail, error, so the factory methods will inevitably map to those three.

Implementation Patterns

Inject the factory, not the static helper. Since v3.0.2 ResponseFactoryInterface is auto-wired, so constructor injection is now the low-friction default. This is worth standardising on across the codebase: it makes controllers mockable in unit tests, and it gives you one seam to decorate if you later want a request ID or timestamp on every envelope. Reserve the static JsSendResponse::success(...) calls for one-off scripts or places where DI is genuinely awkward.

Wrap every JSON endpoint. The point of JSend is consistency. Adopt a rule that no controller returns a raw JsonResponse. Return a response built through the factory so front-end code can branch on response.status once, everywhere.

Exception → JSend via kernel events. The common integration is a listener/subscriber on kernel.exception that detects ApiException-style exceptions and renders fail(...) or error(...). Inject the same ResponseFactoryInterface into the subscriber so the envelope shape is identical to the happy path. If the bundle does not ship a listener, add your own so that uncaught ValidationException becomes:

{"status":"fail","data":{"email":"Not a valid email"}}

and uncaught ServerException becomes:

{"status":"error","message":"Internal error","code":500}

Keeping this in one subscriber avoids scattered try/catch in controllers.

Serializer integration. For entity-backed payloads, normalize first and pass the array into the factory. This keeps the response object dumb and lets you test normalization separately:

$payload = $this->serializer->normalize($order, null, ['groups' => ['api:read']]);
return $this->jsend->success($payload);

Validation and errors are different statuses. JSend distinguishes fail (the client sent bad input — HTTP 4xx) from error (the server failed — HTTP 5xx). Map your domain exceptions accordingly, and set the actual HTTP status code on the response as well; JSend's status is not a replacement for the status line. Browsers, proxies, and HTTP clients still look at 400/500.

Testing. assertJson plus a structural check is enough; don't snapshot entire payloads. A useful base assertion:

$this->assertSame('success', $response->json('status'));
$this->assertArrayHasKey('data', $response->json());

If you inject the factory, you can also assert in a unit test that the controller passed the expected payload to success() without booting a kernel.

Gotchas and Tips

  • Type-hint the interface, not the bundle class. The auto-wiring added in v3.0.2 covers JsendStandard\Interfaces\ResponseFactoryInterface. If you type-hint a concrete class from PHPTCloud\JsSendBundle (or the interface before you upgrade), you can still hit "no such service." If you're on an older version and can't upgrade yet, define an explicit alias in config/services.yaml — but upgrading is the cleaner fix.
  • data is required for success and fail. The spec allows data to be null for error but requires it for the other two. Passing an empty array is fine; omitting the key is not. If the factory signature takes ?array $data = null, be explicit and pass [].
  • Do not encode entities directly. success($entity) will work only until the entity has a lazy relation, a circular reference, or a DateTime that should be ISO-8601. Always normalize/serialize first, or you will find yourself debugging JsonException: Recursion detected.
  • HTTP status ≠ JSend status. It is very easy to return fail(...) with a 200 because the factory defaults to 200. Check the interface signature and pass the HTTP code explicitly: fail($errors) should typically be a 400/422, error(...) a 500. Search the bundle source for setStatusCode to confirm the defaults.
  • Stack traces in error responses. Only include debug detail when APP_ENV=dev. In production, message and code should be opaque. If the bundle's built-in exception listener exposes getMessage() verbatim, override it with your own subscriber.
  • Ordering with other normalizers. If you use API Platform or FOSRestBundle, their format negotiators can hijack the Accept header before JSend ever sees it. Either disable format_listener for JSON routes or make JSend responses bypass content negotiation by setting Content-Type: application/json directly.
  • Extension points. Because the package is small, the safest customization is to decorate the auto-wired factory with your own wrapper rather than editing vendor code. Symfony's decorates: key makes that trivial now that there is a stable interface to decorate — the v3.0.2 wiring makes this materially easier than wrapping a static helper. Also check Resources/config/services.yml for other services marked public: true — the factory is covered, but anything else the bundle registers may still be private.
  • Debugging. Use Symfony's profiler to inspect the actual response body (dump() in the controller won't show what the listener did). If the envelope looks wrong, var_dump($response->getContent()) in a functional test is faster than clicking through requests. When injection misbehaves, bin/console debug:container JsendStandard\\Interfaces\\ResponseFactoryInterface tells you immediately whether the alias resolved.
  • Maintenance risk. With 14 stars and no dependents, treat this as a utility you own, not a black box. Pin the version in composer.json. That said, v3.0.2 is a responsive, low-risk patch that improves DI ergonomics — a good signal. The JSend spec is a two-page document; if the maintainer goes inactive, a hand-rolled response class implementing the same interface is a few dozen lines.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console