google/shopping-common-protos
google/shopping-common-protos provides generated PHP protobuf classes for Google Shopping shared message types. It supplies common data structures used by Google Shopping APIs and related PHP libraries, simplifying protobuf request and response handling. A low-level dependency, not a standalone clie...
This package is not a client library — it is a bundle of protobuf-generated PHP classes for the shared google.shopping.type messages (Price, Weight, CustomAttribute, Destination, Channel, ReportingContext, etc.) that the Merchant Center / Shopping APIs use in their request and response bodies. You will almost never install it deliberately; it arrives transitively when you pull in any google/shopping-merchant-* client.
composer require google/shopping-merchant-products # brings this in
composer why google/shopping-common-protos # confirm the path
Look first at: vendor/google/shopping-common-protos/src/ for the generated message classes under Google\Shopping\Type\, and composer.json there to confirm the PSR-4 roots and the pinned google/protobuf constraint. There is no service provider, no config file, no facade — nothing Laravel-specific to wire up.
First use case: building the common value objects that get embedded in a product payload.
use Google\Shopping\Type\Price;
use Google\Shopping\Type\CustomAttribute;
$price = (new Price())
->setAmountMicros(1_999_000_000) // micros, not cents
->setCurrencyCode('USD');
$attr = (new CustomAttribute())
->setName('season')
->setValue('ss26');
If you only need to read these values from an API response, you rarely touch the classes at all — the generated client hydrates them for you.
Keep proto construction out of controllers. Treat these classes the way you'd treat a third-party SDK DTO: map from Eloquent models in a dedicated layer.
// app/Services/Merchant/ProductMapper.php
public function priceFrom(Product $product): Price
{
return (new Price())
->setAmountMicros((int) round($product->price * 1_000_000))
->setCurrencyCode($product->currency);
}
Static factories for the unit conversion. Micros are the single most common source of off-by-1000 bugs. Wrap the conversion once (PriceFactory::fromCents(), fromDecimalString()) and never inline * 1_000_000 again.
Build payloads once, reuse across jobs. A queued bulk-sync job should construct the message tree in handle() and let the transport serialize it. Don't rebuild per retry attempt from scratch if the input hasn't changed — but do clone any message you reuse across two different requests (see Gotchas).
Round-trip through JSON at your boundaries. For logging, API responses, or Blade views, convert to plain arrays:
$payload = json_decode($message->serializeToJsonString(), true);
This is also the cleanest way to snapshot a message in a test fixture.
Testing. Build messages in test factories and assert on serialized output rather than object identity:
$this->assertJsonStringEqualsJsonString(
'{"amountMicros":"1999000000","currencyCode":"USD"}',
$price->serializeToJsonString()
);
Integration tip: if you're already using a google/shopping-merchant-* client, pass these objects straight into the request builders ($request->setPrice($price)) — no conversion layer needed. The reverse (pulling fields out of a response) is just $response->getPrice()->getAmountMicros().
Namespace hygiene. Group these in something like app/Support/Google/Shopping/ so the rest of the app never imports Google\Shopping\Type\* directly. That gives you one place to swap or stub when Google changes the surface.
composer update deadlocks later.json_encode() silently fails on messages. They are not JsonSerializable in a useful way. Always use serializeToJsonString() / mergeFromJsonString().0 and '' are indistinguishable from "unset." hasFoo() only exists for message and optional fields. A zero-price product may serialize with the field omitted — send a non-zero placeholder or use an explicit optional wrapper if the API requires presence.amountMicros will come back as "1999000000", not 1999000000. Cast deliberately; don't assertSame(1999000000, ...) against decoded JSON.Destination::SHOPPING_ADS is a constant, not an object. Comparing with === works, but unknown enum values are legal on the wire — handle the default branch.Price instance across a loop of requests means later mutations leak into earlier references. clone (or rebuild) when sharing.setCurrencyCode('usdollars') will not throw; the failure surfaces as a 400 from the API, often with a field path that's hard to trace back. Validate at your own boundary before constructing.composer show google/shopping-common-protos against the client's constraint before debugging anything else.vendor/. It's Apache-2.0 generated code from googleapis; local edits vanish on composer install. Report upstream or wrap the behavior.serializeToJsonString() immediately before the transport call. It's the only faithful view of what actually goes over the wire and reproduces most API rejections instantly.config/services.php entry this package needs — that belongs to your credentials setup, not here.How can I help you explore Laravel packages today?