spotonlive/laravel-google-ads
A Laravel wrapper for the Google Ads API that simplifies authentication and lets you manage campaigns, ad groups, ads, and reporting from your PHP application. Includes configuration, service provider, and facade integration for quick setup.
composer require spotonlive/laravel-google-ads. On Laravel 5.5+ the service provider and GoogleAds facade are auto-discovered; on older apps register SpotonLive\GoogleAds\GoogleAdsServiceProvider in config/app.php and alias the facade manually.php artisan vendor:publish --provider="SpotonLive\GoogleAds\GoogleAdsServiceProvider" and open config/google-ads.php. This is the single source of truth for credentials and API settings — read it before reading any package source.php artisan config:clear. Keep secrets out of the repo.src/.SELECT campaign.id, campaign.name, metrics.clicks FROM campaign. Confirm you get rows before wiring anything into your app.The first real use case is almost always "campaign performance for the last N days" — get that working end-to-end in a tinker session before building around it.
Wrap the facade behind your own service. Don't scatter GoogleAds:: calls through controllers. Create a GoogleAdsReportService (or repository) that takes the facade/client via constructor injection and returns your own DTOs or collections. This insulates the rest of the app from the package's nested response arrays and makes it trivial to swap the implementation if the wrapper goes stale.
One client, many customer IDs. Credentials are configured once, but the target account varies per call. Pass the customer ID explicitly at call time, and set login-customer-id in config when you operate through an MCC. Treat customer IDs as strings everywhere (they can exceed 32-bit ints).
Keep GAQL in dedicated classes/constants. Store query strings in App\GoogleAds\Queries\* classes or constants rather than inlining multi-line strings in jobs. You'll reuse and diff them far more than you expect, and Google Ads requires field/segment compatibility that's easier to reason about in isolation.
Cache reports, don't hammer the API. Reporting data changes slowly. Key a cache entry by sha1($gaql . $customerId . $dateRange) with a 15–60 minute TTL, and warm it from a scheduled command (php artisan ads:warm-reports) so user requests hit cache instead of the API. This also smooths over quota limits.
Run heavy pulls in queued jobs. Large SearchStream-style reports belong in a job, not a request. Map rows into an Eloquent table (ads_campaign_daily) so dashboards query your DB, not Google. Schedule the job nightly.
Normalize Google's shapes immediately. Metrics like cost_micros and nested campaign/ad_group objects should be flattened into your DTOs at the boundary. Downstream code should never see $row['metrics']['costMicros'].
Testing. Mock the facade (GoogleAds::shouldReceive(...)->andReturn(...)) or swap the resolved instance in the container. Reset the facade/container between tests so singletons don't leak state. For integration tests use a dedicated test customer ID and gate them behind an env flag.
google-ads-php dependency must track them. Before committing, check composer.json for the pinned Google Ads library version and confirm that API version is still live. On modern PHP/Laravel you may need a fork, a version bump, or to bypass the wrapper and call googleads/google-ads-php directly. Budget for this — it's the difference between "quick integration" and "abandoned dependency."login-customer-id, run php artisan config:clear (and config:cache in deploy scripts) or you'll debug a ghost.invalid_grant gracefully.DEVELOPER_TOKEN_NOT_APPROVED / AUTHORIZATION_ERROR until then. These are account-setup errors, not code bugs.login-customer-id is mandatory when operating under an MCC — omitting it yields auth errors that look like permission problems.RESOURCE_EXHAUSTED. Implement exponential backoff and prefer batched/streaming reads and cached results over tight loops.*_micros by 1,000,000 for currency. Reporting dates are in the account's time zone, not your app's — don't mix segments.date with UTC assumptions.$this->app->bind(...)) rather than editing vendor code.How can I help you explore Laravel packages today?