tourze/json-rpc-container-bundle
Symfony bundle that integrates a JSON-RPC container, helping wire PHP services into JSON-RPC endpoints with dependency injection and framework configuration for clean, container-managed RPC APIs.
First, verify what you actually installed. The -bundle suffix is a strong signal this is a Symfony bundle, not a Laravel package — check composer.json for "type": "symfony-bundle" and a require on symfony/http-kernel. If so, you will not get a Laravel service provider, auto-discovery, or vendor:publish; you'll be consuming the underlying JSON-RPC classes with your own wiring.
Minimal steps:
composer require tourze/json-rpc-container-bundle:^2.0 (pin it — see Gotchas).vendor/tourze/json-rpc-container-bundle/ in this order: composer.json → *Bundle.php (shows which services are registered and how) → DependencyInjection/ (shows what's configurable) → src/ (the actual dispatcher/server classes you'll reuse).Server, Handler, or Dispatcher that accepts a method map and a request payload. That class is the whole package; everything else is Symfony plumbing.// routes/api.php
Route::post('/rpc', RpcController::class)->middleware(['auth:sanctum', 'throttle:60,1']);
class RpcController
{
public function __invoke(Request $request, RpcDispatcher $rpc)
{
$result = $rpc->handle($request->json()->all());
return response()->json($result);
}
}
First use case: expose one existing service method (user.get) as a JSON-RPC method and call it from a front-end via POST /api/rpc. Get that single round-trip working before wiring a method registry.
Method registry in config, not in code. Map method names to [class, method] pairs so the handler classes stay plain and testable:
// config/rpc.php
return [
'methods' => [
'user.get' => [\App\Rpc\UserHandler::class, 'get'],
'user.update' => [\App\Rpc\UserHandler::class, 'update'],
],
];
Resolve handlers through the Laravel container. The bundle's Symfony-side container wiring is inert in Laravel. Instead, resolve handlers per call so constructor injection works:
$entry = config("rpc.methods.{$method}");
[$class, $action] = $entry;
$handler = app($class); // constructor DI works
$result = $handler->{$action}($params);
Keep handlers transport-agnostic. Handlers should take a plain array and return arrays/scalars — no Request, no Response. This makes them unit-testable and lets you mount the same handlers behind HTTP, a queue consumer, or a CLI command.
Validate inside the handler and map errors deliberately. JSON-RPC 2.0 reserves codes:
| Code | Meaning | When |
|---|---|---|
| -32700 | Parse error | malformed JSON body |
| -32600 | Invalid Request | not an object / bad jsonrpc field |
| -32601 | Method not found | unknown method name |
| -32602 | Invalid params | validation failure |
| -32603 | Internal error | uncaught exception |
Wrap validation in a RpcException carrying the code; catch it at the controller boundary.
Support batch and notification in one code path. If the payload is a list, iterate and collect responses; if a single request has no id, it's a notification — process it and return HTTP 204 with an empty body. Skipping this is the most common way JSON-RPC servers break JavaScript clients.
Auth and tenancy belong in middleware, not handlers. Put the resolved user/tenant on the container or pass it into the handler constructor:
$this->app->bind(RpcContext::class, fn () => new RpcContext(auth()->user()));
Test with feature tests, not mocks. Post raw JSON and assert the envelope:
$this->postJson('/api/rpc', [
'jsonrpc' => '2.0', 'method' => 'user.get',
'params' => ['id' => 1], 'id' => 1,
])->assertOk()->assertJsonPath('result.id', 1);
Use Http::fake() only when you're on the client side calling a third-party JSON-RPC endpoint.
2.0.0 is a major bump with no release notes. The only artifact is a 1.0.0...2.0.0 compare link. Do not upgrade blind. Before touching composer.json:
https://github.com/tourze/json-rpc-container-bundle/compare/1.0.0...2.0.0composer.json — a major on a low-traffic package usually coincides with dropping old PHP support.The bundle will not auto-register in Laravel. Laravel's package discovery looks for Illuminate\Support\ServiceProvider in extra.laravel.providers. A Symfony bundle has none. If you composer require it and nothing happens, that's expected — you're using it as a library, not an installed package.
HTTP status vs JSON-RPC error. A JSON-RPC error (bad params, unknown method) must be returned with HTTP 200 and an error member in the body. Laravel's exception handler will happily convert your ValidationException into a 422, which strict clients treat as a transport failure. Catch at the controller boundary and render the envelope yourself.
$request->json() returns null on a bad Content-Type. Clients that omit Content-Type: application/json will produce a null payload that looks like a parse error. Read $request->getContent() and json_decode it yourself if you want to be forgiving; otherwise return -32700 explicitly rather than letting a null propagate.
Positional vs named params. JSON-RPC 2.0 allows params to be an array (positional) or an object (named). Most implementations only handle one. Decide which you support, document it, and reject the other with -32602 — silent misbehavior here is painful to debug from the client side.
Don't resolve the container at boot. Building the method map in a service provider's register() and eagerly instantiating handlers will pull every handler's dependencies on every request, including for routes that never touch RPC. Store the map as config and resolve lazily.
Extension points to look for in the source: a method resolver interface (swap in an attribute/annotation-based scanner), an error formatter (normalize envelopes), and a middleware or event hook (attach request IDs / tracing). If 2.0 introduced an interface for any of these, wire to the interface, not the concrete class — that's what will keep you stable through the next major.
Log the raw request body on -32603 only. Logging every payload is noisy and risks leaking parameters; logging nothing makes production incidents unreadable.
How can I help you explore Laravel packages today?