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

ivory/google-map-bundle

Ivory Google Map Bundle integrates Google Maps into Symfony applications, providing services and Twig helpers to render maps, markers, info windows, and controls, with configurable map options and geocoding support.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and enable

composer require ivory/google-map-bundle
// config/bundles.php
Ivory\GoogleMapBundle\IvoryGoogleMapBundle::class => ['all' => true],
# config/packages/ivory_google_map.yaml
ivory_google_map:
    api_key: '%env(GOOGLE_MAPS_BROWSER_KEY)%'
    language: 'en'
    region: 'US'

Check vendor/ivory/google-map-bundle/composer.json first — this is a low-traffic fork (9 stars, 0 dependents) of the abandoned egeloen/ivory-google-map-bundle, so confirm the Symfony constraint matches your app before you build on it. Pin the exact versions.

Understand the two layers

  • ivory/google-map — the real library: a pure-PHP object model (Map, Coordinate, Marker, InfoWindow, overlay managers, JS builders).
  • ivory/google-map-bundle — thin Symfony glue: DI services (ivory_google_map.map, ivory_google_map.helper.*, the geocoder/directions/elevation HTTP services) plus Twig functions ivory_google_map() and ivory_google_map_js().

Almost everything you'll debug lives in the first layer. Read Ivory\GoogleMap\Map, Overlay\Marker, Overlay\InfoWindow, and Helper\MapHelper before anything else.

First use case: server-rendered display map

// src/Controller/StoreController.php
use Ivory\GoogleMap\Base\Coordinate;
use Ivory\GoogleMap\Map;
use Ivory\GoogleMap\Overlay\Marker;
use Ivory\GoogleMap\Overlay\InfoWindow;

$map = new Map();
$map->setHtmlId('store-map');                 // stable id, do this explicitly
$map->setCenter(new Coordinate(48.8566, 2.3522)); // lat, lon — in that order
$map->setZoom(13);

foreach ($stores as $store) {
    $marker = new Marker(new Coordinate($store->getLat(), $store->getLng()));
    $marker->setInfoWindow(new InfoWindow('<strong>'.htmlspecialchars($store->getName()).'</strong>'));
    $map->getOverlayManager()->addMarker($marker); // older versions: $map->addMarker()
}

return $this->render('store/index.html.twig', ['map' => $map]);
{# templates/store/index.html.twig #}
<div class="map-wrapper">{{ ivory_google_map(map) }}</div>

{% block javascripts %}
    {{ ivory_google_map_js(map) }}
{% endblock %}

Two calls, two jobs: ivory_google_map() emits the container <div>, ivory_google_map_js() emits the <script> that loads the Maps JS API and instantiates the map and overlays. Render the container first, the script after.

Implementation Patterns

Controller → Twig, with a map factory service. Don't scatter map construction across controllers. Extract a MapFactory that takes coordinates + options and returns a configured Map; controllers stay thin, and you can unit-test the factory (the Map object is pure PHP, no browser needed).

final class StoreMapFactory
{
    public function create(iterable $stores): Map
    {
        $map = new Map();
        $map->setHtmlId('store-map');
        $map->setCenter(new Coordinate(48.8566, 2.3522));
        $map->setZoom(12);
        $map->getOverlayManager()->addMarkerClusterer($this->clusterer($stores));
        return $map;
    }
}

Many points → marker clustering, not more markers. For anything past a few dozen markers, build a MarkerClusterer and add markers to it rather than to the map. Emitting thousands of individual new google.maps.Marker() calls in inline JS is the usual cause of "page feels slow" reports.

Interactive behavior via the event manager. Server-side map building is fine for read-only display. When you need click handlers, drag callbacks, or form population, attach events with JsCode on the map's event manager and keep the callback bodies tiny — one function call into a small Stimulus controller that owns the real logic:

$map->getEventManager()->addEvent($marker, 'click', new JsCode('window.dispatchEvent(new CustomEvent("store:selected", {detail: '.$store->getId().'}))'));

Render the JS without tags when you need control. ivory_google_map_js(map, false) (second arg toggles the <script> wrapper) lets you place the payload in a deferred script or hand it to your asset pipeline, which is what you want with Turbo/Turbolinks or a CSP that forbids inline scripts.

Asset loading order with modern front-ends. Under Turbo Drive or htmx partial swaps, the container <div> is replaced but the map instance is not re-created. Re-run the generated JS on turbo:load / after the swap, or mark the wrapper data-turbo-permanent.

Server-side Google APIs are separate services. The bundle also wires geocoder / directions / distance-matrix / elevation clients over an HTTP adapter. Use these from a service with a cache layer (PSR-6) — geocoding the same address on every request is both slow and billable. These calls need a server key (IP-restricted), not the browser key.

Testing. Assert on the model, not the HTML: $map->getOverlayManager()->getMarkers() gives you a countable array; $map->getCenter()->getLatitude() etc. For the rendered output, one smoke assertion that the response contains your html id and maps.googleapis.com is enough.

Gotchas and Tips

  • ivory_google_map.map is a shared service. Injecting it and mutating it leaks state between maps in the same request and, in long-running workers (RoadRunner, FrankenPHP, Messenger), across requests. Always new Map().
  • HTML ids must be unique and stable. The default id is derived from the object, so it changes per request and breaks any JS or CSS targeting it. Call setHtmlId() explicitly for every map.
  • The container div must exist before the script runs. If you emit ivory_google_map_js() in <head>, you get InvalidValueError: not an instance of Element or a silently blank canvas. Same failure mode if the div is inside a tab/modal that isn't in the DOM yet.
  • Missing or misconfigured key. No key → "For development purposes only" watermark and console warnings; wrong referrer restriction → RefererNotAllowedMapError. Browser key = HTTP-referrer restricted; server key = IP restricted. Two keys, two config entries.
  • Coordinate argument order is (latitude, longitude) everywhere, while Bound takes south-west/north-east corners. Swapped coordinates put your map in the ocean; there's no validation.
  • InfoWindow content is raw HTML. It is injected as-is into the generated JS. Escape or render a Twig partial and pass the escaped string — building it by string concatenation from user data is an XSS vector.
  • Escaping in JsCode. The builder does not escape your callback bodies. Use json_encode() for any dynamic values, never bare interpolation.
  • Debug by reading the generated script. View source and look at the output of ivory_google_map_js() — it's plain, readable google.maps.* calls. Copy it into the browser console to isolate whether a bug is in your PHP model or the JS.
  • Deprecated google.maps.Marker. The Maps JS API has moved to AdvancedMarkerElement; this bundle's JS builders still emit the classic marker. It works today but will eventually warn/break — one more reason to keep the map layer behind a small factory you can swap.
  • Extension points. Custom Twig helpers: implement the bundle's helper interface and override the ivory_google_map.helper.* service. Custom overlays: extend the library's base classes and register a matching JS builder. Custom map styling: pass a stylesheet array through the map options rather than post-processing the output.
  • Bus factor. 9 stars, zero dependents, last release January 2025. That's fine for a stable, thin wrapper — but keep all map construction behind your own factory/service so migrating to a hand-rolled Stimulus controller plus the Maps JS API (or a symfony/ux-style approach) is a contained change rather than a rewrite.
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.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings