google/cloud-common-protos
PHP classes generated from the protocol buffer types shared across multiple Google Cloud APIs. Stable and backward-compatible, this Apache 2.0 licensed Composer package lets you depend on these common protos directly via composer require google/cloud-common-protos.
First, check whether you already have it. This package is almost never a direct dependency — it arrives transitively with google/cloud-logging, google/cloud-pub-sub, google/cloud-firestore, google/cloud-trace, etc. Before adding anything:
composer show | grep -i proto
# google/cloud-common-protos 1.0.3
# google/common-protos 4.x
# google/protobuf 4.x
Note there are two similarly named packages. google/common-protos ships the google.protobuf.*, Google\Rpc\*, Google\Type\* and Google\LongRunning\* types; google/cloud-common-protos layers the Cloud-specific shared protos on top (Google\Api\* annotations, IAM policy types, audit logging, etc.). Requiring the latter pulls the former. Inspect vendor/google/ after install to confirm exactly which namespaces you have rather than guessing from docs.
Install only if you are using the classes directly:
composer require google/cloud-common-protos:^1.0
The PHP protobuf runtime ships a pure-PHP fallback, so ext-protobuf is optional — but install it in production if you serialize a lot (10–50x faster). ext-grpc is not required to use these message classes; they are plain DTOs.
First use case worth wiring: a shared serialization helper. In Laravel, register one small service and use it everywhere:
// app/Support/Proto.php
namespace App\Support;
use Google\Protobuf\Internal\Message;
final class Proto
{
/** @return array<string,mixed> */
public static function toArray(Message $m): array
{
return json_decode($m->serializeToJsonString(), true, 512, JSON_THROW_ON_ERROR);
}
public static function fromJson(Message $m, string $json): Message
{
return $m->mergeFromJsonString($json);
}
}
Use it to standardize API error envelopes (see below) or to format Google\Type\Money for invoices — Google\Type\Money is the safest first real-world use because it is trivial, testable, and touches no IAM or gRPC surface.
1. Money for anything financial. Never use floats. Store minor units, format at the edge:
use Google\Type\Money;
$price = (new Money())
->setCurrencyCode('EUR')
->setUnits(19)
->setNanos(990000000); // 19.99
// persist
$cents = $price->getUnits() * 100 + intdiv($price->getNanos(), 10_000_000);
// present
$formatted = number_format($price->getUnits() + $price->getNanos() / 1e9, 2);
2. Carbon ↔ Google\Protobuf\Timestamp in one place. Do not scatter ->getSeconds() calls. Timestamps are (seconds, nanos), not floats:
use Google\Protobuf\Timestamp;
function toTimestamp(\Carbon\CarbonInterface $c): Timestamp
{
return (new Timestamp())->setSeconds($c->getTimestamp());
}
function fromTimestamp(Timestamp $t): \Carbon\CarbonImmutable
{
return \Carbon\CarbonImmutable::createFromTimestampUTC($t->getSeconds());
}
3. Google\Protobuf\Struct/Value for dynamic JSON (Pub/Sub attributes, Firestore documents, Vertex AI parameters). The ergonomics are poor — wrap it:
use Google\Protobuf\Struct;
use Google\Protobuf\Value;
use Google\Protobuf\ListValue;
use Google\Protobuf\NullValue;
function toStruct(array $data): Struct
{
$s = new Struct();
foreach ($data as $k => $v) {
$s->getFields()[$k] = toValue($v);
}
return $s;
}
function toValue(mixed $v): Value
{
$val = new Value();
return match (true) {
is_null($v) => $val->setNullValue(NullValue::NULL_VALUE),
is_bool($v) => $val->setBoolValue($v),
is_int($v), is_float($v) => $val->setNumberValue((float) $v),
is_array($v) && array_is_list($v)
=> $val->setListValue((new ListValue())->setValues(array_map('toValue', $v))),
is_array($v) => $val->setStructValue(toStruct($v)),
default => $val->setStringValue((string) $v),
};
}
4. Standardized error responses via Google\Rpc\Status. If your Laravel app talks to GCP services and front-ends to a public API, mirror Google's error contract so clients need one parser:
use Google\Rpc\Status;
use Google\Rpc\Code;
// app/Exceptions/Handler.php
public function render($request, Throwable $e)
{
if ($request->expectsJson() && $e instanceof GoogleApiException) {
$status = (new Status())
->setCode(Code::INVALID_ARGUMENT)
->setMessage($e->getMessage());
return response()->json(
\App\Support\Proto::toArray($status),
match ($status->getCode()) {
Code::NOT_FOUND => 404,
Code::PERMISSION_DENIED => 403,
Code::INVALID_ARGUMENT => 422,
default => 500,
}
);
}
return parent::render($request, $e);
}
5. DTO mapping with spatie/laravel-data or plain mappers. Protobuf messages are not containers you should pass through your domain. Keep a PaymentData DTO and add toMoney() / toTimestamp() methods; keep protobuf construction at the infrastructure boundary. This is the single biggest ergonomic win — protobuf objects are verbose, weakly typed at the PHP level, and hostile to Eloquent::create().
6. Container binding. Bind clients, never messages. Messages are values; the container should hold a ProtoMapper or a CloudLoggingClient factory. Don't make messages singletons.
7. Testing. Assert on the JSON projection, not on object identity — generated classes don't compare well and field ordering in JSON is not guaranteed:
$this->assertSame(
['currencyCode' => 'EUR', 'units' => 19, 'nanos' => 990000000],
json_decode($money->serializeToJsonString(), true)
);
For inbound fixtures, use mergeFromJsonString() from a tests/Fixtures/*.json file — it's more readable than 20 setter calls and doubles as documentation of the wire shape.
json_encode($message) returns {}. Generated messages are not JsonSerializable. Always call serializeToJsonString(). This bites everyone once, usually inside a response()->json($msg).json_decode($msg->serializeToJsonString()) will not round-trip a field explicitly set to its zero value. If a consumer needs to distinguish "unset" from zero, use optional fields in a .proto you control, or carry a FieldMask.Timestamp will silently break toArray(), model serialization, and queue payloads. Convert at the boundary instead.$msg->setFoo([$a, $b]) works, but $msg->getFoo()[] = $a does not (it returns a copy). For maps like Struct::$fields, use $s->getFields()[$k] = $v — this one does mutate, which is inconsistent with repeated fields and easy to get wrong. Verify with a test.var_dump is useless — you get deeply nested private internals. Debug with serializeToJsonString() or serializeToString() + hex.Google\Protobuf\Any requires a type registry. Unpacking needs Any::unpack() with the concrete class; if you're storing Any payloads in the DB, also store the type_url so you can map back later.^1.0 for cloud-common-protos, but also pin google/protobuf and google/common-protos in CI. GCP SDKs have historically bumped common-protos majors independently (v2 → v3 → v4), and a composer update on a Tuesday can move them.ext-protobuf can differ in performance and, historically, in error messages on malformed input. Run your test suite in both modes if you can — at minimum, mirror the production configuration in CI.Code::NOT_FOUND is an int. Don't type-hint them as enums, and don't assume Code::name() exists.ReservationResourceUsage). Grep your codebase for fields you use when bumping the package; PHPStan won't tell you.google/cloud-storage over HTTP and never touch audit logs, Pub/Sub binary payloads, or gRPC, every class here is dead weight. The strongest signal is: do you ever need to construct a Struct, Money, Timestamp, or Status yourself? If no, let it arrive transitively and ignore it.How can I help you explore Laravel packages today?