Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Google Api Php Client Laravel Package

evert/google-api-php-client

A PHP client library for accessing Google APIs with OAuth 2.0, service-account authentication, token refresh, and generated service classes for Gmail, Drive, Calendar, Sheets, and more. Composer-ready for server-side PHP applications.

Deep Wiki
Context7

Getting Started

Read the package name again: the maintainer says don't use it. evert/google-api-php-client was a fork of google/apiclient created when the upstream project was effectively unmaintained. That situation reversed years ago; upstream is now actively released, splits its ~200 service classes into individually installable google/apiclient-services-* packages, and supports current PHP versions. The fork did not follow. Installing it today means pinning a stale copy of Guzzle-era code with public OAuth/OIDC plumbing in it, on a package whose own description tells you to stop.

So the "getting started" work is a migration, not an install.

Step 0 — verify what you actually have

composer show | grep -i google
composer why evert/google-api-php-client

If it's a transitive dependency of something abandoned, you'll need to replace that too, otherwise Composer will pull the fork back in.

Step 1 — swap the requirement

composer remove evert/google-api-php-client
composer require google/apiclient:^2.16

Step 2 — confirm nothing breaks on namespaces

The fork kept the Google\ namespace, so most of your code compiles untouched. What breaks are the entry points that changed between v1 and v2: Google_Client → Google\Client, Google_Service_* → Google\Service\* (with sub-namespaces, e.g. Google\Service\SearchConsole\SearchanalyticsQueryRequest), and Google_Http_Request → GuzzleHttp\Psr7\Request. Grep for underscores in class names — that's the tell:

grep -rn "Google_" app/ config/ tests/

Step 3 — pick your first real use case and stop

The single highest-leverage thing this library does for a Laravel app is token lifecycle management around an OAuth API — Search Console, Analytics, Drive, Calendar, Gmail. Start with one: a scheduled command that pulls Search Console data into your DB. That exercises auth config, refresh tokens, a service class, error handling, and caching in one small surface. Everything else in the package scales from that pattern.

Where to look first: Google\Client (auth, token cache, scopes, HTTP client) and the generated service class for the one API you're integrating. Ignore the other 199 service namespaces until you need them.

Implementation Patterns

Bind the client, don't instantiate it

The Google\Client is stateful (it holds credentials, a token, a token store, and an HTTP client). Create it once per request in the container and inject it.

// app/Providers/GoogleServiceProvider.php
$this->app->singleton(Client::class, function ($app) {
    $client = new Client();
    $client->setAuthConfig(storage_path('app/google/service-account.json'));
    $client->addScope(SearchConsole::WEBMASTERS_READONLY);
    $client->setApplicationName(config('app.name'));
    return $client;
});

Avoid resolving it inside model events, jobs, and console commands independently — you'll end up with several authenticated clients per process and duplicated token refreshes.

Service account for server-to-server

The right default for backend integrations where no end user is involved.

$client = app(Client::class);
$client->setSubject($impersonatedUser); // only for domain-wide delegation
$service = new SearchConsole($client);
$response = $service->searchanalytics->query($siteUrl, $queryRequest);

Domain-wide delegation (setSubject) requires an admin-console grant on the Workspace side. If you get unauthorized_client, it's almost always the delegation config, not your code.

OAuth web flow with persisted refresh tokens

The pattern that actually matters, because refresh token handling is where 90% of production bugs live.

// Redirect
$client->setRedirectUri(route('google.callback'));
$client->setAccessType('offline');
$client->setPrompt('consent');            // forces refresh_token on every grant
$url = $client->createAuthUrl();

// Callback
$token = $client->fetchAccessTokenWithAuthCode($request->get('code'));

// Persist (encrypted) against the user
$user->update([
    'google_token' => encrypt($token),
    'google_email' => $client->getGrantedScopes() ? $client->getTokenPayload()?->getEmail() : null,
]);

On subsequent requests, hydrate and let the client refresh itself:

$client->setAccessToken(decrypt($user->google_token));

if ($client->isAccessTokenExpired()) {
    $new = $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    $client->setAccessToken($new);
    $user->update(['google_token' => encrypt($new)]); // must persist — new access token
}

Always persist the result of a refresh. The access token changes; if you don't write it back, you refetch on every request and eventually hit refresh rate limits.

Use the token callback instead of manual persistence

setTokenCallback (upstream v2) lets the client tell you when it refreshed, so you don't sprinkle update() calls everywhere:

$client->setTokenCallback(function ($cacheKey, $token) use ($user) {
    $user->update(['google_token' => encrypt($token)]);
});

This is the single best extension point in the library — it centralises persistence and works for every service call.

Wrap the service, don't leak it

Don't inject Google\Client into controllers or jobs directly for domain work. Wrap it:

class SearchConsoleClient
{
    public function __construct(private Client $client) {}

