Installation
composer require symfony/ux-map
npm install @symfony/ux-map
Basic Integration
Add the bundle to config/bundles.php:
return [
// ...
Symfony\UX\Map\MapBundle::class => ['all' => true],
];
First Use Case Embed a map in a Twig template:
{% import '@Map/map.html.twig' as map %}
{{ map.map({
'mapId': 'my-map',
'center': [48.8566, 2.3522], # [lat, lng]
'zoom': 12,
'markers': [
{ 'lat': 48.8566, 'lng': 2.3522, 'title': 'Paris' }
]
}) }}
Stimulus Controller
Register the controller in assets/controllers.js:
import { MapController } from '@symfony/ux-map';
const mapController = new MapController();
Dynamic Map Updates Use Stimulus actions to update map data via AJAX:
{{ map.map({
'mapId': 'dynamic-map',
'center': [0, 0], # Default
'markers': []
}, 'dynamic-map') }}
// assets/controllers/dynamic_map_controller.js
import { Controller } from '@hotwired/stimulus';
import { MapController } from '@symfony/ux-map';
export default class extends Controller {
static targets = ['map'];
connect() {
const map = new MapController();
this.mapController = map;
this.mapController.connect(this.mapTarget);
}
updateMarkers({ detail }) {
this.mapController.setMarkers(detail.markers);
}
}
Reusable Map Components Create a custom Twig component for maps:
{# templates/components/map_component.html.twig #}
{% import '@Map/map.html.twig' as map %}
{{ map.map({
'mapId': id,
'center': center,
'zoom': zoom,
'markers': markers,
'options': options
}, id) }}
Integration with Doctrine Entities Fetch coordinates from a database and pass them to the map:
// src/Controller/LocationController.php
public function show(Location $location): Response
{
return $this->render('location/show.html.twig', [
'mapData' => [
'center' => [$location->getLatitude(), $location->getLongitude()],
'markers' => [
[
'lat' => $location->getLatitude(),
'lng' => $location->getLongitude(),
'title' => $location->getName(),
]
],
],
]);
}
Custom Map Layers Extend the map with custom layers (e.g., GeoJSON):
{{ map.map({
'mapId': 'custom-layers-map',
'center': [40.7128, -74.0060],
'layers': [
{
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': [...]
}
}
]
}) }}
Stimulus Initialization Order
MapController is connected after the map element exists. Use data-controller="map" on the parent container.Coordinate Order
[lat, lng] and [lng, lat] formats can cause markers to appear in the wrong location.[lat, lng] (standard in Leaflet, the underlying library).Zooming to Markers
fitMarkers: true in options:
'options': { 'fitMarkers': true }
CORS for Custom Tiles
Check Stimulus Logs
Enable Stimulus debugging in assets/controllers.js:
import { Application } from '@hotwired/stimulus';
const application = Application.start();
application.debug = true; // Enable debug logs
Inspect Map Events Use browser dev tools to listen for Leaflet events:
this.mapController.map.on('click', (e) => {
console.log('Map clicked at:', e.latlng);
});
Validate JSON Data
Ensure markers and layers are valid JSON arrays/objects. Use json_encode() in PHP to sanitize data:
$markers = json_encode([
['lat' => 1.0, 'lng' => 2.0, 'title' => 'Test'],
]);
Custom Map Providers Extend the bundle to support other map libraries (e.g., Google Maps) by creating a wrapper class:
// src/Map/GoogleMapProvider.php
namespace App\Map;
use Symfony\UX\Map\MapProviderInterface;
class GoogleMapProvider implements MapProviderInterface
{
public function renderMap(array $options): string
{
// Custom Google Maps implementation
}
}
Add Interactive Features Use Leaflet plugins via Stimulus:
// assets/controllers/advanced_map_controller.js
import { MapController } from '@symfony/ux-map';
import L from 'leaflet';
export default class extends Controller {
connect() {
const map = new MapController();
map.map.on('click', (e) => {
L.marker(e.latlng).addTo(map.map)
.bindPopup('Clicked at ' + e.latlng.toString())
.openPopup();
});
}
}
Server-Side Geocoding Integrate with services like Nominatim or Google Geocoding API:
// src/Service/Geocoder.php
public function geocode(string $address): array
{
$response = file_get_contents("https://nominatim.openstreetmap.org/search?format=json&q=$address");
$data = json_decode($response, true);
return [
'lat' => $data[0]['lat'],
'lng' => $data[0]['lon'],
];
}
Performance Optimization
data-map-loaded="false" and load maps via Intersection Observer.MarkerCluster plugin for large datasets:
'options': {
'plugins': ['markercluster'],
'markerClusterOptions': {
'spiderfyOnMaxZoom': true
}
}
How can I help you explore Laravel packages today?