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.
## Getting Started
### Minimal Setup
1. **Installation**
```bash
composer require symfony/ux-leaflet-map ^2.35
npm install leaflet @symfony/ux-leaflet-map
Add to webpack.config.js (ensure compatibility with Symfony UX 3.x):
Encore
.addEntry('app', './assets/app.js')
.enableStimulusBridge('./assets/controllers.json')
.copyFiles(['public/images/**/*'])
.configureBabel((config) => {
config.plugins.push('@babel/plugin-proposal-class-properties');
});
Basic Controller Integration (Symfony UX 3.x compatible)
use Symfony\UX\LeafletMap\MapController;
class LocationController extends AbstractController
{
#[Route('/map', name: 'map')]
public function map(MapController $mapController): Response
{
return $this->render('location/map.html.twig', [
'mapController' => $mapController,
]);
}
}
First Template Usage (Symfony UX 3.x)
{{ include_controller(mapController) }}
Basic JavaScript Activation (Symfony UX 3.x)
// assets/controllers/map_controller.js
import { Controller } from '@hotwired/stimulus';
import { MapController } from '@symfony/ux-leaflet-map';
export default class extends Controller {
static values = { ...MapController.values };
connect() { MapController.connect(this); }
}
{{ include_controller(mapController, {
'center': [48.8566, 2.3522], // Paris coordinates
'zoom': 12,
'markers': [
{ 'lat': 48.8584, 'lng': 2.2945, 'popup': 'Eiffel Tower' }
]
}) }}
// Controller
$mapController->setOptions([
'center' => [$lat, $lng],
'markers' => $this->getMarkersFromDatabase(),
'theme' => 'dark', // New in v2.35 (optional)
]);
// assets/controllers/map_controller.js
import { Controller } from '@hotwired/stimulus';
import { MapController } from '@symfony/ux-leaflet-map';
export default class extends Controller {
static values = {
...MapController.values,
customOption: { type: String, default: 'default' } // Symfony UX 3.x default support
};
connect() {
MapController.connect(this);
this.map.setView(this.centerValue, this.zoomValue);
this.map.setMaxBounds([[40, -10], [60, 10]]);
}
}
{# location/_form.html.twig #}
<div {{ stimulus_controller('map', {
'center': [{{ form.lat.data }}, {{ form.lng.data }}],
'zoom': 14,
'theme': 'light' // New theme option
}) }}>
{{ include_controller(mapController) }}
</div>
{{ form_row(form.lat) }}
{{ form_row(form.lng) }}
// assets/controllers/map_controller.js
export default class extends Controller {
move(event) {
this.dispatch('map-moved', { detail: { lat: event.latLng.lat, lng: event.latLng.lng } });
}
// New in v2.35: TypeScript support
declare @customOption: string;
}
{# In parent template #}
<div data-controller="map" data-map-action="move->parent#handleMapMove">
{{ include_controller(mapController) }}
</div>
// assets/controllers/map_controller.js
export default class extends Controller {
addLayer() {
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(this.map);
}
}
// Controller
use Symfony\UX\LeafletMap\Geocoder\GeocoderInterface;
class LocationController extends AbstractController
{
public function __construct(private GeocoderInterface $geocoder) {}
#[Route('/search')]
public function search(string $query): Response
{
$results = $this->geocoder->geocode($query);
return $this->render('location/search.html.twig', [
'results' => $results,
]);
}
}
{{ include_controller(mapController, {
'markers': markers|map((marker) => {
'lat': marker.latitude,
'lng': marker.longitude,
'popup': marker.name,
'icon': {
'iconUrl': '/icons/' ~ marker.type ~ '.png',
'iconSize': [32, 32]
}
}),
'clusterOptions': {
'spiderfyOnMaxZoom': true,
'showCoverageOnHover': false
},
'theme': 'dark' // New theme option
}) }}
Symfony UX 3.x Compatibility
webpack.config.js includes Babel plugin for class properties and update Stimulus imports:
import { Controller } from '@hotwired/stimulus';
Coordinate Order
[lat, lng] but some APIs return [lng, lat].$coordinates = [$marker['lat'], $marker['lng']];
Tile Layer CORS Issues
Stimulus Controller Naming
MapController as a class name conflicts with Symfony’s MapController.LeafletMapController).Z-Index Conflicts
zIndex in CSS:
.leaflet-container { z-index: 1000; }
Check Webpack Output
npm run dev and inspect the browser console for Stimulus-related errors. Ensure Symfony UX 3.x compatibility:
npm install @hotwired/stimulus@^3.0
Inspect Stimulus Controllers
Log Map Events
connect() {
this.map.on('move', (e) => {
console.log('Map moved:', e.target.getCenter());
});
}
Validate JSON Options
include_controller are valid JSON. Use json_encode() in Twig:
{{ include_controller(mapController, {
'options': mapOptions|json_encode|raw,
'theme': 'light' // New theme option
}) }}
Default Center
[0, 0] (Atlantic Ocean). Always specify a center.Marker Icons
/images/marker.png). Use asset() in Twig:
'icon': {
'iconUrl': '{{ asset('images/marker.png') }}',
'iconSize': [25, 41]
}
Responsive Design
.leaflet-container {
width: 100%;
height: 100vh;
}
New Theme Options (v2.35)
'light', 'dark', or 'osm' (OpenStreetMap default).{{ include_controller(mapController, { 'theme': 'dark' }) }}
How can I help you explore Laravel packages today?