happyr/google-api-bundle
Symfony bundle that integrates the official Google APIs PHP client. It configures and exposes a ready-to-use Google_Client service, with bundle settings for application credentials, OAuth, and API access, simplifying Google service integration in Symfony applications.
happyr/google-api-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no facade, and no package auto-discovery. In a Laravel app the practical starting point is to not install the bundle and instead install its underlying dependency directly:
composer require google/apiclient
Use the bundle as a reference implementation for configuration and client/service construction. The files worth reading first are:
DependencyInjection/Configuration.php — the config keys the bundle exposes (client_id, client_secret, redirect_uri, developer_key, application_name, scopes, etc.)Resources/config/services.* — the service definitions. The primary service is happyr.google.api.client; as of 2.3.0 there are also service definitions for Drive and Slides, which are the best reference for how the bundle wires a Google\Service\* object onto a configured clientREADME.md — the intended Symfony usage flowThe first use case is standard OAuth:
use Google\Client;
$client = new Client();
$client->setClientId(config('services.google.client_id'));
$client->setClientSecret(config('services.google.client_secret'));
$client->setRedirectUri(config('services.google.redirect'));
$client->setApplicationName(config('app.name'));
$client->setScopes(['https://www.googleapis.com/auth/calendar']);
$client->setAccessType('offline');
$client->setPrompt('consent');
$authUrl = $client->createAuthUrl();
Then exchange the callback code, persist the token, and instantiate a Google service:
$token = $client->fetchAccessTokenWithAuthCode($request->get('code'));
$client->setAccessToken($token);
$calendar = new \Google\Service\Calendar($client);
In Laravel, create a singleton binding in a service provider so the configured client can be injected. Mirror the bundle's config keys in config/services.php and .env.
Use a config-first client factory. Keep Google credential setup in one place, not scattered across controllers. The bundle's main value is exactly this: a preconfigured Google\Client. In Laravel:
$this->app->singleton(Client::class, function () {
$client = new Client();
$client->setClientId(config('services.google.client_id'));
$client->setClientSecret(config('services.google.client_secret'));
$client->setRedirectUri(config('services.google.redirect'));
$client->setApplicationName(config('app.name'));
$client->setScopes(config('services.google.scopes'));
$client->setAccessType('offline');
$client->setPrompt('consent');
return $client;
});
Do not put per-user access tokens on that singleton. Resolve the singleton, clone/reconfigure it, then call setAccessToken() with the current user's token.
Persist the whole token array, not just the access token. Google returns an array with access_token, expires_in, refresh_token, created, and sometimes id_token. Store it as JSON (encrypted cast or encrypted column) and rehydrate it:
$client->setAccessToken($account->google_token); // array or JSON string
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
$account->update(['google_token' => $client->getAccessToken()]);
}
Wrap service construction in your own factory. The bundle ships a small, fixed set of services — the client, plus Drive and Slides since 2.3.0 — not one service per Google API. Mirror its pattern and add the APIs you actually use:
class GoogleServiceFactory
{
public function __construct(private Client $client) {}
public function calendar(): Calendar
{
return new Calendar($this->client);
}
public function drive(): Drive
{
return new Drive($this->client);
}
public function slides(): Slides
{
return new Slides($this->client);
}
}
The Drive/Slides definitions in 2.3.0 are worth reading for scope choices — Drive and Slides both typically need https://www.googleapis.com/auth/drive (or a narrower drive.file), and Slides requires the Drive scope for anything beyond presentation content itself. Copy their scope list rather than guessing.
Queue jobs should carry an identifier, not the client. Serializing Google\Client or Google service objects into jobs is fragile — and the bundle's service definitions do not change that, since they are container singletons in a Symfony request lifecycle. Pass a user/account ID, reload the token in the job, rebuild the client, and execute the API call.
Use service accounts for server-to-server work. For Workspace/domain-wide delegation:
$client->setAuthConfig(storage_path('app/google/service-account.json'));
$client->setScopes(['https://www.googleapis.com/auth/admin.directory.user']);
$client->setSubject('[email protected]');
This is the pattern to use when no end-user OAuth flow is involved — including for Drive/Slides automation against shared drives.
Request incremental scopes. Start with the minimum scopes needed and add more with setScopes() when the user enables a feature. This reduces consent friction and avoids requesting sensitive scopes up front. Drive in particular has restricted scopes that trigger Google verification requirements for public apps.
Test by mocking at the client/service boundary. Bind a fake Google\Client in the container, or inject a Guzzle mock handler into the Google client's HTTP stack where supported. Assert that your code calls the right methods rather than making real Google requests.
happyr.google.api.client service in Laravel's container, and 2.3.0's Drive/Slides services are Symfony-only too. Treat the bundle as documentation/reference only.composer.json Symfony and google/apiclient constraints before assuming it fits a modern stack. Even when it does, it is still Symfony-shaped glue; in Laravel use current google/apiclient plus a small in-app wrapper.Google_Client and Google_Service_Calendar; modern code should use Google\Client and Google\Service\Calendar. The old names are compatibility aliases and are deprecated. If you copy examples from older bundle docs or issues, translate the names first.refresh_token only on the first consent or when prompt=consent / approval_prompt=force is set. If you do not persist it immediately, the user must re-consent. Always store the full token response.localhost and 127.0.0.1 are different. This is the most common cause of redirect_uri_mismatch.isAccessTokenExpired() and refresh before the call, not after a 401. Persist the refreshed token; otherwise every request triggers a refresh.google/apiclient pulls its own Guzzle constraints. If the old bundle pins an older Guzzle, it may conflict with Laravel's HTTP client dependencies. Install the library directly and let Composer resolve modern versions.sub user to be a Workspace admin and the service account client ID to be authorized in the admin console. Clock skew can cause JWT errors; keep servers synced.Google\Service\Exception, inspect the code, and retry with exponential backoff for idempotent operations.happyr.google.api.client service definition (or the Drive/Slides definitions) with your own factory, or decorate them. Do not fork the bundle just to change client construction. In Laravel, skip the bundle and implement the same pattern in a service provider.How can I help you explore Laravel packages today?