Skip to content

Clients

Create a client using the static factory method:

use Cline\RPC\Clients\Client;
$client = Client::json('https://api.example.com/rpc');

For legacy XML-RPC endpoints:

$client = Client::xml('https://api.example.com/xmlrpc');

Create a client with custom protocol:

use Cline\RPC\Protocols\JsonRpcProtocol;
$client = Client::create(
'https://api.example.com/rpc',
new JsonRpcProtocol()
);

Make a single RPC call:

use Cline\RPC\Data\RequestObjectData;
$client = Client::json('https://api.example.com/rpc');
$request = RequestObjectData::asRequest(
'app.user_get',
['user_id' => 42],
1
);
$response = $client->add($request)->request();
if ($response->hasResult()) {
$user = $response->result;
echo $user['name'];
}
if ($response->hasError()) {
$error = $response->error;
echo "Error {$error->code}: {$error->message}";
}

Send a notification (no response expected):

$request = RequestObjectData::asNotification(
'app.log_event',
['event' => 'user_login', 'user_id' => 42]
);
$client->add($request)->request();
$response = $client
->add(
RequestObjectData::asRequest(
'app.user_get',
['user_id' => 42],
1
)
)
->request();

Execute multiple requests in a single HTTP call:

$client = Client::json('https://api.example.com/rpc');
$responses = $client
->add(
RequestObjectData::asRequest(
'app.user_get',
['user_id' => 1],
1
)
)
->add(
RequestObjectData::asRequest(
'app.user_get',
['user_id' => 2],
2
)
)
->add(
RequestObjectData::asRequest(
'app.user_list',
['cursor' => ['limit' => 10]],
3
)
)
->request();
// Responses is a DataCollection
foreach ($responses as $response) {
if ($response->hasResult()) {
var_dump($response->result);
}
}

Add multiple requests at once:

$requests = [
RequestObjectData::asRequest(
'app.user_get',
['user_id' => 1],
1
),
RequestObjectData::asRequest(
'app.user_get',
['user_id' => 2],
2
),
];
$responses = $client->addMany($requests)->request();

Match responses by ID:

$responses = $client
->add(
RequestObjectData::asRequest(
'app.user_get',
['user_id' => 1],
'user-1'
)
)
->add(
RequestObjectData::asRequest(
'app.post_get',
['post_id' => 100],
'post-100'
)
)
->request();
foreach ($responses as $response) {
match ($response->id) {
'user-1' => $this->handleUser($response->result),
'post-100' => $this->handlePost($response->result),
};
}
$response = $client->add($request)->request();
if ($response->hasResult()) {
// Success - process result
$data = $response->result;
}
if ($response->hasError()) {
// Error occurred
$error = $response->error;
echo "Code: {$error->code}";
echo "Message: {$error->message}";
if ($error->data) {
var_dump($error->data);
}
}

JSON-RPC 2.0 defines standard error codes:

if ($response->hasError()) {
$error = $response->error;
match ($error->code) {
-32700 => 'Parse error',
-32600 => 'Invalid request',
-32601 => 'Method not found',
-32602 => 'Invalid params',
-32603 => 'Internal error',
default => "Application error: {$error->message}",
};
}
$client = Client::json('https://api.example.com/rpc');
$cursor = null;
$allUsers = [];
do {
$response = $client
->add(
RequestObjectData::asRequest(
'app.user_list',
[
'cursor' => [
'limit' => 50,
'cursor' => $cursor,
],
],
1
)
)
->request();
$result = $response->result;
$allUsers = array_merge($allUsers, $result['data']);
$cursor = $result['meta']['next_cursor'] ?? null;
} while ($cursor !== null);
echo "Fetched " . count($allUsers) . " users";

Fetch multiple pages simultaneously:

$client = Client::json('https://api.example.com/rpc');
// Fetch first 3 pages in parallel
$responses = $client
->add(
RequestObjectData::asRequest(
'app.user_list',
['cursor' => ['limit' => 50, 'cursor' => null]],
1
)
)
->add(
RequestObjectData::asRequest(
'app.user_list',
['cursor' => ['limit' => 50, 'cursor' => 'page2']],
2
)
)
->add(
RequestObjectData::asRequest(
'app.user_list',
['cursor' => ['limit' => 50, 'cursor' => 'page3']],
3
)
)
->request();
$allUsers = $responses->flatMap(fn($r) => $r->result['data']);

