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 Map Bundle Laravel Package

petrepatrasc/google-map-bundle

A Symfony bundle for integrating Google Maps into your application, helping you configure map instances, markers, and frontend assets from PHP. It simplifies embedding interactive maps and managing map-related settings with minimal setup in Symfony projects.

View on GitHub
Deep Wiki
Context7

Getting Started

First, accept the framing: you are not installing this package. petrepatrasc/google-map-bundle is a Symfony 2 bundle (AppKernel registration, a DependencyInjection/*Extension, XML/YAML service definitions, Twig helpers). Laravel has no bundle concept, so composer require petrepatrasc/google-map-bundle gets you a package that boots nothing, resolves no services, and registers no Blade directives. At 0 stars, 0 dependents, and a Symfony 2.x baseline, there is no version of this you should take a runtime dependency on.

What you do with it day-to-day is read it and reimplement the thin slice it wraps. The reusable payload is the underlying, framework-neutral map-builder library it depends on (an egeloen/ivory-google-map-style core). The bundle layer itself — DI wiring, config normalization, Twig rendering — is exactly the part you will throw away and rewrite in a few hundred lines.

Minimal setup order:

  1. Spike, 2 days max. composer show -a petrepatrasc/google-map-bundle (or read composer.json on GitHub) and identify the wrapped library + its version. Then inspect whether the builder/encoder classes carry Symfony annotations or container lookups. If they do, stop — treat it as a rewrite, not an extraction. If they're clean, you have a candidate for a vendored helper.
  2. Put the key in Laravel config, not in the package's YAML shape. The bundle's app/config/config.yml API-key pattern translates to config/services.php + .env:
// config/services.php
'google_maps' => [
    'browser_key' => env('GOOGLE_MAPS_BROWSER_KEY'),
    'server_key'  => env('GOOGLE_MAPS_SERVER_KEY'),
    'signing_secret' => env('GOOGLE_MAPS_SIGNING_SECRET'), // only if premium plan
],
GOOGLE_MAPS_BROWSER_KEY=AIza...   # HTTP-referrer restricted
GOOGLE_MAPS_SERVER_KEY=AIza...    # IP-restricted, never sent to the browser
  1. First use case: a single-marker interactive map in a Blade view. This is the 90% case and it replaces everything the bundle's Twig helper did. Build it as a Blade component so the "map widget" surface is one call site you can later swap for Leaflet/MapLibre without touching feature code:
{{-- resources/views/components/map.blade.php --}}
@props(['id' => 'map', 'center' => ['lat' => 0, 'lng' => 0], 'zoom' => 12, 'markers' => []])

<div id="{{ $id }}" style="height:400px" data-center='@json($center)' data-zoom="{{ $zoom }}"
     data-markers='@json($markers)'></div>

@once
    <script>
    window.initMaps = window.initMaps || [];
    </script>
    <script async
        src="https://maps.googleapis.com/maps/api/js?key={{ config('services.google_maps.browser_key') }}&loading=async&callback=__gmapsReady">
    </script>
@push('scripts')
    <script>
    window.__gmapsReady = () => window.initMaps.forEach(fn => fn());
    </script>
@endpush
@endonce

Do not port the bundle's inline <script> emission verbatim. Older Symfony-era helpers emit inline JS that a nonce-based CSP will block; see Gotchas.

Implementation Patterns

1. Provider + config translation, not bundle translation. If you do extract the core classes, the mapping table is short and worth internalizing before you start estimating:

Bundle artifact Laravel equivalent Mechanical port?
*Bundle class ServiceProvider (package discovery) No — rewrite
DependencyInjection/Configuration tree config/*.php + mergeConfigFrom() Schema only
XML/YAML service defs Container bindings No — rewrite
Twig extension helpers Blade component / directive No — rewrite
Map builder / encoder classes Framework-agnostic service Yes, if truly decoupled

Only the last row is reusable. Validate it before spending time on the rest.

2. Treat map config as a DTO, not an array soup. The bundle's value was normalizing options; keep that idea but make it typed and testable:

final class MapView
{
    public function __construct(
        public readonly array $center,   // ['lat' => 51.5, 'lng' => -0.12]
        public readonly int $zoom = 12,
        public readonly array $markers = [],
    ) {}

    public static function fromModels(iterable $stores): self
    {
        return new self(
            center: ['lat' => (float) config('services.maps.default_lat'),
                     'lng' => (float) config('services.maps.default_lng')],
            markers: collect($stores)->map(fn ($s) => [
                'lat' => (float) $s->latitude,
                'lng' => (float) $s->longitude,
                'title' => $s->name,
            ])->all(),
        );
    }
}

Pass it to the component as ['lat' => ..., 'lng' => ...] and @json it into the data attribute. This is where the bundle gave you "clean presentation logic" — a view-model plus a Blade component gives you the same thing without the dependency.

3. Keep server-side work server-side. Two things genuinely belong in PHP rather than the browser: static map URLs and polyline encoding. Both are pure functions and both are exactly what you'd extract from the bundle's core — or reimplement in ~150 lines.

// app/Support/Maps/StaticMap.php
public function url(array $markers, int $width = 600, int $height = 400): string
{
    $path = '/maps/api/staticmap';
    $params = [
        'size' => "{$width}x{$height}",
        'maptype' => 'roadmap',
        'markers' => collect($markers)->map(fn ($m) => "{$m['lat']},{$m['lng']}")->implode('|'),
        'key' => config('services.google_maps.server_key'),
    ];
    ksort($params);
    $query = http_build_query($params);

    if ($secret = config('services.google_maps.signing_secret')) {
        $sig = rtrim(strtr(base64_encode(
            hash_hmac('sha1', $path.'?'.$query, $secret, true)
        ), '+/', '-_'), '=');
        $query .= '&signature='.$sig;
    }

    return 'https://maps.googleapis.com'.$path.'?'.$query;
}

4. Cache geocoding results with a hard TTL. Google's ToS caps how long you may retain geocoded coordinates. Don't let a Cache::rememberForever() creep in:

$coords = Cache::remember("geocode:{$hash}", now()->addDays(29), function () use ($address) {
    return Http::get('https://maps.googleapis.com/maps/api/geocode/json', [
        'address' => $address,
        'key' => config('services.google_maps.server_key'),
    ])->throw()->json('results.0.geometry.location');
});

5. Lazy-initialize maps behind a callback registry. The modern JS API wants loading=async&callback=. Push one initializer per map onto window.initMaps and let a single callback drain it. This mirrors the bundle's "render container, then render init script" split but is SPA/Turbo-safe:

document.addEventListener('turbo:load', () => {
  document.querySelectorAll('[data-map]').forEach(el => {
    const map = new google.maps.Map(el, {
      center: JSON.parse(el.dataset.center),
      zoom: +el.dataset.zoom,
    });
    JSON.parse(el.dataset.markers).forEach(m =>
      new google.maps.Marker({ position: m, map, title: m.title }));
    el._mapInstance = map; // hold reference; see Gotchas
  });
});

6. Test the encoders, mock the network. Http::fake() for geocode/directions; a fixture-based test for polyline encoding against a known encoded string; and a snapshot assertion on the static-map URL shape (including signature) so an API drift shows up as a failing test, not a broken page.

7. Cut over behind a feature flag. Map rendering is stateless. Roll out per-route with Feature::active('maps') in the component, keep the old surface for one release, flip back if Google returns errors.

Gotchas and Tips

  • PHP compatibility is a hard blocker, not a nuisance. Expect composer.json pinned around PHP 5.x and code that uses curly-brace string offsets, each(), or dynamic properties — all removed or deprecated by PHP 8. Laravel 10/11 requires 8.1+, so any extraction needs an explicit modernization pass and a version pin in your own composer.json.
  • Inline <script> from Twig helpers will break a nonce CSP. If you port rendering logic, emit a Blade component with a @push('scripts') block and nonce it, or you'll spend an afternoon chasing blocked scripts.
  • Version pinning on the JS API is a latent break. The old "v3" is evergreen now. Use v=weekly (or omit) plus loading=async&callback=, never a pinned v=3.x string.
  • Two keys, two restrictions. Browser key → HTTP-referrer restricted. Server key → IP restricted. Sending the server key to the browser or reusing one key for both is the most common and most expensive mistake (billing abuse). The bundle has no opinion here; your config/services.php must.
  • Billing account is mandatory. A keyless or billing-less key now returns a grey "for development purposes only" map or BillingNotEnabledMapError. Budget for this before feature work — it's a procurement task, not a code task.
  • Distinguish the error codes when debugging. RefererNotAllowedMapError = referrer restriction mismatch (note localhost needs its own entry). ApiNotActivatedMapError = Maps JS API not enabled on the project. InvalidKeyMapError = wrong key for the surface. Check the browser network tab first; the server side is rarely the culprit for blank maps.
  • Sign static-map URLs on the server, and only there. The signing secret must never appear in a Blade-rendered URL or a JS bundle. If it's in .env on dev machines, that's fine — but treat production as a secrets-manager value.
  • @json in a Blade attribute is XSS-safe only if you keep the quoting. Use single quotes around the attribute, or Js::from() on Laravel 9+, which escapes correctly for embedded JSON.
  • Memory leaks in SPA/Turbo navigation. Store the map instance (el._mapInstance) and call google.maps.event.clearInstanceListeners(el._mapInstance) on teardown. The bundle's "render once per page" assumption doesn't survive client-side navigation.
  • Cluster before you scale. Above a few hundred markers, plain Markers tank the frame rate. Use @googlemaps/markerclusterer — don't expect the wrapper to have solved this.
  • Respect geocode retention limits. Cache geocodes with a ≤30-day TTL, and don't render Google-geocoded coordinates on a non-Google basemap. This is your ToS obligation once you own the wrapper; a stale bundle will not encode it for you.
  • Float precision and type coercion. Lat/lng arriving from MySQL DECIMAL, Eloquent casts, or form input will silently become strings. Cast to float in the DTO or google.maps.LatLng will warn and occasionally misplace the pin.
  • Extension point worth knowing: because the only durable artifact is a Blade component plus two helper classes, swapping to self-hosted Leaflet/MapLibre with OpenStreetMap later is a single-file change. Design for that now — it's the cheapest insurance against Google's terms or pricing shifting under you.
  • If the code review is "should we fork the bundle itself?" — no. You'd inherit Symfony-shaped abstractions, rewrite them anyway, and take on maintenance for code you didn't design. Reimplement the slice; keep the MIT-licensed encoder logic if it's clean and tested, and nothing else.
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.
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo
bernskioldmedia/laravel-harvest
bernskiold/laravel-data-scrubber
bernskiold/laravel-currency-converter
bernskiold/laravel-campaign-monitor
bernskioldmedia/laravel-autotranslate
siberfx/telegram-bot
roy-sandip/laravel-view-extended
gregsphotography/site-builder-core
roy-sandip/laravel-view
anodyne/blade-fluentui-icons
webx-ui/module-catalog-collections