google/cloud-core
google/cloud-core provides shared infrastructure for Google Cloud PHP client libraries, including authentication, transport, and request handling. It is installed automatically as a dependency and is not intended for direct use. This GA component offers stable APIs for the rest of the Google Cloud P...
Installation:
composer require google/cloud-storage
Note: google/cloud-core is not used directly—it's the shared runtime (auth, retries, pagination, HTTP transport) pulled in transitively by every google/cloud-* service package. You install the service package; Composer resolves core for you.
Confirm the version you actually got — this matters for behaviour changes:
composer show google/cloud-core | grep versions
# v1.74.0+ ships GCS idempotency tokens for all Storage API operations
Because core is transitive, upgrading core alone is not enough: bump the service package too (composer update google/cloud-storage google/cloud-core) so the two stay in lockstep. Commit composer.lock.
First Use Case: When you integrate a Google Cloud service (Storage, BigQuery, Pub/Sub), core transparently provides:
GOOGLE_APPLICATION_CREDENTIALS / metadata server detection.429, 503, connection errors).use Google\Cloud\Storage\StorageClient;
$storage = new StorageClient(); // core handles auth, retries, idempotency tokens
$bucket = $storage->bucket('my-bucket');
foreach ($bucket->objects() as $object) { // streamed via core iterator
echo $object->name();
}
Where to Look First:
CHANGELOG.md / releases page: core changes land here first and affect every service package you depend on.google/cloud-core intentionally documents almost nothing—read the client class you actually instantiate.Application Default Credentials (local dev):
gcloud auth application-default login # writes ~/.config/gcloud/application_default_credentials.json
In Laravel, keep the path in .env and out of config caching surprises:
// .env
GOOGLE_APPLICATION_CREDENTIALS=/abs/path/keyfile.json
Core reads this process env var directly, so run php artisan config:cache with the var exported in the runtime environment (not just in .env) for queued workers and Octane.
Explicit credentials (multi-tenant / multi-bucket apps):
$client = new StorageClient([
'projectId' => config('services.gcs.project'),
'credentials' => base64_decode(config('services.gcs.credentials_b64')),
]);
Bind it once in a service provider so you don't build clients per request:
$this->app->singleton(StorageClient::class, fn () => new StorageClient([...]));
Legacy keyFile / keyFilePath options: deprecated. They still work but emit deprecation noise; prefer the env var or the credentials array above.
$client = new StorageClient([
'retry' => ['max_attempts' => 5, 'timeout' => 30.0],
]);
public function handle(StorageClient $storage): void
{
$storage->bucket('uploads')->upload(fopen($this->path, 'r'), [
'name' => $this->objectName,
]);
}
// public $tries = 3; public $backoff = [10, 60, 180];
With v1.74.0+ this is safe even for creates: core reuses the same idempotency token across its internal retries, so a retried insert converges on one object instead of uploads/report.txt plus uploads/report.txt(1)-style surprises.// ❌ Each call is a NEW operation → new token → genuine duplicate
for ($i = 0; $i < 3; $i++) {
try { $bucket->upload($stream, ['name' => $name]); break; }
catch (\Exception $e) { sleep(2); }
}
If you must hand-roll retries, do the dedup yourself (deterministic object name, or if (! $bucket->object($name)->exists())) rather than hoping the token propagates across separate client calls.idempotency upload option. Tokens are a transport-level concern now; drop ad-hoc 'idempotency' => '...' hacks from uploads and let core do it.Google\Cloud\Core\Exception\InvalidArgumentException (note: core's namespace is singular Exception, not Exceptions). Catch that separately from transport errors so a typo fails fast in tests instead of becoming a 400 in production:
try {
$object->update(['metadata' => ['custom' => 'value']]);
} catch (\Google\Cloud\Core\Exception\InvalidArgumentException $e) {
// programmer error — surface loudly
}
ServiceException (404/403) into your own types.iterator_to_array() a bucket listing.
foreach ($bucket->objects(['prefix' => 'exports/']) as $object) { ... }
fopen), not a string, so core uses resumable/chunked transfer:
$bucket->upload(fopen($path, 'r'), ['name' => $name, 'resumable' => true]);
LazyCollection when you need collection ergonomics:
LazyCollection::make(fn () => yield from $bucket->objects());
projectId explicitly outside GCP.new StorageClient([
'projectId' => 'test-project',
'apiEndpoint' => 'http://localhost:4443',
]);
Emulators generally ignore the idempotency token header — harmless, but don't write tests that assert on it, and don't rely on emulator dedup behaviour to prove token correctness.export GOOGLE_CLOUD_DEBUG=1
$client = new StorageClient([
'logger' => \Log::channel('google-cloud')->getLogger(),
]);
Give it a dedicated channel with daily rotation — debug logging is verbose and will flood your default stack channel.Direct usage anti-pattern:
Google\Cloud\Core\Client. It's an internal base class.StorageClient, BigQueryClient, etc., and inject those.Version skew between core and service packages:
google/cloud-storage. Pinning google/cloud-storage to an old version while core floats forward (or vice versa) is the most common source of "works locally, duplicates in prod". Constrain both, update both, and re-run your upload tests after any composer update.Double submission from your own retry layer:
try/catch + re-call, a queue $tries re-run, or a user double-clicking upload. For those, enforce a deterministic object name or an exists() check.ShouldBeUnique when the payload is the same, otherwise a TimeoutExceeded retry legitimately starts a second operation.Deprecated keyFile / keyFilePath:
'credentials' => base64_decode(...) pattern; long-lived JSON keys in a repo are both a deprecation and a security finding.Retry overrides can silently disable resilience:
'retry' => ['max_attempts' => 1], // no retries at all — will surface as user-facing 503s
Test override values in staging; service packages ship tuned defaults per method.
PHP 8.4+ implicit-nullable deprecations: many core signatures were tightened (fixed around v1.62.2). If you subclass or mock core classes, declare nullable types explicitly or you'll get deprecations in the test suite.
Timezone handling: core deliberately doesn't mutate DateTime objects. Normalise to UTC at the boundary (new \DateTimeImmutable('now', new \DateTimeZone('UTC'))) rather than expecting conversion.
Emulator + transport mismatch: REST-only emulator setups can fatal if the client is configured for gRPC. Match the emulator's transport, or force REST via the client's transport option.
Enable request logging (env var for a single process, or putenv() in a tinker session):
GOOGLE_CLOUD_DEBUG=1 php artisan tinker
Capture raw requests into your log channel:
$client = new StorageClient([
'logger' => \Log::channel('google-cloud')->getLogger(),
]);
Then grep for the idempotency token header to confirm retries reuse the same value across attempts:
Prove credential resolution before blaming your code:
gcloud auth application-default print-access-token
If that fails, the PHP side will too — no amount of client config fixes a missing ADC.
Reproduce duplicate-upload reports deliberately: point the client at a proxy/emulator that drops the first response after the request lands, then confirm only one object exists. That's the fastest way to verify the v1.74.0 behaviour is actually in the build you deployed.
Check the class namespace you're catching: Google\Cloud\Core\Exception\ServiceException (singular) — a use of Exceptions\... will silently never match and your error handling will look like it "does nothing".
How can I help you explore Laravel packages today?