Skip to content

Commit 553e49b

Browse files
committed
[Map] Enhance ux_map() + Add <twig:ux:map/> TwigComponent
* add an MapFactory (internal) * allow ux_map Twig function to render map from an array * add basic TwigComponent
1 parent e22484b commit 553e49b

14 files changed

+692
-7
lines changed

src/Map/CHANGELOG.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
## 2.20
44

5-
- Rename `render_map` Twig function `ux_map`
6-
- Deprecate `render_map` Twig function
5+
- Deprecate `render_map` Twig function (will be removed in 2.21). Use
6+
`ux_map` or the `<twig:ux:map />` Twig component instead.
7+
- Add `ux_map` Twig function (replaces `render_map` with a more flexible
8+
interface)
9+
- Add `<twig:ux:map />` Twig component
710

811
## 2.19
912

src/Map/composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"symfony/asset-mapper": "^6.4|^7.0",
4040
"symfony/framework-bundle": "^6.4|^7.0",
4141
"symfony/phpunit-bridge": "^6.4|^7.0",
42-
"symfony/twig-bundle": "^6.4|^7.0"
42+
"symfony/twig-bundle": "^6.4|^7.0",
43+
"symfony/ux-twig-component": "^2.18"
4344
},
4445
"extra": {
4546
"thanks": {

src/Map/config/services.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\UX\Map\Renderer\Renderer;
1616
use Symfony\UX\Map\Renderer\Renderers;
1717
use Symfony\UX\Map\Twig\MapExtension;
18+
use Symfony\UX\Map\Twig\MapRuntime;
1819

1920
/*
2021
* @author Hugo Alliaume <hugo@alliau.me>
@@ -26,7 +27,6 @@
2627
->args([
2728
abstract_arg('renderers configuration'),
2829
])
29-
->tag('twig.runtime')
3030

3131
->set('ux_map.renderer_factory.abstract', AbstractRendererFactory::class)
3232
->abstract()
@@ -41,5 +41,11 @@
4141

4242
->set('ux_map.twig_extension', MapExtension::class)
4343
->tag('twig.extension')
44+
45+
->set('ux_map.twig_runtime', MapRuntime::class)
46+
->args([
47+
service('ux_map.renderers'),
48+
])
49+
->tag('twig.runtime')
4450
;
4551
};

src/Map/config/twig_component.php

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\UX\Map\Twig\UXMapComponent;
15+
use Symfony\UX\Map\Twig\UXMapComponentListener;
16+
use Symfony\UX\TwigComponent\Event\PreCreateForRenderEvent;
17+
18+
return static function (ContainerConfigurator $container): void {
19+
$container->services()
20+
->set('.ux_map.twig_component_listener', UXMapComponentListener::class)
21+
->args([
22+
service('ux_map.renderers'),
23+
])
24+
->tag('kernel.event_listener', [
25+
'event' => PreCreateForRenderEvent::class,
26+
'method' => 'onPreCreateForRender',
27+
])
28+
29+
->set('.ux_map.twig_component.map', UXMapComponent::class)
30+
->tag('twig.component', ['key' => 'UX:Map'])
31+
;
32+
};

src/Map/src/MapFactory.php

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\Map;
13+
14+
/**
15+
* @author Simon André <smn.andre@gmail.com>
16+
*
17+
* @internal
18+
*/
19+
final class MapFactory
20+
{
21+
/**
22+
* @param array{
23+
* center?: array{lat: float, lng: float},
24+
* zoom?: float,
25+
* markers?: list<array{
26+
* position: array{lat: float, lng: float},
27+
* title?: string,
28+
* infoWindow?: array{
29+
* content?: string,
30+
* headerContent?: string,
31+
* },
32+
* }>,
33+
* } $array The array representation of the map
34+
*/
35+
public static function fromArray(array $array): Map
36+
{
37+
$map = new Map();
38+
39+
$map->fitBoundsToMarkers();
40+
41+
if (isset($array['center'])) {
42+
if (!\is_array($array['center'])) {
43+
throw new \InvalidArgumentException('The "center" parameter must be an array.');
44+
}
45+
46+
$map->center(self::createPoint($array['center']));
47+
$map->fitBoundsToMarkers(false);
48+
unset($array['center']);
49+
}
50+
51+
if (isset($array['zoom'])) {
52+
if (!is_numeric($array['zoom'])) {
53+
throw new \InvalidArgumentException('The "zoom" parameter must be numeric.');
54+
}
55+
56+
$map->zoom((float) $array['zoom']);
57+
$map->fitBoundsToMarkers(false);
58+
unset($array['zoom']);
59+
}
60+
61+
if (isset($array['markers'])) {
62+
if (!\is_array($array['markers'])) {
63+
throw new \InvalidArgumentException('The "markers" parameter must be an array.');
64+
}
65+
foreach ($array['markers'] as $marker) {
66+
if (!\is_array($marker)) {
67+
throw new \InvalidArgumentException('The "markers" parameter must be an array of arrays.');
68+
}
69+
$marker = self::createMarker($marker);
70+
$map = $map->addMarker($marker);
71+
}
72+
unset($array['markers']);
73+
}
74+
75+
if (\count($array) > 0) {
76+
throw new \InvalidArgumentException(\sprintf('Unknown map parameters: "%s"', implode(', ', array_keys($array))));
77+
}
78+
79+
return $map;
80+
}
81+
82+
private static function createMarker(array $marker): Marker
83+
{
84+
if (!\array_key_exists('position', $marker)) {
85+
throw new \InvalidArgumentException('The "position" parameter is required.');
86+
}
87+
if (!\is_array($marker['position'])) {
88+
throw new \InvalidArgumentException(\sprintf('The "position" parameter must be an array, "%s" given.', get_debug_type($marker['position'])));
89+
}
90+
$point = self::createPoint($marker['position']);
91+
unset($marker['position']);
92+
93+
$infoWindow = null;
94+
if (\array_key_exists('infoWindow', $marker)) {
95+
if (!\is_array($marker['infoWindow'])) {
96+
throw new \InvalidArgumentException(\sprintf('The "infoWindow" parameter must be an array, "%s" given.', get_debug_type($marker['infoWindow'])));
97+
}
98+
99+
$infoWindow = self::createInfoWindow($marker['infoWindow']);
100+
unset($marker['infoWindow']);
101+
}
102+
103+
$title = null;
104+
if (\array_key_exists('title', $marker)) {
105+
if (!\is_string($marker['title'])) {
106+
throw new \InvalidArgumentException(\sprintf('The "title" parameter must be a string, "%s" given.', get_debug_type($marker['title'])));
107+
}
108+
$title = $marker['title'];
109+
unset($marker['title']);
110+
}
111+
112+
$extra = [];
113+
if (\array_key_exists('extra', $marker)) {
114+
if (!\is_array($marker['extra'])) {
115+
throw new \InvalidArgumentException(\sprintf('The "extra" parameter must be an array, "%s" given.', get_debug_type($marker['extra'])));
116+
}
117+
$extra = $marker['extra'];
118+
unset($marker['extra']);
119+
}
120+
121+
if (\count($marker) > 0) {
122+
throw new \InvalidArgumentException(\sprintf('Unknown marker parameters: "%s".', implode('", "', array_keys($marker))));
123+
}
124+
125+
return new Marker($point, $title, $infoWindow, $extra);
126+
}
127+
128+
/**
129+
* @param array{content?: string, headerContent?: string} $infoWindow
130+
*/
131+
private static function createInfoWindow(array $infoWindow): InfoWindow
132+
{
133+
$headerContent = null;
134+
if (\array_key_exists('headerContent', $infoWindow)) {
135+
if (!\is_string($infoWindow['headerContent'])) {
136+
throw new \InvalidArgumentException(\sprintf('The "header" parameter must be a string, "%s" given.', get_debug_type($infoWindow['headerContent'])));
137+
}
138+
139+
$headerContent = $infoWindow['headerContent'];
140+
unset($infoWindow['headerContent']);
141+
}
142+
143+
$content = null;
144+
if (\array_key_exists('content', $infoWindow)) {
145+
if (!\is_string($infoWindow['content'])) {
146+
throw new \InvalidArgumentException(\sprintf('The "content" parameter must be a string, "%s" given.', get_debug_type($infoWindow['content'])));
147+
}
148+
149+
$content = $infoWindow['content'];
150+
unset($infoWindow['content']);
151+
}
152+
153+
if (\count($infoWindow) > 0) {
154+
throw new \InvalidArgumentException(\sprintf('Unknown "infoWindow" parameters: "%s".', implode('", "', array_keys($infoWindow))));
155+
}
156+
157+
if (!$headerContent && !$content) {
158+
throw new \InvalidArgumentException('The "infoWindow" parameter must have at least one of "header" or "content" values.');
159+
}
160+
161+
return new InfoWindow($headerContent, $content);
162+
}
163+
164+
/**
165+
* @param array{lat?: float, lng?: float} $point
166+
*/
167+
private static function createPoint(array $point): Point
168+
{
169+
if (!isset($point['lat']) || !isset($point['lng'])) {
170+
throw new \InvalidArgumentException('The Point parameter must be an array with "lat" and "lng" keys.');
171+
}
172+
if (!is_numeric($point['lat']) || !is_numeric($point['lng'])) {
173+
throw new \InvalidArgumentException('The "lat" and "lng" values must be numeric.');
174+
}
175+
176+
$lat = (float) $point['lat'];
177+
$lng = (float) $point['lng'];
178+
unset($point['lat'], $point['lng']);
179+
180+
if (\count($point) > 2) {
181+
throw new \InvalidArgumentException(\sprintf('Unknown Point parameters: "%s".', implode('", "', array_keys($point))));
182+
}
183+
184+
return new Point($lat, $lng);
185+
}
186+
}

src/Map/src/Twig/MapExtension.php

+2-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Symfony\UX\Map\Twig;
1313

14-
use Symfony\UX\Map\Renderer\Renderers;
1514
use Twig\DeprecatedCallableInfo;
1615
use Twig\Extension\AbstractExtension;
1716
use Twig\TwigFunction;
@@ -26,13 +25,13 @@ final class MapExtension extends AbstractExtension
2625
public function getFunctions(): array
2726
{
2827
return [
29-
new TwigFunction('render_map', [Renderers::class, 'renderMap'], [
28+
new TwigFunction('render_map', [MapRuntime::class, 'renderMap'], [
3029
'is_safe' => ['html'],
3130
...(class_exists(DeprecatedCallableInfo::class)
3231
? ['deprecation_info' => new DeprecatedCallableInfo('symfony/ux-map', '2.20', 'ux_map')]
3332
: ['deprecated' => '2.20', 'deprecating_package' => 'symfony/ux-map', 'alternative' => 'ux_map']),
3433
]),
35-
new TwigFunction('ux_map', [Renderers::class, 'renderMap'], ['is_safe' => ['html']]),
34+
new TwigFunction('ux_map', [MapRuntime::class, 'renderMap'], ['is_safe' => ['html']]),
3635
];
3736
}
3837
}

src/Map/src/Twig/MapRuntime.php

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\Map\Twig;
13+
14+
use Symfony\UX\Map\Map;
15+
use Symfony\UX\Map\MapFactory;
16+
use Symfony\UX\Map\Renderer\RendererInterface;
17+
use Twig\Extension\RuntimeExtensionInterface;
18+
19+
/**
20+
* @author Simon André <smn.andre@gmail.com>
21+
*
22+
* @internal
23+
*/
24+
final class MapRuntime implements RuntimeExtensionInterface
25+
{
26+
public function __construct(
27+
private readonly RendererInterface $renderer,
28+
) {
29+
}
30+
31+
/**
32+
* @param Map|array<string, mixed> $map
33+
* @param array<string, mixed> $attributes
34+
*/
35+
public function renderMap(Map|array $map, array $attributes = []): string
36+
{
37+
if (\is_array($map)) {
38+
if (isset($map['attr'])) {
39+
if (!\is_array($map['attr'])) {
40+
throw new \InvalidArgumentException(\sprintf('The "attr" parameter must be an array, "%s" given.', get_debug_type($map['attr'])));
41+
}
42+
$attributes = [...$map['attr'], ...$attributes];
43+
unset($map['attr']);
44+
}
45+
$map = MapFactory::fromArray($map);
46+
}
47+
48+
return $this->renderer->renderMap($map, $attributes);
49+
}
50+
}

src/Map/src/Twig/UXMapComponent.php

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\Map\Twig;
13+
14+
use Symfony\UX\Map\Marker;
15+
use Symfony\UX\Map\Point;
16+
17+
/**
18+
* @author Simon André <smn.andre@gmail.com>
19+
*
20+
* @internal
21+
*/
22+
final class UXMapComponent
23+
{
24+
public ?float $zoom;
25+
26+
public ?Point $center;
27+
28+
/**
29+
* @var Marker[]
30+
*/
31+
public array $markers;
32+
}

0 commit comments

Comments
 (0)