alcedo / json-rpc-server
JSON-RPC server for executing functions remotely.
Installs: 41
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/alcedo/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
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 alcedo/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 or to instances implementing RemoteProcedureInterface.
use Alcedo\JsonRpc\Server\Server; use Alcedo\JsonRpc\Server\Factory\RequestFactory; use Alcedo\JsonRpc\Server\RemoteProcedureInterface; use Alcedo\JsonRpc\Server\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; }, // Object procedure: implement RemoteProcedureInterface 'remote.ok' => new class implements RemoteProcedureInterface { public function call(): Response { return new Response(result: 'ok', id: 123); } }, ]; // 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 Alcedo\JsonRpc\Server\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 Alcedo\JsonRpc\Server\DTO\BatchResponse and is countable
How it works
Core types under Alcedo\JsonRpc\Server\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:
RemoteProcedureInterface— Implementcall(): Responseto provide fully controlled JSON-RPC responses from objects.- Callables — Any PHP callable is allowed; its return value becomes
resultand exceptions are converted tointernal error.
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..
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)
Object procedure (RemoteProcedureInterface)
class HelloProc implements RemoteProcedureInterface { public function call(): Response { return new Response(result: 'hello', id: 7); } } $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)
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
- RemoteProcedureInterface::call() accepts no parameters; if you need params, you can add them to the implementing class with default values.
- 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.