Customize the underlying HTTP client:

use Illuminate\Support\Facades\Http;
$client = new Client(
host: 'https://api.example.com/rpc',
protocol: new JsonRpcProtocol()
);
// Access the underlying HTTP client
$httpClient = Http::baseUrl('https://api.example.com/rpc')
->timeout(30)
->withHeaders([
'Authorization' => 'Bearer ' . $token,
'Content-Type' => 'application/json',
])
->retry(3, 100);

Handle errors gracefully with retries:

function callWithRetry(Client $client, RequestObjectData $request, int $maxRetries = 3): mixed
{
$attempt = 0;
while ($attempt < $maxRetries) {
$response = $client->add($request)->request();
if ($response->hasResult()) {
return $response->result;
}
if ($response->hasError()) {
$error = $response->error;
// Don't retry client errors
if ($error->code >= -32602 && $error->code <= -32600) {
throw new Exception("Client error: {$error->message}");
}
// Retry server errors
$attempt++;
sleep(pow(2, $attempt)); // Exponential backoff
}
}
throw new Exception('Max retries exceeded');
}

Create a typed client for your API:

<?php
namespace App\Services;
use Cline\RPC\Clients\Client;
use Cline\RPC\Data\RequestObjectData;
class UserApiClient
{
private Client $client;
private int $requestId = 1;
public function __construct(string $baseUrl, string $apiToken)
{
$this->client = Client::json($baseUrl);
// Configure authentication here
}
public function getUser(int $userId): array
{
$response = $this->client
->add(
RequestObjectData::asRequest(
'app.user_get',
['user_id' => $userId],
$this->requestId++
)
)
->request();
if ($response->hasError()) {
throw new Exception($response->error->message);
}
return $response->result;
}
public function listUsers(int $limit = 20, ?string $cursor = null): array
{
$response = $this->client
->add(
RequestObjectData::asRequest(
'app.user_list',
[
'cursor' => [
'limit' => $limit,
'cursor' => $cursor,
],
],
$this->requestId++
)
)
->request();
if ($response->hasError()) {
throw new Exception($response->error->message);
}
return $response->result;
}
public function createUser(array $userData): array
{
$response = $this->client
->add(
RequestObjectData::asRequest(
'app.user_create',
$userData,
$this->requestId++
)
)
->request();
if ($response->hasError()) {
throw new Exception($response->error->message);
}
return $response->result;
}
public function batchGetUsers(array $userIds): array
{
$client = $this->client;
foreach ($userIds as $userId) {
$client->add(
RequestObjectData::asRequest(
'app.user_get',
['user_id' => $userId],
$this->requestId++
)
);
}
$responses = $client->request();
return $responses
->filter(fn($r) => $r->hasResult())
->map(fn($r) => $r->result)
->all();
}
}

Usage:

$api = new UserApiClient('https://api.example.com/rpc', 'token');
// Get single user
$user = $api->getUser(42);
// List users
$users = $api->listUsers(limit: 50);
// Create user
$newUser = $api->createUser([
'name' => 'Alice',
'email' => 'alice@example.com',
]);
// Batch fetch
$users = $api->batchGetUsers([1, 2, 3, 4, 5]);

Add caching to reduce API calls:

use Illuminate\Support\Facades\Cache;
class CachedUserApiClient extends UserApiClient
{
public function getUser(int $userId): array
{
return Cache::remember(
"user.{$userId}",
now()->addMinutes(5),
fn() => parent::getUser($userId)
);
}
}

Process batch requests asynchronously:

use Illuminate\Support\Facades\Http;
$promises = collect([1, 2, 3, 4, 5])->mapWithKeys(function ($userId) {
return [
"user-{$userId}" => Http::async()
->post('https://api.example.com/rpc', [
'jsonrpc' => '2.0',
'method' => 'app.user_get',
'params' => ['user_id' => $userId],
'id' => $userId,
]),
];
});
$responses = Http::pool(fn($pool) => $promises->all());
foreach ($responses as $key => $response) {
$data = $response->json();
if (isset($data['result'])) {
echo "Got user data for {$key}\n";
}
}