symfony/ux-leaflet-map
Symfony UX package integrating Leaflet maps into your app with Stimulus controllers and Twig components. Easily render interactive maps, markers, and layers while keeping configuration in PHP/Twig and assets managed via Symfony’s UX tooling.
composer require symfony/ux-leaflet-map
npm install --force # Only if using Webpack Encore
npm run watch # Restart Encore if needed
.env):
UX_MAP_DSN=leaflet://default
{{ ux_map(map, {'data-controller': 'map' }) }}
Where map is a Symfony\UX\Map\Map object configured in your controller.// src/Controller/MapController.php
use Symfony\UX\Map\Map;
use Symfony\UX\Map\Point;
use Symfony\UX\Map\Marker;
$map = (new Map())
->center(new Point(48.8566, 2.3522)) // Paris coordinates
->zoom(12)
->addMarker(new Marker(new Point(48.8584, 2.2945), 'Eiffel Tower'));
return $this->render('map/index.html.twig', ['map' => $map]);
$map = (new Map())->center(new Point($lat, $lng))->zoom(10);
foreach ($locations as $location) {
$map->addMarker(new Marker(new Point($location['lat'], $location['lng']), $location['name']));
}
$marker->on('click', function (MarkerClickEvent $event) {
// Handle click (e.g., fetch details via AJAX)
});
// assets/controllers/map_controller.js
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
connect() {
this.element.addEventListener('ux:map:marker:before-create', (event) => {
const { definition, L } = event.detail;
definition.bridgeOptions.icon = L.icon({ iconUrl: '/custom-icon.png' });
});
}
}
Register in Twig:
{{ ux_map(map, {'data-controller': 'map' }) }}
$leafletOptions = (new LeafletOptions())
->tileLayer(new TileLayer(
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
attribution: 'Custom Attribution'
));
$map->options($leafletOptions);
$leafletOptions = (new LeafletOptions())
->zoomControl(false)
->attributionControl(false);
{{ form_row(form.address) }}
{{ ux_map(map, {'data-controller': 'map', 'data-map-action': 'set-form-value~form.address' }) }}
Stimulus controller to sync form values:
connect() {
this.map = this.element.querySelector('[data-map]');
this.map.addEventListener('click', (e) => {
const latlng = e.latlng;
this.element.dispatchEvent(new CustomEvent('set-form-value', {
detail: { value: `${latlng.lat},${latlng.lng}` }
}));
});
}
{% if app.request.get('_route') == 'location_dashboard' %}
{{ ux_map(map, {'data-controller': 'map' }) }}
{% endif %}
let debounceTimer;
this.map.addEventListener('move', () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
// Fetch new data
}, 300);
});
Webpack Encore CSS Path Issue:
Module not found: leaflet/dist/leaflet.min.css.webpack.config.js:
Encore.addAliases({
'leaflet/dist/leaflet.min.css': 'leaflet/dist/leaflet.css',
});
Marker Icon Conflicts:
bridgeOptions.icon on a marker that already has an icon triggers a warning.extra data to pass custom icons:
$marker = new Marker($point, 'Label', ['icon_url' => '/custom-icon.png']);
Symfony UX Version Mismatch:
symfony/ux-leaflet-map v3.x requires Symfony 7.4+ and PHP 8.4+.composer require symfony/ux-leaflet-map:^2.35
Event Listener Leaks:
disconnect():
disconnect() {
this.element.removeEventListener('ux:map:marker:before-create', this._onMarkerBeforeCreate);
}
Inspect Map Events:
document.querySelector('[data-map]').addEventListener('ux:map:marker:click', (e) => {
console.log('Marker clicked:', e.detail);
});
Leaflet Console Debugging:
connect() {
this.map = this.element.querySelector('[data-map]').map;
console.log(this.map); // Inspect methods/options
}
Twig Debugging:
{{ dump(map) }}
Custom Layers:
_onLayerBeforeCreate(event) {
const { definition, L } = event.detail;
if (definition.type === 'geojson') {
definition.bridgeOptions = {
style: (feature) => ({ color: feature.properties.color || '#3388ff' })
};
}
}
Third-Party Plugins:
leaflet-routing-machine) by extending the Stimulus controller:
connect() {
this.map.on('load', () => {
L.Routing.control({ ... }).addTo(this.map);
});
}
Server-Side Rendering (SSR):
{% if app.request.isXmlHttpRequest or app.request.isTurboFrame %}
{{ ux_map(map, {'data-controller': 'map' }) }}
{% endif %}
Testing:
$client->executeScript("
document.querySelector('[data-map]').dispatchEvent(new CustomEvent('ux:map:marker:click', {
detail: { definition: { id: 'test-marker' } }
}));
");
$this->assertSelectorTextContains('h1', 'Marker clicked!');
How can I help you explore Laravel packages today?