spiral/grpc-client
Lightweight, extensible PHP gRPC client with a Guzzle-like API. Supports standalone use or Spiral integration, configurable via DTOs, includes interceptors (timeouts, retries), and rich exceptions for error handling. Requires the PHP gRPC extension.
The package provides powerful and convenient functionality for making client gRPC requests through a simple and extensible interface.
composer require spiral/grpc-client -W
Note that the package requires gRPC extension to be installed.
The package provides the following parts of the API:
[!NOTE] The
spiral/roadrunner-bridgepackage includes thespiral/grpc-clientpackage by default since version4.0.0and provides its integration and configuration flow.
Add the \Spiral\Grpc\Client\Bridge\GrpcClientBootloader bootloader to the list of bootloaders
in the application configuration (usually it is Kernel.php).
The GrpcClient class provides a lightweight, Guzzle-like API for making gRPC calls
without any framework or DI container:
use Spiral\Grpc\Client\GrpcClient;
// Minimal — one endpoint, call service methods right away
$client = GrpcClient::create('localhost:9001');
$mailSender = $client->service(\GRPC\MyService\MailSenderInterface::class);
$mailSender->sendMail($ctx, $request);
Add interceptors using the convenient createConfig() factories:
use Spiral\Grpc\Client\GrpcClient;
use Spiral\Grpc\Client\Interceptor\SetTimeoutInterceptor;
use Spiral\Grpc\Client\Interceptor\RetryInterceptor;
$client = GrpcClient::create('localhost:9001')
->withInterceptors([
SetTimeoutInterceptor::createConfig(10_000),
RetryInterceptor::createConfig(maximumAttempts: 3),
]);
Multiple connections for failover:
$client = GrpcClient::create(['localhost:9001', 'localhost:9002']);
ConnectionConfig for TLS:
use Spiral\Grpc\Client\Config\ConnectionConfig;
use Spiral\Grpc\Client\Config\TlsConfig;
$client = GrpcClient::create(
new ConnectionConfig('localhost:9001', tls: new TlsConfig(
certChain: '/my-project.pem',
privateKey: '/my-project.key',
)),
);
With a factory (e.g. Spiral's container) to resolve class-string interceptors via DI:
$client = GrpcClient::create('localhost:9001')
->withFactory($factory);
The with* methods are immutable — each returns a new instance, so you can safely
derive pre-configured clients:
$base = GrpcClient::create('localhost:9001')
->withInterceptors([SetTimeoutInterceptor::createConfig(5_000)]);
$withRetry = $base->withInterceptors([
SetTimeoutInterceptor::createConfig(5_000),
RetryInterceptor::createConfig(maximumAttempts: 3),
]);
[!NOTE]
GrpcClientdoes not support per-service interceptors. If you need that, useServiceClientProviderwith the fullGrpcClientConfig/ExecuteServiceInterceptorssetup.
If you are using this package with another framework,
you can use \Spiral\Grpc\Client\ServiceClientProvider with a GrpcClientConfig
to get ready-to-use gRPC clients, or use the standalone GrpcClient described above.
Now let's consider a configuration example:
use Spiral\Grpc\Client\Config\GrpcClientConfig;
use Spiral\Grpc\Client\Config\ServiceConfig;
use Spiral\Grpc\Client\Config\ConnectionConfig;
use Spiral\Grpc\Client\Interceptor\SetTimeoutInterceptor;
use Spiral\Grpc\Client\Interceptor\RetryInterceptor;
use Spiral\Grpc\Client\Interceptor\ExecuteServiceInterceptors;
new GrpcClientConfig(
interceptors: [
SetTimeoutInterceptor::createConfig(10_000), // 10 seconds
RetryInterceptor::createConfig(
maximumAttempts: 3,
initialInterval: 100, // 0.1 seconds
backoffCoefficient: 1.5,
),
ExecuteServiceInterceptors::class,
],
services: [
new ServiceConfig(
connections: new ConnectionConfig('my-service:9001'),
interfaces: [
\GRPC\MyService\MailSenderInterface::class,
\GRPC\MyService\BlackListInterface::class,
\GRPC\MyService\SubscriberInterface::class,
],
),
],
)
This class represents the configuration of the gRPC client in general. It includes a list of service configurations and a general list of interceptors that will be applied to all services.
Interceptors can be declared as class names, Spiral\Core\Container\Autowire objects
(if custom constructor arguments need to be passed), or objects.
Note that some interceptors provide convenient methods for creating configurations.
This class represents the configuration of a specific service or a group of similar services but with different connection options.
The interfaces parameter is a list of gRPC service interfaces that were generated by the protoc utility
and that are implemented by the service.
[!NOTE] Currently, we support only service interfaces that are generated using the
protocutility with theprotoc-gen-php-grpcplugin.
The configuration example above doesn't include the interceptors parameter.
It's the same as in the general configuration, but it's applied only to the specified service.
This branch of interceptors is run via the ExecuteServiceInterceptors interceptor.
So, it's important to include it if you want to use service-specific interceptors.
The connections parameter is a list of connection configurations. You can specify multiple connections
to distribute the load between them or to provide fail-over.
Multi-connection orchestration strategy can be configured by interceptors,
for example, ConnectionsRotationInterceptor.
This class represents the configuration of a single connection to the gRPC service. It includes credentials and the service address.
To create a secure connection, use the TlsConfig class.
new ConnectionConfig(
address: 'my-service:9001',
tls: new TlsConfig(
privateKey: '/my-project.key',
certChain: '/my-project.pem',
),
),
After the integration and configuration are ready, you can get the client for the desired service interface from the container and call the service methods.
final class Sender
{
public function __construct(
private \GRPC\MyService\MailSenderInterface $mailSender,
) {}
public function __invoke(string $email, $subject, string $message): bool
{
$request = (new \GRPC\MyService\SendMailRequest())
->setEmail($email)
->setSubject($subject)
->setMessage($message);
$response = $this->mailSender->sendMail(new \Spiral\RoadRunner\GRPC\Context([]), $request);
return $response->getSuccess();
}
}
Important points when using interceptors in the long-running mode:
When writing your own interceptors, you will likely want to work with gRPC-specific fields
(options, metadata, input message, etc.).
Use the \Spiral\Grpc\Client\Interceptor\Helper class to get or set the values of these context fields.
final class AuthContextInterceptor implements InterceptorInterface
{
public function __construct(
private readonly AuthContextInterface $authContext,
) {}
public function intercept(CallContextInterface $context, HandlerInterface $handler): mixed
{
$token = $this->authContext->getToken();
if ($token === null) {
return $handler->handle($context);
}
$metadata = \Spiral\Grpc\Client\Interceptor\Helper::withMetadata($context);
$metadata['auth-token'] = [$token];
return $handler->handle(\Spiral\Grpc\Client\Interceptor\Helper::withMetadata($context, $metadata));
}
}
Interceptors are executed in the order in which they are declared in the configuration. The order of the interceptors is important.
For example, if we have the following configuration:
SetTimeout(10 seconds)Retry(maxAttempts: 3, interval: 2 seconds)SetTimeout(3 seconds)then we will have a 3-second timeout for each retry attempt.
We also have a 10-second timeout for all attempts in total, after which no new request attempts will be made.
This happens because the RetryInterceptor takes into account previously configured timeouts.
Also, the placement of the ExecuteServiceInterceptors interceptor,
which embeds the interceptors of the currently running service,
will affect the final sequence of interceptors.
How can I help you explore Laravel packages today?