Skip to content

Commit

Permalink
[Map] Enhance ux_map() + Add <twig:ux:map/> TwigComponent
Browse files Browse the repository at this point in the history
* add an MapFactory (internal)
* allow ux_map Twig function to render map from an array
* add basic TwigComponent
  • Loading branch information
smnandre committed Sep 11, 2024
1 parent e22484b commit 553e49b
Show file tree
Hide file tree
Showing 14 changed files with 692 additions and 7 deletions.
7 changes: 5 additions & 2 deletions src/Map/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

## 2.20

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

## 2.19

Expand Down
3 changes: 2 additions & 1 deletion src/Map/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"symfony/asset-mapper": "^6.4|^7.0",
"symfony/framework-bundle": "^6.4|^7.0",
"symfony/phpunit-bridge": "^6.4|^7.0",
"symfony/twig-bundle": "^6.4|^7.0"
"symfony/twig-bundle": "^6.4|^7.0",
"symfony/ux-twig-component": "^2.18"
},
"extra": {
"thanks": {
Expand Down
8 changes: 7 additions & 1 deletion src/Map/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\UX\Map\Renderer\Renderer;
use Symfony\UX\Map\Renderer\Renderers;
use Symfony\UX\Map\Twig\MapExtension;
use Symfony\UX\Map\Twig\MapRuntime;

/*
* @author Hugo Alliaume <[email protected]>
Expand All @@ -26,7 +27,6 @@
->args([
abstract_arg('renderers configuration'),
])
->tag('twig.runtime')

->set('ux_map.renderer_factory.abstract', AbstractRendererFactory::class)
->abstract()
Expand All @@ -41,5 +41,11 @@

->set('ux_map.twig_extension', MapExtension::class)
->tag('twig.extension')

->set('ux_map.twig_runtime', MapRuntime::class)
->args([
service('ux_map.renderers'),
])
->tag('twig.runtime')
;
};
32 changes: 32 additions & 0 deletions src/Map/config/twig_component.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

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()
->set('.ux_map.twig_component_listener', UXMapComponentListener::class)
->args([
service('ux_map.renderers'),
])
->tag('kernel.event_listener', [
'event' => PreCreateForRenderEvent::class,
'method' => 'onPreCreateForRender',
])

->set('.ux_map.twig_component.map', UXMapComponent::class)
->tag('twig.component', ['key' => 'UX:Map'])
;
};
186 changes: 186 additions & 0 deletions src/Map/src/MapFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\Map;

/**
* @author Simon André <[email protected]>
*
* @internal
*/
final class MapFactory
{
/**
* @param array{
* center?: array{lat: float, lng: float},
* zoom?: float,
* markers?: list<array{
* position: array{lat: float, lng: float},
* title?: string,
* infoWindow?: array{
* content?: string,
* headerContent?: string,
* },
* }>,
* } $array The array representation of the map
*/
public static function fromArray(array $array): Map
{
$map = new Map();

$map->fitBoundsToMarkers();

if (isset($array['center'])) {
if (!\is_array($array['center'])) {
throw new \InvalidArgumentException('The "center" parameter must be an array.');
}

$map->center(self::createPoint($array['center']));
$map->fitBoundsToMarkers(false);
unset($array['center']);
}

if (isset($array['zoom'])) {
if (!is_numeric($array['zoom'])) {
throw new \InvalidArgumentException('The "zoom" parameter must be numeric.');
}

$map->zoom((float) $array['zoom']);
$map->fitBoundsToMarkers(false);
unset($array['zoom']);
}

if (isset($array['markers'])) {
if (!\is_array($array['markers'])) {
throw new \InvalidArgumentException('The "markers" parameter must be an array.');
}
foreach ($array['markers'] as $marker) {
if (!\is_array($marker)) {
throw new \InvalidArgumentException('The "markers" parameter must be an array of arrays.');
}
$marker = self::createMarker($marker);
$map = $map->addMarker($marker);
}
unset($array['markers']);
}

if (\count($array) > 0) {
throw new \InvalidArgumentException(\sprintf('Unknown map parameters: "%s"', implode(', ', array_keys($array))));
}

return $map;
}

private static function createMarker(array $marker): Marker
{
if (!\array_key_exists('position', $marker)) {
throw new \InvalidArgumentException('The "position" parameter is required.');
}
if (!\is_array($marker['position'])) {
throw new \InvalidArgumentException(\sprintf('The "position" parameter must be an array, "%s" given.', get_debug_type($marker['position'])));
}
$point = self::createPoint($marker['position']);
unset($marker['position']);

$infoWindow = null;
if (\array_key_exists('infoWindow', $marker)) {
if (!\is_array($marker['infoWindow'])) {
throw new \InvalidArgumentException(\sprintf('The "infoWindow" parameter must be an array, "%s" given.', get_debug_type($marker['infoWindow'])));
}

$infoWindow = self::createInfoWindow($marker['infoWindow']);
unset($marker['infoWindow']);
}

$title = null;
if (\array_key_exists('title', $marker)) {
if (!\is_string($marker['title'])) {
throw new \InvalidArgumentException(\sprintf('The "title" parameter must be a string, "%s" given.', get_debug_type($marker['title'])));
}
$title = $marker['title'];
unset($marker['title']);
}

$extra = [];
if (\array_key_exists('extra', $marker)) {
if (!\is_array($marker['extra'])) {
throw new \InvalidArgumentException(\sprintf('The "extra" parameter must be an array, "%s" given.', get_debug_type($marker['extra'])));
}
$extra = $marker['extra'];
unset($marker['extra']);
}

if (\count($marker) > 0) {
throw new \InvalidArgumentException(\sprintf('Unknown marker parameters: "%s".', implode('", "', array_keys($marker))));
}

return new Marker($point, $title, $infoWindow, $extra);
}

/**
* @param array{content?: string, headerContent?: string} $infoWindow
*/
private static function createInfoWindow(array $infoWindow): InfoWindow
{
$headerContent = null;
if (\array_key_exists('headerContent', $infoWindow)) {
if (!\is_string($infoWindow['headerContent'])) {
throw new \InvalidArgumentException(\sprintf('The "header" parameter must be a string, "%s" given.', get_debug_type($infoWindow['headerContent'])));
}

$headerContent = $infoWindow['headerContent'];
unset($infoWindow['headerContent']);
}

$content = null;
if (\array_key_exists('content', $infoWindow)) {
if (!\is_string($infoWindow['content'])) {
throw new \InvalidArgumentException(\sprintf('The "content" parameter must be a string, "%s" given.', get_debug_type($infoWindow['content'])));
}

$content = $infoWindow['content'];
unset($infoWindow['content']);
}

if (\count($infoWindow) > 0) {
throw new \InvalidArgumentException(\sprintf('Unknown "infoWindow" parameters: "%s".', implode('", "', array_keys($infoWindow))));
}

if (!$headerContent && !$content) {
throw new \InvalidArgumentException('The "infoWindow" parameter must have at least one of "header" or "content" values.');
}

return new InfoWindow($headerContent, $content);
}

/**
* @param array{lat?: float, lng?: float} $point
*/
private static function createPoint(array $point): Point
{
if (!isset($point['lat']) || !isset($point['lng'])) {
throw new \InvalidArgumentException('The Point parameter must be an array with "lat" and "lng" keys.');
}
if (!is_numeric($point['lat']) || !is_numeric($point['lng'])) {
throw new \InvalidArgumentException('The "lat" and "lng" values must be numeric.');
}

$lat = (float) $point['lat'];
$lng = (float) $point['lng'];
unset($point['lat'], $point['lng']);

if (\count($point) > 2) {
throw new \InvalidArgumentException(\sprintf('Unknown Point parameters: "%s".', implode('", "', array_keys($point))));
}

return new Point($lat, $lng);
}
}
5 changes: 2 additions & 3 deletions src/Map/src/Twig/MapExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

namespace Symfony\UX\Map\Twig;

use Symfony\UX\Map\Renderer\Renderers;
use Twig\DeprecatedCallableInfo;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
Expand All @@ -26,13 +25,13 @@ final class MapExtension extends AbstractExtension
public function getFunctions(): array
{
return [
new TwigFunction('render_map', [Renderers::class, 'renderMap'], [
new TwigFunction('render_map', [MapRuntime::class, 'renderMap'], [
'is_safe' => ['html'],
...(class_exists(DeprecatedCallableInfo::class)
? ['deprecation_info' => new DeprecatedCallableInfo('symfony/ux-map', '2.20', 'ux_map')]
: ['deprecated' => '2.20', 'deprecating_package' => 'symfony/ux-map', 'alternative' => 'ux_map']),
]),
new TwigFunction('ux_map', [Renderers::class, 'renderMap'], ['is_safe' => ['html']]),
new TwigFunction('ux_map', [MapRuntime::class, 'renderMap'], ['is_safe' => ['html']]),
];
}
}
50 changes: 50 additions & 0 deletions src/Map/src/Twig/MapRuntime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\Map\Twig;

use Symfony\UX\Map\Map;
use Symfony\UX\Map\MapFactory;
use Symfony\UX\Map\Renderer\RendererInterface;
use Twig\Extension\RuntimeExtensionInterface;

/**
* @author Simon André <[email protected]>
*
* @internal
*/
final class MapRuntime implements RuntimeExtensionInterface
{
public function __construct(
private readonly RendererInterface $renderer,
) {
}

/**
* @param Map|array<string, mixed> $map
* @param array<string, mixed> $attributes
*/
public function renderMap(Map|array $map, array $attributes = []): string
{
if (\is_array($map)) {
if (isset($map['attr'])) {
if (!\is_array($map['attr'])) {
throw new \InvalidArgumentException(\sprintf('The "attr" parameter must be an array, "%s" given.', get_debug_type($map['attr'])));
}
$attributes = [...$map['attr'], ...$attributes];
unset($map['attr']);
}
$map = MapFactory::fromArray($map);
}

return $this->renderer->renderMap($map, $attributes);
}
}
32 changes: 32 additions & 0 deletions src/Map/src/Twig/UXMapComponent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\Map\Twig;

use Symfony\UX\Map\Marker;
use Symfony\UX\Map\Point;

/**
* @author Simon André <[email protected]>
*
* @internal
*/
final class UXMapComponent
{
public ?float $zoom;

public ?Point $center;

/**
* @var Marker[]
*/
public array $markers;
}
Loading

0 comments on commit 553e49b

Please sign in to comment.