mi/rest-extra-bundle
A Symfony bundle that extends your application's REST API capabilities with reusable helpers and services for building, structuring, and customizing JSON endpoints, reducing boilerplate and keeping API code consistent across projects of any size.
Reality check for a Laravel developer first. mi/rest-extra-bundle is a Symfony bundle, not a Laravel package. Laravel consumes Symfony components (HttpFoundation, Routing, Console), but not bundles — there is no config/bundles.php, no DependencyInjection\Extension, and no kernel to register it against. You cannot composer require mi/rest-extra-bundle into a Laravel app and get behavior. The value of this package to you is as a reference implementation of a pattern you would rebuild in Laravel middleware, plus a cautionary tale about annotation-free controller magic.
What it actually does. The bundle exists to remove two annotations that were ubiquitous in Symfony REST APIs circa 2015–2019:
@ParamConverter from SensioFrameworkExtraBundle — which hydrated a controller argument from the request (route params, or the request body via FOSRestBundle's body listener).@View from FOSRestBundle — which told the view layer to serialize the controller's return value and set the status code / content type.Instead of annotating every action, the bundle registers event listeners that infer the intent from the controller's signature and the request: type-hint a DTO/entity on a write action and the body is deserialized into it; return a domain object and it is serialized with the negotiated format. It is "convention over annotation," implemented with kernel.request / kernel.view subscribers plus reflection.
Where to look first. Clone the repo and read, in this order:
MiRestExtraBundle.php and DependencyInjection/ — see what services get registered and under what config key (mi_rest_extra is the likely root, but confirm in the extension; with no Flex recipe you register the bundle manually in config/bundles.php).EventListener/ or EventSubscriber/ — this is the whole product. The request listener is the "deserialize into typed argument" half; the view listener is the "serialize return value" half.Resources/config/services.* — check the Symfony version constraints on the service definitions. A 2021 release means the YAML/XML will be written against Symfony 4/5 idioms.First use case worth stealing. The kernel.view half. In Laravel terms it is roughly this, and it is the piece with the best cost/benefit:
// app/Http/Middleware/SerializesResponses.php (conceptual)
return $next($request);
// if the controller returned a plain array/DTO, wrap it:
// ['data' => $result] -> response()->json(..., 200)
// null -> response()->noContent()
// and derive the status from the HTTP verb when the result is an entity
Laravel's JsonResource + response()->json() already covers most of this, so the honest conclusion is: read the bundle for the idea, implement it with Route::model(), FormRequest, and JsonResource, and move on.
The three patterns the bundle encodes, and their Laravel-native equivalents:
1. Body → typed argument (the @ParamConverter replacement).
Symfony shape: a kernel.request listener inspects the controller via _controller reflection, finds a parameter whose class is not a Doctrine entity and not a service, and deserializes $request->getContent() into it with the Serializer. Laravel shape:
// Option A — FormRequest (validation + hydration in one, and it's what your team already knows)
public function store(StoreOrderRequest $request) {
$order = Order::create($request->validated());
return new OrderResource($order);
}
// Option B — DTO hydration, closest analogue to the bundle's behavior
// composer require spatie/laravel-data
public function store(StoreOrderData $data) { ... } // resolved from container, validated internally
For route-parameter conversion (the other half of @ParamConverter), Laravel's implicit binding covers it: Route::get('/orders/{order}', ...) with Order $order in the signature. Explicit bindings via Route::bind() when you need custom resolution (soft-deleted, tenant-scoped, slug-based).
2. Return value → negotiated response (the @View replacement).
Laravel: return an ApiResource / ResourceCollection, or a plain array (Laravel JSON-encodes it automatically). Content negotiation is opt-in via $request->expectsJson(); the bundle does it automatically from the Accept header. If you want the automatic behavior, add a middleware that forces JSON for your /api/* prefix and sets Accept: application/json — that is the one-line version of what FOSRestBundle's format listener does.
3. Convention-based routing / verb mapping.
The bundle pairs with FOSRestBundle's automatic route generation (fos_rest.routing.loader, resource-based REST routes). Laravel equivalent is just Route::apiResource(). Nothing to port.
Workflow if you are actually maintaining a Symfony app that uses this:
bin/console debug:event-dispatcher kernel.view to confirm the listener is attached and at which priority — this is how you verify the integration in five seconds.kernel.request (before FOSRestBundle's own body listener) and late in kernel.view (after your own transformers), or the ordering will silently produce double-deserialization.Integration tips that transfer to Laravel:
"mi/rest-extra-bundle": "1.0.*") if you vendor it. With zero dependents there is no community pressure to keep BC.Content-Type, malformed JSON, and a controller argument that is an interface. These are where reflection-driven hydration always breaks.The package's core assumption is now obsolete. SensioFrameworkExtraBundle reached end-of-life and is not compatible with Symfony 6.2+; Symfony shipped first-party attributes (#[MapRequestPayload], #[MapQueryString], #[MapQueryParameter], #[IsGranted], #[Cache]) that do exactly what this bundle did, but as opt-in attributes. FOSRestBundle itself is effectively in maintenance mode. So the problem the package solved has been absorbed upstream — which is almost certainly why it sits at zero stars and zero dependents with a 2021 release.
Zero stars / zero dependents / last release 2021 is a maintenance verdict, not a quality verdict. Treat it as unmaintained: no security response, no PHP 8.2/8.3 compatibility guarantees, no Symfony 6/7 service-definition updates. The common breakages in bundles of this vintage:
EventSubscriberInterface and EventDispatcherInterface method signatures tightened in Symfony 5/6.ContainerAware* base classes removed in Symfony 6.null to non-nullable internals).Reflection-based argument hydration has sharp edges. Type-hint based inference cannot resolve union types, mixed, interfaces, variadics, or nullable scalars. It also silently does nothing when the class is a service — so a controller action that takes a DTO and a service will behave inconsistently if the listener's heuristics are wrong. Always check whether the type is Doctrine-managed (entity) versus a transport DTO; the bundle and FOSRestBundle both claim the entity case and will race.
Content-type vs. accept-header confusion. These bundles key off two different headers: the body deserializer needs Content-Type (application/json, application/x-www-form-urlencoded), the response serializer needs Accept. A client sending Accept: */* will get a format you did not intend. In Laravel terms: this is why you should force JSON in middleware for API routes rather than trusting expectsJson().
Empty bodies and 204s are the classic failure. The view listener tries to serialize null, the request listener tries to deserialize "". Both need explicit guards; the bundle's era of code often did not have them. Test DELETE and PUT with no body first.
Debugging recipe. bin/console debug:event-dispatcher, then bin/console debug:container --tag=kernel.event_subscriber, then a dump()/dd() inside the listener if you forked it. There is no logging or config dump for this bundle.
Extension points, such as they are. The bundle is small; the intended extension points are (a) override the serializer/normalizer service, (b) decorate the listener service, (c) fork. There is no documented public API, no tests to read as spec, and no CHANGELOG discipline you can rely on. Assume (c).
Laravel-specific bottom line. Do not try to graft this on. The nearest day-to-day equivalents you already have:
| Bundle feature | Laravel equivalent |
|---|---|
@ParamConverter on body |
FormRequest / spatie/laravel-data |
@ParamConverter on route param |
Implicit route model binding, Route::bind() |
@View + format negotiation |
JsonResource, response()->json(), expectsJson(), forced-JSON middleware |
| FOSRestBundle auto routes | Route::apiResource() |
| Exception → HTTP status mapping | Handler::render() + custom HttpExceptions |
If you are assessing this package to decide whether to adopt it: the answer is no, for a Laravel codebase it is inapplicable, and for a Symfony codebase you should use Symfony's native #[MapRequestPayload] instead. Its remaining value is as a compact, readable illustration of convention-over-annotation in a controller layer — roughly 300 lines of idea, worth thirty minutes of reading.
How can I help you explore Laravel packages today?