    public function query(string $site, CarbonInterface $from, CarbonInterface $to): array
    {
        $req = new SearchanalyticsQueryRequest([
            'startDate' => $from->toDateString(),
            'endDate'   => $to->toDateString(),
            'dimensions' => ['query'],
            'rowLimit'  => 25000,
        ]);

        return (new SearchConsole($this->client))
            ->searchanalytics
            ->query($site, $req)
            ->getRows() ?? [];
    }
}

This is what makes the library testable — you can fake SearchConsoleClient in feature tests instead of mocking generated Google classes.

Batch and defer for bulk reads

For many small calls (e.g. per-URL inspection), batch beats sequential by an order of magnitude:

$client->setUseBatch(true);
$batch = $service->createBatch();
$batch->add($service->urlInspection->index(...), $id);
$results = $batch->execute();

setDefer(true) is the lower-level alternative: get a GuzzleHttp\Promise\PromiseInterface per request, fire them concurrently, then $client->execute($promise).

Job-safe pattern

Wrap every API call in a job with retryUntil, but be careful: retrying a 401 after refreshing is fine, retrying a 403 quotaExceeded will just burn quota. Distinguish them (see Gotchas).

Gotchas and Tips

Both packages installed at once is the worst failure mode. Composer will resolve Google\Client to whichever autoloader wins, and you'll get method-not-found errors that look like version drift. composer why google/apiclient and pin the tree to one.

refresh_token is returned only once. It comes back on the first consent for a given client/user/scope pair. If you lose it, the user must re-consent — which is why setPrompt('consent') is standard practice until you've safely stored the token. If you're getting null refresh tokens in staging, you previously consented; revoke at myaccount.google.com/permissions.

isAccessTokenExpired() uses a 30-second skew (TOKEN_EXPIRY_BUFFER in the fork, 60 in newer upstream). On a slow request you can still 401 mid-flight. Defensive pattern: catch Google\Service\Exception with getCode() === 401, force-refresh, retry once — and guard with an attempt counter so you don't loop.

Distinguish quota errors. Google returns 403 for both permission failures and rate limiting. Check $e->getErrors()[0]['reason']:

  • rateLimitExceeded / userRateLimitExceeded → exponential backoff, retry
  • quotaExceeded → daily cap, retrying is pointless; fail and alert
  • insufficientPermissions → scope problem, retrying is pointless

A generic catch (Exception $e) { retry(); } here will hammer a dead quota all night.

The v2 service split. Upstream v2 lets you install only what you use:

composer require google/apiclient-services:^0.370   # or individual -searchconsole, -drive, etc.

The full apiclient-services package is enormous (~50MB of PHP). Use google/apiclient-services with the extra.google/apiclient-services allowlist in composer.json to prune at build time:

"extra": { "google/apiclient-services": ["SearchConsole", "Drive"] }

The abandoned fork predates this mechanism entirely — one more reason it hurts in a real codebase.

Token storage. These are bearer credentials. Use Laravel's encrypted cast on the model column, and never let a token reach a log line. Set a PSR-3 logger on the client (setLogger) for debugging, but redact Authorization — the fork's debug output can be chatty.

Cache/config path in containerised builds. setCacheConfigPath() and setClassConfigPath() write to disk. In read-only or ephemeral containers this throws at the worst time. Either bake the paths into the image or point them at storage_path().

Scope changes silently break existing users. Adding a scope to addScope() doesn't retroactively grant it for already-tokenised users; you'll get 403 insufficientPermissions on the new endpoint only. Version your scope set and detect the failure to trigger re-consent.

Custom HTTP client for observability. setHttpClient() accepts any GuzzleHttp\ClientInterface. Injecting a pre-configured Guzzle client is how you get tracing, timeouts, and connection reuse — the default has no timeout beyond Guzzle's. Worth doing on day one.

Testing. Don't mock Google\Client; it's a concrete, stateful class. Mock your own wrapper. For the auth-refresh path, use MockHandler via Guzzle and assert the refresh happened exactly once.

Extension points worth knowing: setTokenCallback (persistence), setHttpClient (transport), setDefer/batch (throughput), and the Google\Service\Exception::getErrors() accessor (retry classification). Those four cover most of what you'll ever need. Anything beyond that — custom middlewares, discovery caching — is a sign you should be looking at whether the API has a purpose-built SDK instead.

On the fork specifically: if it's already in production, treat migration as a security task rather than a cleanup. It carries an unmaintained HTTP stack and OAuth implementation, no PHP 8.x guarantees, and no response to CVEs in its dependency tree. The API surface you're using is a small subset (Client, one or two services), so the swap is usually a day of work — much less than the first incident.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics
robbiep/afterthedeadline
frankkessler/incontact-laravel-oauth2-rest
jlxh/laravel-userlog
davidmgilo/timetables
qylinfly/role-permission
reflect-digital/conduit
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr