fresh/common-api-bundle
A Symfony bundle that provides shared building blocks for building APIs, bundling common utilities, configuration, and reusable components to speed up development across projects.
First, calibrate expectations. fresh/common-api-bundle is a Symfony bundle, not a Laravel package — there is no service provider, no facade, no config/common-api.php. Even as a Laravel developer you'll be reading Symfony idioms: a Bundle class, a DependencyInjection/*Extension.php with a Configuration tree, and event subscribers wired via Resources/config/services.xml (or services.yaml). If your stack is Laravel-only, treat this repo as a source of patterns to port, not a dependency to install.
Minimal install (Symfony host app):
composer config repositories.common-api vcs https://bitbucket.org/fre5h/commonapibundle.git
composer require fre5h/common-api-bundle:@dev
Pin it explicitly — the package has no stars, no dependents, and likely no tagged release. @dev against a Bitbucket VCS repo means your build breaks the day the branch is force-pushed.
Then register it in config/bundles.php:
return [
// ...
Fre5h\CommonApiBundle\CommonApiBundle::class => ['all' => true],
];
Where to look first, in this order:
composer.json — the require block tells you which Symfony versions are actually supported. This is the single most important file; a stale constraint (^3.4 || ^4.0) means the bundle won't boot on a modern kernel.DependencyInjection/Configuration.php — the full config surface. If it's absent, there is nothing to configure and everything is convention-driven.EventSubscriber/ / EventListener/ — a "common API bundle" is almost always a thin set of listeners. This directory is the package.Resources/config/ — service definitions and any tag priorities, which determine behavior against other bundles.First use case: JSON error responses. The typical gap this class of bundle fills is turning uncaught exceptions into a consistent {"error": {...}} payload instead of Symfony's HTML error page. Point a request at a route that throws, and diff the response before/after enabling the bundle. That single test tells you whether the bundle earns its place.
Exception → JSON response listener. The core pattern is a listener on kernel.exception (or a kernel.event_subscriber) that inspects the exception and writes a normalized body. Expect a handler/registry service you can extend:
// Conceptual shape — verify against the actual class names in EventSubscriber/
final class ApiExceptionListener
{
public function onKernelException(ExceptionEvent $event): void
{
$e = $event->getThrowable();
$status = $e instanceof HttpExceptionInterface ? $e->getStatusCode() : 500;
$event->setResponse(new JsonResponse([
'error' => ['code' => $status, 'message' => $e->getMessage()],
], $status));
}
}
The Laravel analogue is your App\Exceptions\Handler::render() returning response()->json(...). If you only need this, copy the pattern into a Laravel exception renderer and skip the dependency entirely.
Request body → object listener. Common second feature: a kernel.request listener that decodes application/json payloads into the request attributes or a DTO, so controllers receive structured input instead of $request->getContent(). This maps to a Laravel FormRequest/prepareForValidation pipeline. Register it with a high priority so it runs before routing-dependent listeners.
Consistent collection/pagination envelope. Watch for a wrapper that normalizes list responses ({data: [...], meta: {...}}). Adopt it application-wide or not at all — half-enveloped APIs are worse than no envelope.
Wiring and extension tips:
decorates: fre5h.common_api.some_service lets you swap behavior without forking.services.yaml with the same id.bin/console debug:event-dispatcher kernel.exception before trusting the output.WebTestCase hitting a deliberately-throwing test route — assert status code and Content-Type: application/json.Maintenance risk is the headline. Zero stars, zero dependents, no visible release history. Assume no upstream fixes. Vendor the relevant classes into src/ (with attribution) rather than adding a VCS dependency to composer.json; it costs you an hour and removes a permanent supply-chain and build-reproducibility hazard.
Symfony version drift. Bundles of this vintage commonly extend Symfony\Component\HttpKernel\Bundle\Bundle fine but break on EventDispatcherInterface signature changes (Symfony 5→6 removed the $eventName argument from dispatch()). Run bin/console cache:clear first — container compile errors surface here, and the message usually names the offending service definition.
Listener priority collisions. If another bundle also handles kernel.exception, the lowest-priority listener wins the response (the first one to call setResponse() short-circuits the rest). If you see HTML errors, the order is wrong — adjust with priority: on the tag.
Empty or charset-suffixed bodies. application/json; charset=utf-8 and zero-length bodies are the two classic decode crashes in request listeners. Wrap json_decode and return 400 rather than letting it become a 500.
Information leakage. Any default handler that puts $e->getMessage() in the response will leak stack-adjacent detail in production. Gate on kernel.debug and emit a generic message plus a correlation id when debug is off.
Debugging commands worth memorizing:
bin/console debug:container --tag=kernel.event_subscriber
bin/console debug:event-dispatcher kernel.exception
bin/console debug:config common_api # only if an extension exists
Extension points to check before forking: a configurable error-code map, a pluggable normalizer/serializer service, and tag-based handler registration. If none of those exist, the bundle is a closed box — that alone justifies reimplementing the ~100 lines yourself.
Laravel translation note: everything here is trivially portable. The exception listener becomes Handler::render(), the request-body listener becomes middleware, and the envelope becomes an API Resource. Unless you are maintaining a Symfony service, the correct decision for a Laravel team is almost always "read it, don't require it."
How can I help you explore Laravel packages today?