google/grpc-gcp
GCP-specific extensions for gRPC, providing components and tooling to enhance gRPC clients when accessing Google Cloud APIs. Includes source for extensions plus infrastructure for end-to-end tests and benchmarks for cloud API access.
Install Dependencies:
# On Ubuntu/Debian
sudo apt-get install build-essential autoconf libtool pkg-config php php-dev
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
Set Up gRPC-PHP Environment:
# Install protobuf and gRPC (follow README instructions)
git clone -b $(curl -L https://grpc.io/release) https://github.com/grpc/grpc
cd grpc && git submodule update --init
cd third_party/protobuf && ./autogen.sh && ./configure && make -j8 && sudo make install
cd ../.. && make -j8 && sudo make install
sudo pecl install protobuf grpc
Generate Protobuf Classes:
# Clone googleapis and generate client code
git clone https://github.com/googleapis/googleapis.git
cd googleapis && make LANGUAGE=php OUTPUT=../project
Laravel Integration:
composer.json:
"require": {
"google/grpc-gcp": "^0.4.3",
"grpc/grpc": "^1.50"
},
"autoload": {
"psr-4": {
"App\\Gcp\\": "project/"
}
}
composer install.First RPC Call (Firestore Example):
// config/gcp.php
return [
'credentials' => env('GOOGLE_APPLICATION_CREDENTIALS', '/path/to/key.json'),
'host' => 'firestore.googleapis.com',
];
// app/Providers/GcpServiceProvider.php
public function register()
{
$this->app->singleton(\Google\Cloud\Firestore\V1beta1\FirestoreClient::class, function ($app) {
$host = config('gcp.host');
$credentials = \Grpc\ChannelCredentials::createSsl();
$auth = \Google\Auth\ApplicationDefaultCredentials::getCredentials();
$opts = [
'credentials' => $credentials,
'update_metadata' => $auth->getUpdateMetadataFunc(),
];
return new \Google\Cloud\Firestore\V1beta1\FirestoreClient($host, $opts);
});
}
Usage in Controller:
use Google\Cloud\Firestore\V1beta1\ListDocumentsRequest;
public function listDocuments()
{
$client = app(\Google\Cloud\Firestore\V1beta1\FirestoreClient::class);
$request = new ListDocumentsRequest();
$request->setParent("projects/{PROJECT_ID}/databases/(default)/documents");
[$response, $status] = $client->ListDocuments($request)->wait();
return $response->getDocuments();
}
GcpServiceProvider to avoid duplication.
// app/Providers/GcpServiceProvider.php
public function register()
{
$this->app->bind(\Google\Cloud\PubSub\V1\PublisherClient::class, function ($app) {
$credentials = \Grpc\ChannelCredentials::createSsl();
$auth = \Google\Auth\ApplicationDefaultCredentials::getCredentials();
return new \Google\Cloud\PubSub\V1\PublisherClient(
config('gcp.pubsub.host'),
[
'credentials' => $credentials,
'update_metadata' => $auth->getUpdateMetadataFunc(),
]
);
});
}
// app/Dtos/FirestoreDocumentDto.php
class FirestoreDocumentDto
{
public function __construct(
public string $name,
public array $fields,
public ?string $createTime,
public ?string $updateTime
) {}
}
// Convert gRPC response
$documents = $response->getDocuments()->map(function ($doc) {
return new FirestoreDocumentDto(
$doc->getName(),
$doc->getFields(),
$doc->getCreateTime(),
$doc->getUpdateTime()
);
});
Server-Side Streaming (e.g., Pub/Sub):
public function streamMessages()
{
$client = app(\Google\Cloud\PubSub\V1\SubscriberClient::class);
$subscription = $client->subscription(config('gcp.pubsub.subscription'));
$stream = $subscription->pull(['return_immediately' => false]);
foreach ($stream->getReceivedMessages() as $message) {
// Process message
$subscription->acknowledge([$message]);
}
}
Client-Side Streaming (e.g., Firestore Writes):
public function batchWriteDocuments(array $documents)
{
$client = app(\Google\Cloud\Firestore\V1beta1\FirestoreClient::class);
$stream = $client->BatchWriteDocuments();
foreach ($documents as $doc) {
$stream->send($doc);
}
return $stream->finish()->wait();
}
try {
[$response, $status] = $client->ListDocuments($request)->wait();
if ($status->code !== 0) {
throw new \RuntimeException("gRPC Error: {$status->details}");
}
} catch (\Grpc\RpcException $e) {
\Log::error("gRPC Call Failed", ['error' => $e->getMessage()]);
throw new \RuntimeException("Service Unavailable", 503, $e);
}
use Google\Rpc\Code;
public function callWithRetry(callable $callable, int $maxRetries = 3)
{
$attempt = 0;
while ($attempt < $maxRetries) {
try {
return $callable();
} catch (\Grpc\RpcException $e) {
if ($e->getCode() !== Code::UNAVAILABLE) {
throw $e;
}
$attempt++;
sleep(2 ** $attempt); // Exponential backoff
}
}
throw new \RuntimeException("Max retries exceeded");
}
// config/gcp.php
return [
'firestore' => [
'host' => env('GCP_FIRESTORE_HOST', 'firestore.googleapis.com'),
'database' => env('GCP_FIRESTORE_DATABASE', '(default)'),
],
'pubsub' => [
'host' => env('GCP_PUBSUB_HOST', 'pubsub.googleapis.com'),
'subscription' => env('GCP_PUBSUB_SUBSCRIPTION'),
],
];
// tests/Unit/Gcp/FirestoreTest.php
public function testListDocuments()
{
$mockClient = Mockery::mock(\Google\Cloud\Firestore\V1beta1\FirestoreClient::class);
$mockClient->shouldReceive('ListDocuments')
->once()
->andReturn([new \Google\Cloud\Firestore\V1beta1\ListDocumentsResponse(), null]);
$this->app->instance(\Google\Cloud\Firestore\V1beta1\FirestoreClient::class, $mockClient);
// Test your service
}
Protobuf Generation Issues:
grpc_php_plugin fails to find dependencies if .proto files are not in the same directory.
Fix: Use the googleapis Makefile or explicitly list all dependencies in the protoc command.
protoc --proto_path=googleapis --plugin=protoc-gen-grpc=`which grpc_php_plugin` \
--php_out=./ --grpc_out=./ google/api/annotations.proto google/firestore/v1beta1/firestore.proto
Shared Memory Leaks:
grpc_php_plugin in shared environments. For Laravel, ensure:
supervisor).google/grpc-gcp:^0.1.5+ which fixes memory leaks.**PHP 8.x Deprec
How can I help you explore Laravel packages today?