meinfernbus/google-bundle
Symfony bundle for integrating Google services into PHP applications. It wires the Google API client into Symfony’s service container, simplifies configuration, and provides a consistent foundation for working with Google APIs in your project.
This is a Symfony bundle, not a Laravel package — there's no GoogleServiceProvider, no facade, and no config/google.php auto-merge. The realistic first step is to decide which parts you actually want, because you'll be wiring them by hand.
DependencyInjection/, Resources/config/services.yml, and the Map/ and Adwords/ directories. The value here is in the service classes (static map URL builders, caching decorators, remarketing tag renderers), not in the bundle plumbing.composer require meinfernbus/google-bundle will drag in antimattr/google-bundle ancestry and Symfony components. If your Laravel app is on Symfony 5/6 components, expect constraint conflicts; you may end up copying the handful of classes you need into app/Support/Google/ instead of requiring the package.StaticMap object (center, zoom, size, markers), get a URL, and cache the resulting image or URL.Minimal wiring in Laravel:
// config/google.php
return [
'api_key' => env('GOOGLE_MAPS_KEY'),
'cache_ttl' => 86400,
'cache_disk' => 'public',
];
// app/Support/Google/StaticMapFactory.php
use AntiMattr\GoogleBundle\Maps\StaticMap;
class StaticMapFactory
{
public function make(float $lat, float $lng, int $zoom = 14, string $size = '600x300'): StaticMap
{
$map = new StaticMap();
$map->setCenter(new \AntiMattr\GoogleBundle\Maps\Point($lat, $lng));
$map->setZoom($zoom);
$map->setSize($size);
return $map;
}
}
Look first at the StaticMapInterface / StaticMap classes and the cache decorator — those are the reusable core. Everything else (Twig helpers, Symfony config tree, event listeners) is dead weight in a Laravel app.
Cache the URL, not just the image. The cheapest win is memoizing the generated Static Maps URL keyed by a hash of its parameters. Google charges per request, so a Cache::remember around URL generation eliminates most spend on frequently-viewed listings.
$key = 'gmap:'.md5(serialize([$lat, $lng, $zoom, $size, $markers]));
$url = Cache::remember($key, config('google.cache_ttl'), function () use ($factory, $lat, $lng) {
return $factory->make($lat, $lng)->getUrl(config('google.api_key'));
});
Proxy and store images for hot content. For hero images or OG tags, fetch once and store on a disk, then serve from your own CDN. Combine with an event/listener (e.g. VenueUpdated) to invalidate:
Event::listen(VenueUpdated::class, function ($event) {
Cache::forget('gmap:'.md5(serialize($event->venue->mapParams())));
Storage::disk('public')->delete("maps/{$event->venue->id}.png");
});
Wrap markers in a value object. The bundle's marker API is array-ish and stringly-typed. Build a small MapMarker DTO and a MarkerCollection that knows how to serialize to the bundle's expected format — it keeps your controllers clean and makes swapping providers later trivial.
AdWords remarketing tags: render, don't couple. The bundle's remarketing support is essentially a tag renderer with conversion IDs/labels. In Laravel, put this behind a Blade component or @include partial and feed it config. Don't let the bundle's event system creep into your views.
@if(config('google.adwords.conversion_id'))
@include('partials.google-remarketing', ['conversion' => $conversion])
@endif
Queue image generation for batch jobs. Seeding 500 venues with maps? Dispatch a job per venue with rate limiting (RateLimited middleware) so you don't blow through the per-second quota. The bundle's cache decorator is synchronous — keep it out of the request path for bulk work.
Integration tip — adapt, don't inherit. Rather than subclassing bundle services (which lock you to its constructor signatures), write thin adapters in app/Support/Google/ that use the classes. When the fork inevitably dies, you replace the adapter, not your app.
symfony/* to 3.x/4.x ranges. On a modern Laravel (Symfony 6/7 components), Composer will refuse. Copying the classes is often faster than fighting the resolver.Cache facade is not PSR-compatible out of the box — either add symfony/cache with a FilesystemAdapter or bypass the decorator and cache URLs yourself in Laravel. The latter is usually simpler.REQUEST_DENIED almost always means key restrictions (HTTP referrer vs IP), missing billing, or an unsigned URL on a premium plan. Log the full URL (redact the key) when you get a non-200 from the Static Maps API — the bundle swallows response bodies in places.googlemaps/google-maps-services-php for the Maps APIs; a plain Blade partial for remarketing) when you next touch the feature.How can I help you explore Laravel packages today?