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

Api Rest Utils Laravel Package

everycheck/api-rest-utils

A Symfony utility package offering a JSON response builder with common API statuses and custom headers, plus a paginated repository trait for filtering, ordering, and searching entities across joins.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're actually installing. This is a small, archived Symfony helper library, not a framework-agnostic toolkit. The whole surface area is roughly two things: a ResponseBuilder that wraps jms_serializer and returns Symfony\Component\HttpFoundation\Response objects, and a Doctrine repository trait for filtered/ordered pagination. There are no dependents and one star, so treat it as reference code you may vendor rather than a dependency you trust blindly.

Install and inspect first.

composer require everycheck/api-rest-utils

Before writing any code, open vendor/everycheck/api-rest-utils/src/ and read:

  1. ResponseBuilder.php — the constructor signature (the README shows new ResponseBuilder($this->get('jms_serializer'))), the exact status codes each method returns, and the JSON envelope shape ({"data": ...}, {"errors": ...}, or raw).
  2. The repository trait file — how it builds the query, whether it uses DQL parameter binding, and what the pagination response looks like.
  3. composer.json — PHP version constraint, JMS serializer version, and autoload namespace. If it's not registered as a Symfony bundle, don't add it to bundles.php; if it is, the README omission is a docs gap.

Register JMS serializer if you don't have it. The builder is useless without jms/serializer-bundle configured, since it delegates all serialization. That means at minimum a jms_serializer service and, realistically, metadata config for your entities.

First use case — the standard controller action. The canonical pattern from the README:

public function getAction($id)
{
    $response = new ResponseBuilder($this->get('jms_serializer'));
    $entity = $this->getDoctrine()->getManager()
        ->getRepository(Entity::class)->find($id);

    if (empty($entity)) {
        return $response->notFound();
    }

    return $response->ok($entity);
}

Start by migrating one read-only endpoint. Confirm the JSON shape and status codes match what your frontend already expects before touching write endpoints — that's where the created/deleted/conflict/formError semantics matter and where surprises live.

Implementation Patterns

Inject the builder, don't rebuild it. The README constructs ResponseBuilder inside each action, which is noisy and hides the dependency. In Symfony, register it as a service and inject it into controllers:

# services.yaml
Everycheck\ApiRestUtils\ResponseBuilder:
    arguments: ['@jms_serializer']
    public: true

Then type-hint it in the action. This also gives you one place to subclass or decorate if you need an envelope change.

Early-return guard clauses with header chaining. addHeaders returns $this, so headers must be applied before the terminal call — ->addHeaders(...)->ok($data) works, ->ok($data)->addHeaders(...) silently does nothing useful.

public function show($id)
{
    $entity = $this->repo->find($id);

    if (!$entity) {
        return $this->response->addHeaders('X-Request-Id', $rid)->notFound();
    }

    return $this->response
        ->addHeaders('X-Request-Id', $rid)
        ->addHeaders('Cache-Control', 'no-store')
        ->ok($entity);
}

Build a small base controller or trait that generates a request ID and applies standard headers so every endpoint is consistent.

Form errors as a first-class contract. formError is the one method with real logic in it — it flattens Symfony's FormInterface::getErrors() tree into a field-keyed JSON structure. Standardize on it across all mutating endpoints and let your client render field-level errors from one shape. Anything reaching for ->badRequest($form->getErrors()) raw is fighting the library's purpose.

Repository trait for list endpoints. The second half of the README covers a "find paginated with filter and order by" trait. The workflow:

// src/Repository/EntityRepository.php
use Everycheck\ApiRestUtils\Repository\PaginatedRepositoryTrait;

class EntityRepository extends EntityRepository
{
    use PaginatedRepositoryTrait;
}

Then bind request query params (page, limit, sort, order, plus filter keys) to the trait's method in the controller. The value here is that filter/order handling is centralized instead of reimplemented per repository — which is exactly the kind of duplication that rots in API projects.

Compose a small response layer around it. Because ResponseBuilder only knows about serialization and status codes, the natural extension is a thin application-level service that:

  • maps your domain errors (not-found vs. forbidden vs. conflict) to the right builder method,
  • attaches pagination metadata to ok() payloads via headers (X-Total-Count, Link) since the builder has no envelope for it,
  • normalizes validation errors from non-Symfony-Form sources.

In a Laravel codebase, the direct analogue is replacing scattered response()->json([...], 404) calls with a single ApiResponder class exposing the same verb-based methods. You get the same day-to-day benefit — one status-code vocabulary across the team — without the Symfony coupling.

Gotchas and Tips

Archived and effectively unmaintained. No dependents, one star, README-only maturity. Do not expect bug fixes, security patches, or compatibility updates for new Symfony major versions. Pin the version and plan to vendor or reimplement. For a package this small, copying ResponseBuilder.php (a few hundred lines at most) into your app is often cheaper than carrying a dead dependency.

It is Symfony-only in practice. Everything is built on HttpFoundation + jms_serializer. In Laravel, symfony/http-foundation is already present transitively and jms/serializer works standalone, so you can use the response class — but you'd be bypassing Laravel's Response pipeline, middleware expectation of Illuminate\Http\Response, and JsonResource. In Laravel the honest play is to read the source and port the status-code mapping, not to install it.

empty is a legal method name only from PHP 7.0. The builder exposes ->empty(). It works on modern PHP, but linters, older static analyzers, and IDE autocomplete occasionally choke on it. Don't rename it in a vendored copy unless you're also updating callers.

notFound() returns a Response, not an exception. This means no exception listener, no unified error logging, no consistent trace for 404s. If you rely on Symfony's kernel.exception handling for logging/alerting, these responses will bypass it. Either accept that or wrap calls so failures funnel through an exception subscriber.

JMS serializer will bite on Doctrine relations. ->ok($entity) with a lazy-loaded bidirectional relation typically produces circular-reference exceptions or enormous payloads. Configure serialization groups / exclusion policies and, where the builder allows it, pass a context. Check whether the constructor or methods accept a $context argument — if not, you'll be configuring globally via JMS metadata, which is coarse.

forbiddenAcl vs forbiddenRoute vs forbidden is a leak surface. Distinguishing "you're not authenticated for this route" from "you lack ACL for this resource" is useful internally but can disclose resource existence to clients. Audit which one you call on public endpoints; consider collapsing to forbidden externally and logging the distinction.

Verify the pagination trait's filter handling before trusting it. A trait that accepts arbitrary query keys can either throw on unknown columns (500s) or, worse, interpolate them into DQL/SQL. Read the implementation, then wrap it: whitelist allowed filter fields and sort columns per repository, and cap limit (the trait likely won't). Also check the total-count query is a separate COUNT() rather than counting the hydrated page.

No tests means you own verification. There is no CI surface to lean on. Before rolling it out, write characterization tests asserting the exact status code and body shape of all fifteen response methods, plus one test per list endpoint covering filter/order/pagination bounds. That test file is the real documentation — the README omits method signatures and payload shapes entirely.

Header order and immutability. ResponseBuilder mutates itself and returns $this. If you hold one instance as an injected service and issue two responses in a single request (rare, but happens in error paths and sub-requests), headers leak between them. Register it as a non-shared service, or instantiate per action as the README does.

Extension points are thin but exist. Subclass ResponseBuilder to override the envelope or add methods (accepted, noContent if missing), and register the subclass as the service. Similarly, the repository trait can be overridden by defining the same method in the class that uses it — trait methods lose to class methods, so that's your escape hatch when a trait default doesn't fit.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings