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.
Install the package and PHP gRPC extension:
composer require spiral/grpc-client -W
pecl install grpc
Verify the extension is loaded in php.ini or via php -m | grep grpc.
Basic client usage (standalone):
use Spiral\Grpc\Client\GrpcClient;
$client = GrpcClient::create('localhost:9001');
$service = $client->service(\GRPC\MyService\MailSenderInterface::class);
$response = $service->sendMail($ctx, $request);
Laravel integration:
app/Providers/AppServiceProvider.php:
public function boot(): void
{
if (!app()->has(\Spiral\Grpc\Client\Bridge\GrpcClientBootloader::class)) {
app()->register(\Spiral\Grpc\Client\Bridge\GrpcClientBootloader::class);
}
}
$mailSender = app()->make(\GRPC\MyService\MailSenderInterface::class);
protoc --php_out=. --grpc_out=. -I=. my_service.proto
$request = (new \GRPC\MyService\SendMailRequest())
->setEmail('user@example.com')
->setSubject('Hello')
->setMessage('Test');
$response = $mailSender->sendMail(new \Spiral\Grpc\Client\Context([]), $request);
Use GrpcClientConfig for centralized setup (e.g., in config/grpc.php):
return [
'services' => [
'mail' => new \Spiral\Grpc\Client\Config\ServiceConfig(
connections: new \Spiral\Grpc\Client\Config\ConnectionConfig('mail-service:9001'),
interfaces: [\GRPC\MyService\MailSenderInterface::class],
interceptors: [
\Spiral\Grpc\Client\Interceptor\SetTimeoutInterceptor::createConfig(5_000),
],
),
],
'interceptors' => [
\Spiral\Grpc\Client\Interceptor\RetryInterceptor::createConfig(
maximumAttempts: 3,
initialInterval: 100,
),
],
];
Bind the config to Laravel’s container in a service provider:
$this->app->singleton(\Spiral\Grpc\Client\Config\GrpcClientConfig::class, function ($app) {
return include __DIR__.'/../config/grpc.php';
});
Common patterns:
$client->withInterceptors([
SetTimeoutInterceptor::createConfig(10_000),
RetryInterceptor::createConfig(3),
]);
ExecuteServiceInterceptors):
new GrpcClientConfig(
interceptors: [ExecuteServiceInterceptors::class],
services: [
new ServiceConfig(
interceptors: [LoggingInterceptor::class],
// ...
),
],
);
final class AuthInterceptor implements InterceptorInterface {
public function intercept(CallContextInterface $context, HandlerInterface $handler): mixed {
$metadata = Helper::withMetadata($context);
$metadata['authorization'] = ['Bearer ' . auth()->token()];
return $handler->handle(Helper::withMetadata($context, $metadata));
}
}
$client = GrpcClient::create(['primary:9001', 'backup:9001']);
$client = GrpcClient::create(
new ConnectionConfig('secure-service:9001', tls: new TlsConfig(
certChain: storage_path('certs/chain.pem'),
privateKey: storage_path('certs/key.pem'),
))
);
class MailService {
public function __construct(
private \GRPC\MyService\MailSenderInterface $mailSender,
) {}
}
Register the service in AppServiceProvider:
$this->app->bind(\GRPC\MyService\MailSenderInterface::class, function ($app) {
return $app->make(\Spiral\Grpc\Client\ServiceClientProvider::class)
->getService(\GRPC\MyService\MailSenderInterface::class);
});
Dispatch(new SendMailJob($email, $subject, $message));
class SendMailJob implements ShouldQueue {
public function handle() {
$mailSender->sendMail($ctx, $request);
}
}
try {
$response = $service->sendMail($ctx, $request);
} catch (\GRPC\RpcException $e) {
Log::error('gRPC Error: ' . $e->getMessage());
throw new \RuntimeException('Failed to send mail', 0, $e);
}
throw new \Spiral\Grpc\Client\Exception\GrpcServiceException(
'Service unavailable',
$context->getCode(),
$context->getDetails()
);
Missing gRPC Extension:
Class 'GRPC\ChannelCredentials' not found.pecl install grpc) and restart PHP.Protocol Buffer Mismatches:
Invalid argument: Invalid proto message binary when calling a service..proto file and generated PHP stubs match the server’s schema. Regenerate stubs if the server’s proto changes:
protoc --php_out=. --grpc_out=. -I=. updated_service.proto
Interceptor Order Matters:
RetryInterceptor after SetTimeoutInterceptor overrides the timeout per retry. Reorder interceptors to enforce global timeouts:
// Correct: Timeout applies to all retries
$client->withInterceptors([
SetTimeoutInterceptor::createConfig(10_000),
RetryInterceptor::createConfig(3),
]);
Context Leaks:
Helper::withMetadata() and Helper::withDeadline() to explicitly propagate context:
$context = Helper::withMetadata($context, ['key' => 'value']);
Laravel Container Conflicts:
BindingResolutionException when resolving gRPC services.$this->app->alias(\GRPC\MyService\MailSenderInterface::class, 'grpc.mail.sender');
Enable gRPC Logging:
Add to php.ini or runtime config:
grpc.verbose_logging = 1
grpc.log_level = 3
Logs appear in PHP’s error log or stderr.
Inspect Interceptor Flow: Use a debug interceptor to log context:
final class DebugInterceptor implements InterceptorInterface {
public function intercept(CallContextInterface $context, HandlerInterface $handler): mixed {
Log::debug('Interceptor called', [
'code' => $context->getCode(),
'method' => $context->getMethod(),
'deadline' => $context->getDeadline(),
]);
return $handler->handle($context);
}
}
Validate Protobuf Messages:
Use google/protobuf to validate messages before sending:
use Google\Protobuf\Internal\Message;
if (!$request->isInitialized()) {
throw new \InvalidArgumentException('Protobuf message not initialized');
}
InterceptorInterface and add to the pipeline:
final class CircuitBreakerInterceptor implements InterceptorInterface {
public
How can I help you explore Laravel packages today?