bednic / json-rpc
JSON RPC 2.0 implementation
Requires
- php: ^8.0
- ext-json: *
Requires (Dev)
- phpunit/phpunit: ^11.5
- squizlabs/php_codesniffer: ^3.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
PHP implementation of the JSON-RPC 2.0 specification.
composer require bednic/json-rpc
Server
- Create a controller implementing
\JSONRPC\ProcedureControllerInterface. Its public methods are the procedures. - Pass the controller to
\JSONRPC\Dispatcher. - Call
Dispatcher::dispatch(string $json)with the raw request body. - Send the returned value encoded by
json_encodeto the client. It is aResponsefor a single request, an array ofResponsefor a batch, ornullwhen there is nothing to answer (only notifications).
class MathController implements \JSONRPC\ProcedureControllerInterface
{
public function subtract(int $minuend, int $subtrahend): int
{
return $minuend - $subtrahend;
}
}
$dispatcher = new \JSONRPC\Dispatcher(new MathController());
$response = $dispatcher->dispatch(file_get_contents('php://input'));
if ($response === null) {
http_response_code(204);
} else {
header('Content-Type: ' . \JSONRPC\Message::CONTENT_TYPE);
echo json_encode($response);
}
Params can be passed by position ("params": [42, 23]) or by name ("params": {"subtrahend": 23, "minuend": 42}).
JSON objects are passed to procedures as stdClass.
Multiple controllers
Register controllers under a prefix and call them as [prefix].[method], e.g. math.subtract.
Prefix rpc is reserved by the specification.
$controllers = new \JSONRPC\SimpleControllerCollector();
$controllers->addController('math', new MathController());
$controllers->addController('user', new UserController());
$dispatcher = new \JSONRPC\Dispatcher($controllers);
Errors
Throw an exception implementing \JSONRPC\ProcedureException to send its code, message and data to the client.
Codes from -32768 to -32000 are reserved by the specification, using them is logged by error_log.
class NotFound extends \Exception implements \JSONRPC\ProcedureException
{
public function __construct(private int $userId)
{
parent::__construct('User not found', 404);
}
public function getData(): mixed
{
return ['userId' => $this->userId];
}
}
Any other exception is answered with -32603 Internal error without details, so nothing leaks to the client.
Pass a callback to log it:
$dispatcher = new \JSONRPC\Dispatcher($controller, null, fn(\Throwable $e) => $logger->error($e));
Notifications (requests without id) are never answered, not even with an error.
Client
$request = new \JSONRPC\Request('math.subtract', 1, [42, 23]);
$notification = new \JSONRPC\Request('log', null, ['hello']); // no id, no response expected
$body = json_encode($request); // or json_encode([$request, $notification]) for a batch
$response = \JSONRPC\Response::jsonDeserialize($reply); // Response, or Response[] for a batch
if ($response->getError() !== null) {
echo $response->getError()->getMessage();
} else {
echo $response->getResult()->getData(); // 19
}
Response::jsonDeserialize throws UnexpectedValueException when the reply is not a valid JSON-RPC 2.0 response.
Example
See tests in this repo.