spiral/roadrunner-grpc
Laravel-friendly integration for RoadRunner gRPC: run high-performance PHP gRPC servers/workers, handle protobuf-based services, and communicate with the RoadRunner runtime for fast, long-lived processes and efficient microservices.
Installation:
composer require spiral/roadrunner-grpc
Ensure RoadRunner is installed (roadrunner --version) and configured with the gRPC plugin.
Configure RoadRunner (rr.yaml):
version: '3'
grpc:
active: true
workers:
command: 'php public/grpc_server.php'
pool:
numWorkers: 4
Define a .proto file (e.g., proto/hello.proto):
syntax = "proto3";
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest { string name = 1; }
message HelloResponse { string message = 1; }
Generate PHP stubs:
protoc --php_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_php_plugin` proto/hello.proto
Implement the service (app/Services/HelloService.php):
use Generated\HelloServiceInterface;
class HelloService implements HelloServiceInterface {
public function sayHello($request, ServerInterface $server = null) {
return new HelloResponse(['message' => "Hello, {$request->getName()}"]);
}
}
Bootstrap the gRPC server (public/grpc_server.php):
require __DIR__.'/../vendor/autoload.php';
$worker = new \Spiral\RoadRunner\GRPC\Worker(new \Spiral\RoadRunner\GRPC\Invoker());
$worker->run();
Start RoadRunner:
rr serve
Dependency Injection: Use Laravel’s service container to resolve dependencies in gRPC handlers:
$container = new \Illuminate\Container\Container();
$container->singleton(\App\Services\HelloService::class);
$worker = new \Spiral\RoadRunner\GRPC\Worker(
new \Spiral\RoadRunner\GRPC\Invoker($container)
);
Middleware Pipeline: Wrap handlers with middleware (e.g., auth, logging):
$worker = new \Spiral\RoadRunner\GRPC\Worker(
new \Spiral\RoadRunner\GRPC\Invoker($container),
new \Spiral\RoadRunner\GRPC\Middleware\AuthMiddleware()
);
Server-Side Streaming:
public function streamData($request, ServerInterface $server) {
$stream = $server->write(new DataResponse(['data' => 'chunk1']));
yield $stream;
$stream->write(new DataResponse(['data' => 'chunk2']));
}
Bidirectional Streaming: Use ServerStreamInterface to read client streams:
public function bidirectionalStream(ServerStreamInterface $stream) {
foreach ($stream as $chunk) {
// Process chunk
}
}
Convert PHP Exceptions to gRPC Status Codes:
try {
return $this->service->execute($request);
} catch (\InvalidArgumentException $e) {
throw new \Grpc\RpcException(
new \Google\Protobuf\Internal\RepeatedField(),
\Grpc\Status::INVALID_ARGUMENT,
$e->getMessage()
);
}
Custom Metadata: Attach debug info to responses:
$response = new HelloResponse(['message' => 'Hello']);
$response->setMetadata(['trace_id' => \Illuminate\Support\Str::uuid()]);
return $response;
Backward Compatibility: Use oneof and optional fields to avoid breaking changes:
message User {
oneof user_data {
string email = 1;
string phone = 2;
}
}
Code Generation: Re-generate stubs after .proto changes:
protoc --php_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_php_plugin` --proto_path=proto proto/*.proto
Worker Logs: Check RoadRunner’s worker logs (rr logs) for silent failures. Use monolog for structured logging:
$logger = new \Monolog\Logger('grpc');
$logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stderr'));
$worker = new \Spiral\RoadRunner\GRPC\Worker($invoker, $logger);
gRPC CLI Tools: Test locally with grpcurl:
grpcurl -plaintext -d '{"name":"World"}' localhost:6001 app.HelloService/SayHello
Worker Pool Tuning: Adjust numWorkers and maxJobs in rr.yaml based on load:
grpc:
workers:
pool:
numWorkers: 8 # Match CPU cores
maxJobs: 5000 # Avoid queue starvation
Protobuf Optimization: Use optimize_for = CODE_SIZE in .proto for smaller payloads.
Service Container: Avoid circular dependencies by resolving gRPC services after Laravel’s bootstrapping:
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(\Spiral\RoadRunner\GRPC\Invoker::class)->run();
Shared State: Use Spiral\RoadRunner\GRPC\ContextInterface for request-scoped data:
$context = new \Spiral\RoadRunner\GRPC\Context();
$context->withValue('user_id', auth()->id());
$worker->run($context);
Stub Autoloading: Generated classes must be in vendor/ or manually autoloaded. Add to composer.json:
"autoload": {
"files": ["generated/*.php"]
}
TLS Misconfiguration: Ensure rr.yaml includes TLS settings:
grpc:
tls:
cert: /path/to/cert.pem
key: /path/to/key.pem
Protobuf Namespace Collisions: Use option php_namespace = "Generated"; in .proto to avoid conflicts with Laravel classes.
PHP 8.4 Deprecations: Explicitly typehint array as array<string, mixed> to avoid warnings:
public function handle(array $request): array {
return ['data' => $request['input']];
}
RoadRunner Version Mismatch: Pin spiral/roadrunner to a compatible version (e.g., ^2024 for RR 2024.x). Check release notes for breaking changes.
How can I help you explore Laravel packages today?