Repository: symfony/ux (php)

Issue:
[Map] Google map ID config when using a marker
Hi there,

I'm playing around with UX map with my Google account, and while I'm following a simple example from doc, I get an unexpected behavior;

```php
#[Route('/event/{id}', name: 'app_test')]
public function test(EventETT $event): Response
{
        $point = new Point(48.858370, 2.294481);

        $map = (new Map('default'))
            ->zoom(10)
            ->addMarker(new Marker($point))
            ->center($point)
        ;

        return $this->render('skeletons/event/show.html.twig', [
            'event' => $event,
            'map' => $map
        ]);
}
```
I think this is a very simple example based on docs, if I didn't made any mistake, I get the following behavior:

![image](https://github.com/user-attachments/assets/18b7c519-3f2a-477e-8f77-e7fa05b51590)

`Google Maps can't load correctly on this page.` in the canvas, and `The map is being initialized without a valid map ID, which prevents the use of advanced markers.` Well, OK, let's do this; I created a map ID on my cloud console, and then... I don't really know how to load it in my app. Here's [the official doc about it](https://developers.google.com/maps/documentation/get-map-id#javascript).

So here I am: did I made a mistake if I get stuck like this? If so, can I improve the doc to help future devs to not get stuck as me? If I didn't made a mistake, is there an elegant, already provided way to add this map ID? I expect a en variable the same way than `GOOGLE_MAPS_API_KEY` but where to mix them up to create the final url with map ID? I suppose it's related to `UX_MAP_DSN` but before going deeper and maybe propose an update here, I prefer asking here, because it's a veeery basic step where I'm stuck, so I wonder if there is an already implemented way of dealing with this. And if so, I would add it to the doc!

Pull request:
[Map] Introduce `ux_map.google_maps.default_map_id` configuration
| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes <!-- please update src/**/CHANGELOG.md files -->
| Issues        | Fix #2306 <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| License       | MIT

<!--
Replace this notice by a description of your feature/bugfix.
This will help reviewers and should be a good start for the documentation.

Additionally (see https://symfony.com/releases):
 - Always add tests and ensure they pass.
 - For new features, provide some code snippets to help understand usage.
 - Features and deprecations must be submitted against branch main.
 - Changelog entry should follow https://symfony.com/doc/current/contributing/code/conventions.html#writing-a-changelog-entry
 - Never break backward compatibility (see https://symfony.com/bc).
-->

With this modification, I can configure a default Google Maps `mapId`:
```yaml
# config/packages/ux_map.yaml
ux_map:
    # https://symfony.com/bundles/ux-map/current/index.html#available-renderers
    renderer: '%env(resolve:default::UX_MAP_DSN)%'
    google_maps:
        default_map_id: abcdefgh123456789
```
without having to manually pass the `mapId` when creating a `Map`:
```php
// use default mapId
$map = (new Map());
    ->center(new Point(48.8566, 2.3522))
    ->zoom(6);

// use default mapId
$map2 = (clone $map)
    ->options(new GoogleOptions());

// use custom mapId
$map3 = (clone $map)
    ->options(new GoogleOptions(mapId: 'foobar));
```

Patch:
diff --git a/src/Map/CHANGELOG.md b/src/Map/CHANGELOG.md
index 3883fe05..ef718fcb 100644
--- a/src/Map/CHANGELOG.md
+++ b/src/Map/CHANGELOG.md
@@ -1,4 +1,9 @@
 # CHANGELOG
+ 
+## 2.22
+
+-   Add method `Symfony\UX\Map\Renderer\AbstractRenderer::tapOptions()`, to allow Renderer to modify options before rendering a Map.
+-   Add `ux_map.google_maps.default_map_id` configuration to set the Google ``Map ID``
 
 ## 2.20
 
diff --git a/src/Map/config/twig_component.php b/src/Map/config/twig_component.php
index 096eec4a..0a3ad404 100644
--- a/src/Map/config/twig_component.php
+++ b/src/Map/config/twig_component.php
@@ -12,8 +12,6 @@
 namespace Symfony\Component\DependencyInjection\Loader\Configurator;
 
 use Symfony\UX\Map\Twig\UXMapComponent;
-use Symfony\UX\Map\Twig\UXMapComponentListener;
-use Symfony\UX\TwigComponent\Event\PreCreateForRenderEvent;
 
 return static function (ContainerConfigurator $container): void {
     $container->services()
diff --git a/src/Map/src/Bridge/Google/CHANGELOG.md b/src/Map/src/Bridge/Google/CHANGELOG.md
index 46f7d49c..48478b98 100644
--- a/src/Map/src/Bridge/Google/CHANGELOG.md
+++ b/src/Map/src/Bridge/Google/CHANGELOG.md
@@ -1,5 +1,11 @@
 # CHANGELOG
 
+## 2.22
+
+-   Add support for configuring a default Map ID
+-   Add argument `$defaultMapId` to `Symfony\UX\Map\Bridge\Google\Renderer\GoogleRendererFactory` constructor
+-   Add argument `$defaultMapId` to `Symfony\UX\Map\Bridge\Google\Renderer\GoogleRenderer` constructor
+
 ## 2.20
 
 ### BC Breaks
diff --git a/src/Map/src/Bridge/Google/src/GoogleOptions.php b/src/Map/src/Bridge/Google/src/GoogleOptions.php
index 8b26efcf..a241daa8 100644
--- a/src/Map/src/Bridge/Google/src/GoogleOptions.php
+++ b/src/Map/src/Bridge/Google/src/GoogleOptions.php
@@ -46,6 +46,11 @@ final class GoogleOptions implements MapOptionsInterface
         return $this;
     }
 
+    public function hasMapId(): bool
+    {
+        return null !== $this->mapId;
+    }
+
     public function gestureHandling(GestureHandling $gestureHandling): self
     {
         $this->gestureHandling = $gestureHandling;
diff --git a/src/Map/src/Bridge/Google/src/Renderer/GoogleRenderer.php b/src/Map/src/Bridge/Google/src/Renderer/GoogleRenderer.php
index 808278ab..cb325ba0 100644
--- a/src/Map/src/Bridge/Google/src/Renderer/GoogleRenderer.php
+++ b/src/Map/src/Bridge/Google/src/Renderer/GoogleRenderer.php
@@ -41,6 +41,7 @@ final readonly class GoogleRenderer extends AbstractRenderer
          * @var array<'core'|'maps'|'places'|'geocoding'|'routes'|'marker'|'geometry'|'elevation'|'streetView'|'journeySharing'|'drawing'|'visualization'>
          */
         private array $libraries = [],
+        private ?string $defaultMapId = null,
     ) {
         parent::__construct($stimulusHelper);
     }
@@ -66,7 +67,20 @@ final readonly class GoogleRenderer extends AbstractRenderer
 
     protected function getDefaultMapOptions(): MapOptionsInterface
     {
-        return new GoogleOptions();
+        return new GoogleOptions(mapId: $this->defaultMapId);
+    }
+
+    protected function tapOptions(MapOptionsInterface $options): MapOptionsInterface
+    {
+        if (!$options instanceof GoogleOptions) {
+            throw new \InvalidArgumentException(\sprintf('The options must be an instance of "%s", got "%s" instead.', GoogleOptions::class, get_debug_type($options)));
+        }
+
+        if (!$options->hasMapId()) {
+            $options->mapId($this->defaultMapId);
+        }
+
+        return $options;
     }
 
     public function __toString(): string
diff --git a/src/Map/src/Bridge/Google/src/Renderer/GoogleRendererFactory.php b/src/Map/src/Bridge/Google/src/Renderer/GoogleRendererFactory.php
index a391f676..e3495634 100644
--- a/src/Map/src/Bridge/Google/src/Renderer/GoogleRendererFactory.php
+++ b/src/Map/src/Bridge/Google/src/Renderer/GoogleRendererFactory.php
@@ -17,12 +17,20 @@ use Symfony\UX\Map\Renderer\AbstractRendererFactory;
 use Symfony\UX\Map\Renderer\Dsn;
 use Symfony\UX\Map\Renderer\RendererFactoryInterface;
 use Symfony\UX\Map\Renderer\RendererInterface;
+use Symfony\UX\StimulusBundle\Helper\StimulusHelper;
 
 /**
  * @author Hugo Alliaume <hugo@alliau.me>
  */
 final class GoogleRendererFactory extends AbstractRendererFactory implements RendererFactoryInterface
 {
+    public function __construct(
+        StimulusHelper $stimulus,
+        private ?string $defaultMapId = null,
+    ) {
+        parent::__construct($stimulus);
+    }
+
     public function create(Dsn $dsn): RendererInterface
     {
         if (!$this->supports($dsn)) {
@@ -42,6 +50,7 @@ final class GoogleRendererFactory extends AbstractRendererFactory implements Ren
             url: $dsn->getOption('url'),
             version: $dsn->getOption('version', 'weekly'),
             libraries: ['maps', 'marker', ...$dsn->getOption('libraries', [])],
+            defaultMapId: $this->defaultMapId,
         );
     }
 
diff --git a/src/Map/src/Renderer/AbstractRenderer.php b/src/Map/src/Renderer/AbstractRenderer.php
index aa229009..4471f9be 100644
--- a/src/Map/src/Renderer/AbstractRenderer.php
+++ b/src/Map/src/Renderer/AbstractRenderer.php
@@ -31,6 +31,18 @@ abstract readonly class AbstractRenderer implements RendererInterface
 
     abstract protected function getDefaultMapOptions(): MapOptionsInterface;
 
+    /**
+     * @template T of MapOptionsInterface
+     *
+     * @param T $options
+     *
+     * @return T
+     */
+    protected function tapOptions(MapOptionsInterface $options): MapOptionsInterface
+    {
+        return $options;
+    }
+
     final public function renderMap(Map $map, array $attributes = []): string
     {
         if (!$map->hasOptions()) {
@@ -39,6 +51,8 @@ abstract readonly class AbstractRenderer implements RendererInterface
             $map->options($defaultMapOptions);
         }
 
+        $map->options($this->tapOptions($map->getOptions()));
+
         $controllers = [];
         if ($attributes['data-controller'] ?? null) {
             $controllers[$attributes['data-controller']] = [];
diff --git a/src/Map/src/UXMapBundle.php b/src/Map/src/UXMapBundle.php
index 13929261..2f4bb35a 100644
--- a/src/Map/src/UXMapBundle.php
+++ b/src/Map/src/UXMapBundle.php
@@ -44,6 +44,12 @@ final class UXMapBundle extends AbstractBundle
         $rootNode
             ->children()
                 ->scalarNode('renderer')->defaultNull()->end()
+                ->arrayNode('google_maps')
+                    ->addDefaultsIfNotSet()
+                    ->children()
+                        ->scalarNode('default_map_id')->defaultNull()->end()
+                    ->end()
+                ->end()
             ->end()
         ;
     }
@@ -75,9 +81,17 @@ final class UXMapBundle extends AbstractBundle
         foreach (self::$bridges as $name => $bridge) {
             if (ContainerBuilder::willBeAvailable('symfony/ux-'.$name.'-map', $bridge['renderer_factory'], ['symfony/ux-map'])) {
                 $container->services()
-                    ->set('ux_map.renderer_factory.'.$name, $bridge['renderer_factory'])
+                    ->set($rendererFactoryName = 'ux_map.renderer_factory.'.$name, $bridge['renderer_factory'])
                     ->parent('ux_map.renderer_factory.abstract')
                     ->tag('ux_map.renderer_factory');
+
+                if ('google' === $name) {
+                    $container->services()
+                        ->get($rendererFactoryName)
+                        ->args([
+                            '$defaultMapId' => $config['google_maps']['default_map_id'],
+                        ]);
+                }
             }
         }
     }


Test patch:
diff --git a/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php b/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
index e688d0c8..358eb2fa 100644
--- a/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
+++ b/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
@@ -78,5 +78,23 @@ class GoogleRendererTest extends RendererTestCase
                     fullscreenControl: false,
                 )),
         ];
+
+        yield 'with default map id' => [
+            'expected_renderer' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;my_api_key&quot;}" data-symfony--ux-google-map--map-view-value="{&quot;center&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;zoom&quot;:12,&quot;fitBoundsToMarkers&quot;:false,&quot;options&quot;:{&quot;mapId&quot;:&quot;DefaultMapId&quot;,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20}},&quot;markers&quot;:[],&quot;polygons&quot;:[]}"></div>',
+            'renderer' => new GoogleRenderer(new StimulusHelper(null), 'my_api_key', defaultMapId: 'DefaultMapId'),
+            'map' => (clone $map),
+        ];
+        yield 'with default map id, when passing options (except the "mapId")' => [
+            'expected_renderer' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;my_api_key&quot;}" data-symfony--ux-google-map--map-view-value="{&quot;center&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;zoom&quot;:12,&quot;fitBoundsToMarkers&quot;:false,&quot;options&quot;:{&quot;mapId&quot;:&quot;DefaultMapId&quot;,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20}},&quot;markers&quot;:[],&quot;polygons&quot;:[]}"></div>',
+            'renderer' => new GoogleRenderer(new StimulusHelper(null), 'my_api_key', defaultMapId: 'DefaultMapId'),
+            'map' => (clone $map)
+                ->options(new GoogleOptions()),
+        ];
+        yield 'with default map id overridden by option "mapId"' => [
+            'expected_renderer' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;my_api_key&quot;}" data-symfony--ux-google-map--map-view-value="{&quot;center&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;zoom&quot;:12,&quot;fitBoundsToMarkers&quot;:false,&quot;options&quot;:{&quot;mapId&quot;:&quot;CustomMapId&quot;,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20}},&quot;markers&quot;:[],&quot;polygons&quot;:[]}"></div>',
+            'renderer' => new GoogleRenderer(new StimulusHelper(null), 'my_api_key', defaultMapId: 'DefaultMapId'),
+            'map' => (clone $map)
+                ->options(new GoogleOptions(mapId: 'CustomMapId')),
+        ];
     }
 }