alcedo-rml / json-rpc-server
JSON-RPC server for executing functions remotely.
Package info
github.com/reactive-mnemonic-layers/json-rpc-server
pkg:composer/alcedo-rml/json-rpc-server
Requires
- php: >=8.2
- psr/container: ^2.0
- psr/http-message: ^2.0
Requires (Dev)
- phpdocumentor/phpdocumentor: ^3.8
- phpmd/phpmd: ^2.15
- phpunit/phpunit: ^11.0
- squizlabs/php_codesniffer: ^4.0
This package is auto-updated.
Last update: 2026-08-14 15:23:56 UTC
README
A lightweight, PSR-friendly JSON-RPC 2.0 server for executing functions or objects remotely. It supports single and batch requests, notifications (no response), PSR-7 requests, and error handling compliant with the JSON-RPC 2.0 specification.
- PHP 8.2+
- PSR-7
RequestInterfacesupport - PSR Container for procedure lookups
- Strict DTOs for Requests/Responses/Errors
- Batch requests and notifications
Installation
Install via Composer:
composer require rml/json-rpc-server
Requirements:
- PHP >= 8.2
- psr/http-message ^2.0
- psr/container ^2.0
Quick start
1) Register procedures in a PSR Container
Map method names to callables.
use Alcedo\Rml\JsonRpc\Server; use Alcedo\Rml\JsonRpc\Factory\RequestFactory; use Alcedo\Rml\JsonRpc\DTO\Response; use Psr\Container\ContainerInterface; $map = [ // Callable procedure: parameters will be passed from the JSON-RPC params array 'sum' => function (int $a, int $b): int { return $a + $b; }, // Class with __invoke(): 'remote.ok' => new class { public function __invoke(): string { return 'ok'; } }, ]; // Example minimal container wrapper for the static map $container = new class($map) implements ContainerInterface { public function __construct(private array $map) {} public function has(string $id): bool { return array_key_exists($id, $this->map); } public function get(string $id) { return $this->map[$id]; } }; $server = new Server(new RequestFactory(), $container);
2) Execute a single request from an array
$response = $server->executeArrayRequest([ 'jsonrpc' => '2.0', 'method' => 'sum', 'id' => 1, 'params' => [2, 3], ]); // $response is Rml\JsonRpc\DTO\Response json_encode($response); // {"jsonrpc":"2.0","result":5,"id":1}
3) Execute a PSR-7 request (single or batch)
Server::executePsrRequest() will parse the PSR-7 body (JSON) and handle single or batch automatically.
use Psr\Http\Message\RequestInterface; /** @var RequestInterface $psrRequest */ $rpcResponse = $server->executePsrRequest($psrRequest); // Single request -> Response|null // Batch request -> BatchResponse
4) Notifications (no id)
Requests without id are treated as notifications and return null, though the procedure is executed.
$result = $server->executeArrayRequest([ 'jsonrpc' => '2.0', 'method' => 'notify', // no id -> notification ]); // $result === null
5) Batch requests
Provide an array of requests; notifications are omitted from the resulting BatchResponse.
$rpcResponse = $server->executePsrRequest($psrRequest); // body contains JSON array // $rpcResponse is Rml\JsonRpc\DTO\BatchResponse and is countable
How it works
Core types under Rml\JsonRpc\DTO:
Request— JSON-RPC request with method, params, optional id. Validates method names do not start with the reservedrpc.prefix.Response— JSON-RPC response carrying eitherresultorerror(never both). Provides helpersisError()/isSuccess().Error— JSON-RPC error withcode,message, and optionaldata.BatchRequest— Array-like collection ofRequestorErroritems. Validates element types.BatchResponse— Array-like collection ofResponseitems. Validates element types.ErrorCodes— Enum for standard JSON-RPC error codes and server error range.JsonRpcTrait— ProvidesjsonRpc()returning protocol version2.0.
Factories:
RequestFactory— BuildsRequest/BatchRequestfrom PSR-7 request body or arrays. Maps invalid items within a batch toErrorentries.ErrorFactory— Convenience constructors for errors: parse, invalid request, method not found, invalid params, internal error, server error.
Server:
Server— Executes requests using a PSR Container to resolve procedures by method name. Supports:executeArrayRequest(array $request): Response|BatchResponse|nullexecutePsrRequest(RequestInterface $request): Response|BatchResponse|nullexecute(Request|BatchRequest $request): Response|BatchResponse|null
Procedures:
- Callables — Any PHP callable is allowed; its return value becomes
result(unless it already returns aResponseobject) and exceptions are converted tointernal error.
ProceduresCollection:
ProceduresCollection— AContainerInterfaceimplementation that lets you register procedures as[serviceName]or[serviceName, methodName]pairs instead of resolving them upfront. Services are looked up lazily in an underlyingprovidercontainer (e.g. a DI container/service locator) the first time a procedure is requested, then cached for subsequent calls. Useful when procedures map to services or service methods that should not be instantiated until they are actually invoked.
Error handling
The server adheres to JSON-RPC 2.0 error semantics using ErrorCodes and ErrorFactory:
PARSE_ERROR (-32700)— Invalid JSON in PSR-7 body.INVALID_REQUEST (-32600)— Missing or malformed fields (e.g., missingmethod).METHOD_NOT_FOUND (-32601)— Procedure missing in the container.INVALID_PARAMS (-32602)— For parameter issues (factory available, not auto-generated by server).INTERNAL_ERROR (-32603)— Exceptions thrown by callables are wrapped with the original message.SERVER_ERROR (-32099…-32000)— Generic server-side errors (e.g., non-callable procedure), produced withErrorFactory::serverError().
Transformations and exceptions:
ErrorException::fromErrorCode()can be turned into anErrorvia$exception->toError().InvalidResponseException— Thrown if aResponseis constructed with bothresultanderror.InvalidBatchElementException— Thrown when invalid items appear in batch collections.InvalidMethodNameException— Thrown when aRequestmethod starts withrpc..ProcedureNotFoundException(implementsPsr\Container\NotFoundExceptionInterface) — Thrown byProceduresCollection::get()when the procedure id or its underlying service is not registered.InvalidRemoteProcedureException(implementsPsr\Container\ContainerExceptionInterface) — Thrown byProceduresCollection::get()when the resolved service/method is not callable.
Examples
Callable procedure
$server = new Server(new RequestFactory(), $container); $response = $server->executeArrayRequest([ 'jsonrpc' => '2.0', 'method' => 'sum', 'id' => 1, 'params' => [10, 5] ]); // Response(result: 15, id: 1)
Class procedure (__invoke)
class HelloProc { public function __invoke(): string { return 'hello'; } } $map = ['hello' => new HelloProc()]; $server = new Server(new RequestFactory(), new ArrayContainer($map)); $response = $server->executeArrayRequest(['jsonrpc' => '2.0', 'method' => 'hello', 'id' => 7]); // Response(result: 'hello', id: 7)
Lazy procedure resolution with ProceduresCollection
Register procedures as [serviceName] or [serviceName, methodName] pairs and let
ProceduresCollection resolve them from a provider container on first use.
use Alcedo\Rml\JsonRpc\ProceduresCollection; use Alcedo\Rml\JsonRpc\Server; use Alcedo\Rml\JsonRpc\Factory\RequestFactory; use Psr\Container\ContainerInterface; // $provider resolves service ids to service instances, e.g. a DI container. /** @var ContainerInterface $provider */ $procedures = new ProceduresCollection($provider, [ 'sum' => ['calculator', 'add'], // calls $provider->get('calculator')->add(...) 'ping' => ['pingService'], // calls $provider->get('pingService')(...) ]); // Additional procedures can be registered at runtime. $procedures->add('hello', ['greeterService', 'sayHello']); $server = new Server(new RequestFactory(), $procedures); $response = $server->executeArrayRequest([ 'jsonrpc' => '2.0', 'method' => 'sum', 'id' => 1, 'params' => [2, 3], ]);
Batch via PSR-7 request
$body = json_encode([ ['jsonrpc' => '2.0', 'method' => 'sum', 'id' => 1, 'params' => [1, 2]], ['jsonrpc' => '2.0', 'method' => 'hello', 'id' => 2], ['jsonrpc' => '2.0', 'method' => 'notify'], // notification => omitted in response ]); $psrRequest = new \GuzzleHttp\Psr7\Request('POST', '/', [], $body); $batch = $server->executePsrRequest($psrRequest); // BatchResponse
Notes and caveats
- Notifications (no id) return null but still execute the target procedure.
- Batch responses exclude notifications by design, as per JSON-RPC 2.0.
Requestrejects method names starting withrpc.to reserve the prefix for internal use.
Development
Run tests with PHPUnit:
vendor/bin/phpunit
Coding standards:
- PHP_CodeSniffer (PSR-12) via
vendor/bin/phpcs - PHPMD via
vendor/bin/phpmd
License
MIT License. See LICENSE for details.