diff --git a/src/Icons/CHANGELOG.md b/src/Icons/CHANGELOG.md new file mode 100644 index 00000000000..3b1eb8f7fe0 --- /dev/null +++ b/src/Icons/CHANGELOG.md @@ -0,0 +1,10 @@ +# CHANGELOG + +## 2.19.0 + +- Add `ignore_not_found` option to silence error during rendering if the + icon is not found. + +## 2.17.0 + +- Add component diff --git a/src/Icons/composer.json b/src/Icons/composer.json index 498c36dcd22..14dfadb5adf 100644 --- a/src/Icons/composer.json +++ b/src/Icons/composer.json @@ -45,7 +45,8 @@ "symfony/http-client": "6.4|^7.0", "symfony/phpunit-bridge": "^6.3|^7.0", "symfony/ux-twig-component": "^2.14", - "zenstruck/console-test": "^1.5" + "zenstruck/console-test": "^1.5", + "psr/log": "^2|^3" }, "config": { "sort-packages": true diff --git a/src/Icons/config/services.php b/src/Icons/config/services.php index 02a8ed7e4d8..91a3484a602 100644 --- a/src/Icons/config/services.php +++ b/src/Icons/config/services.php @@ -19,6 +19,7 @@ use Symfony\UX\Icons\Registry\LocalSvgIconRegistry; use Symfony\UX\Icons\Twig\IconFinder; use Symfony\UX\Icons\Twig\UXIconExtension; +use Symfony\UX\Icons\Twig\UXIconRuntime; return static function (ContainerConfigurator $container): void { $container->services() @@ -44,11 +45,18 @@ ->set('.ux_icons.twig_icon_extension', UXIconExtension::class) ->tag('twig.extension') + ->set('.ux_icons.twig_icon_runtime', UXIconRuntime::class) + ->args([ + service('.ux_icons.icon_renderer'), + abstract_arg('ignore_not_found'), + service('logger')->ignoreOnInvalid(), + ]) + ->tag('twig.runtime') + ->set('.ux_icons.icon_renderer', IconRenderer::class) ->args([ service('.ux_icons.icon_registry'), ]) - ->tag('twig.runtime') ->alias('Symfony\UX\Icons\IconRendererInterface', '.ux_icons.icon_renderer') diff --git a/src/Icons/doc/index.rst b/src/Icons/doc/index.rst index f1ee241eda4..21a56c4b438 100644 --- a/src/Icons/doc/index.rst +++ b/src/Icons/doc/index.rst @@ -336,6 +336,19 @@ Now, all icons will have the ``fill`` attribute set to ``currentColor`` by defau # renders "user-profile.svg" with fill="red" {{ ux_icon('user-profile', {fill: 'red'}) }} +Errors +------ + +If an icon is not found, an exception is thrown. This is useful during development, +but in production, you may want to render an error message instead. You can do this +by setting the ``ignore_not_found`` configuration option to ``true``: + +.. code-block:: yaml + + # config/packages/ux_icons.yaml + ux_icons: + ignore_not_found: true + Accessibility ------------- @@ -515,6 +528,9 @@ Full Configuration # The endpoint for the Iconify API. endpoint: 'https://api.iconify.design' + + # Whether to ignore errors when an icon is not found. + ignore_not_found: false Learn more ---------- diff --git a/src/Icons/src/DependencyInjection/UXIconsExtension.php b/src/Icons/src/DependencyInjection/UXIconsExtension.php index c3ae0ab2b94..81e705b7926 100644 --- a/src/Icons/src/DependencyInjection/UXIconsExtension.php +++ b/src/Icons/src/DependencyInjection/UXIconsExtension.php @@ -58,6 +58,10 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->end() ->end() + ->booleanNode('ignore_not_found') + ->info('Ignore error when an icon is not found.') + ->defaultFalse() + ->end() ->end() ; @@ -69,7 +73,7 @@ public function getConfiguration(array $config, ContainerBuilder $container): Co return $this; } - protected function loadInternal(array $mergedConfig, ContainerBuilder $container): void // @phpstan-ignore-line + protected function loadInternal(array $mergedConfig, ContainerBuilder $container): void { $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../../config')); $loader->load('services.php'); @@ -96,6 +100,10 @@ protected function loadInternal(array $mergedConfig, ContainerBuilder $container ->setArgument(1, $mergedConfig['default_icon_attributes']) ; + $container->getDefinition('.ux_icons.twig_icon_runtime') + ->setArgument(1, $mergedConfig['ignore_not_found']) + ; + if ($mergedConfig['iconify']['enabled']) { $loader->load('iconify.php'); diff --git a/src/Icons/src/Twig/UXIconExtension.php b/src/Icons/src/Twig/UXIconExtension.php index 8a69ede30dc..92ffaac24c3 100644 --- a/src/Icons/src/Twig/UXIconExtension.php +++ b/src/Icons/src/Twig/UXIconExtension.php @@ -11,7 +11,6 @@ namespace Symfony\UX\Icons\Twig; -use Symfony\UX\Icons\IconRenderer; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; @@ -25,7 +24,7 @@ final class UXIconExtension extends AbstractExtension public function getFunctions(): array { return [ - new TwigFunction('ux_icon', [IconRenderer::class, 'renderIcon'], ['is_safe' => ['html']]), + new TwigFunction('ux_icon', [UXIconRuntime::class, 'renderIcon'], ['is_safe' => ['html']]), ]; } } diff --git a/src/Icons/src/Twig/UXIconRuntime.php b/src/Icons/src/Twig/UXIconRuntime.php new file mode 100644 index 00000000000..cac051aea84 --- /dev/null +++ b/src/Icons/src/Twig/UXIconRuntime.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Icons\Twig; + +use Psr\Log\LoggerInterface; +use Symfony\UX\Icons\Exception\IconNotFoundException; +use Symfony\UX\Icons\IconRendererInterface; +use Twig\Extension\RuntimeExtensionInterface; + +/** + * @author Simon André + * + * @internal + */ +final class UXIconRuntime implements RuntimeExtensionInterface +{ + public function __construct( + private readonly IconRendererInterface $iconRenderer, + private readonly bool $ignoreNotFound = false, + private readonly ?LoggerInterface $logger = null, + ) { + } + + /** + * @param array $attributes + */ + public function renderIcon(string $name, array $attributes = []): string + { + try { + return $this->iconRenderer->renderIcon($name, $attributes); + } catch (IconNotFoundException $e) { + if ($this->ignoreNotFound) { + $this->logger?->warning($e->getMessage()); + return ''; + } + + throw $e; + } + } +} diff --git a/src/Icons/tests/Unit/Twig/UXIconRuntimeTest.php b/src/Icons/tests/Unit/Twig/UXIconRuntimeTest.php new file mode 100644 index 00000000000..3c70bc7d667 --- /dev/null +++ b/src/Icons/tests/Unit/Twig/UXIconRuntimeTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Icons\Tests\Unit\Twig; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Symfony\UX\Icons\Exception\IconNotFoundException; +use Symfony\UX\Icons\IconRendererInterface; +use Symfony\UX\Icons\Twig\UXIconRuntime; + +/** + * @author Simon André + */ +class UXIconRuntimeTest extends TestCase +{ + public function testRenderIconIgnoreNotFound(): void + { + $renderer = $this->createMock(IconRendererInterface::class); + $renderer->method('renderIcon') + ->willThrowException(new IconNotFoundException('Icon "foo" not found.')); + + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('warning') + ->with('Icon "foo" not found.'); + + $runtime = new UXIconRuntime($renderer, true, $logger); + $this->assertEquals('', $runtime->renderIcon('not_found')); + + $runtime = new UXIconRuntime($renderer, false); + $this->expectException(IconNotFoundException::class); + $runtime->renderIcon('not_found'); + } +}