Skip to content

Commit 1ead83a

Browse files
committed
[Icons] improve DX when symfony/http-client is not installed
- throw exception if http-client is not available - always enable iconify, iconify commands and on-demand registry - display "potential" missing icons to `ux:icons:warm-cache`
1 parent ed705f3 commit 1ead83a

11 files changed

+137
-97
lines changed

src/Icons/config/iconify.php

-56
This file was deleted.

src/Icons/config/services.php

+39
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,17 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

14+
use Symfony\UX\Icons\Command\ImportIconCommand;
15+
use Symfony\UX\Icons\Command\LockIconsCommand;
16+
use Symfony\UX\Icons\Command\SearchIconCommand;
1417
use Symfony\UX\Icons\Command\WarmCacheCommand;
1518
use Symfony\UX\Icons\IconCacheWarmer;
19+
use Symfony\UX\Icons\Iconify;
1620
use Symfony\UX\Icons\IconRenderer;
1721
use Symfony\UX\Icons\IconRendererInterface;
1822
use Symfony\UX\Icons\Registry\CacheIconRegistry;
1923
use Symfony\UX\Icons\Registry\ChainIconRegistry;
24+
use Symfony\UX\Icons\Registry\IconifyOnDemandRegistry;
2025
use Symfony\UX\Icons\Registry\LocalSvgIconRegistry;
2126
use Symfony\UX\Icons\Twig\IconFinder;
2227
use Symfony\UX\Icons\Twig\UXIconExtension;
@@ -86,5 +91,39 @@
8691
service('.ux_icons.cache_warmer'),
8792
])
8893
->tag('console.command')
94+
95+
->set('.ux_icons.iconify', Iconify::class)
96+
->args([
97+
service('.ux_icons.cache'),
98+
abstract_arg('endpoint'),
99+
service('http_client')->nullOnInvalid(),
100+
])
101+
102+
->set('.ux_icons.iconify_on_demand_registry', IconifyOnDemandRegistry::class)
103+
->args([
104+
service('.ux_icons.iconify'),
105+
])
106+
->tag('ux_icons.registry', ['priority' => -10])
107+
108+
->set('.ux_icons.command.import', ImportIconCommand::class)
109+
->args([
110+
service('.ux_icons.iconify'),
111+
service('.ux_icons.local_svg_icon_registry'),
112+
])
113+
->tag('console.command')
114+
115+
->set('.ux_icons.command.lock', LockIconsCommand::class)
116+
->args([
117+
service('.ux_icons.iconify'),
118+
service('.ux_icons.local_svg_icon_registry'),
119+
service('.ux_icons.icon_finder'),
120+
])
121+
->tag('console.command')
122+
123+
->set('.ux_icons.command.search', SearchIconCommand::class)
124+
->args([
125+
service('.ux_icons.iconify'),
126+
])
127+
->tag('console.command')
89128
;
90129
};

src/Icons/doc/index.rst

+14
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,20 @@ In production, you can pre-warm the cache by running the following command:
472472
This command looks in all your Twig templates for ``ux_icon()`` calls and
473473
``<twig:ux:icon>`` tags and caches the icons it finds.
474474

475+
Add ``-v`` to the command to see a list of icons being cached and potential
476+
missing icons:
477+
478+
.. code-block:: terminal
479+
480+
$ php bin/console ux:icons:warm-cache -v
481+
482+
.. caution::
483+
484+
The process to find icons to cache in your Twig templates is imperfect. It
485+
looks for any string that matches the pattern ``something:something`` so
486+
its probable there will be false positives. This command should not be used
487+
to audit the icons in your templates in an automated way.
488+
475489
.. caution::
476490

477491
Icons that have a name built dynamically will not be cached. It's advised to

src/Icons/src/Command/WarmCacheCommand.php

+5
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
4545
$io->writeln(\sprintf(' Warmed icon <comment>%s</comment>.', $name));
4646
}
4747
},
48+
onFailure: function (string $name, \Exception $e) use ($io) {
49+
if ($io->isVerbose()) {
50+
$io->writeln(\sprintf(' Failed to warm (potential) icon <error>%s</error>.', $name));
51+
}
52+
}
4853
);
4954

5055
$io->success('Icon cache warmed.');

src/Icons/src/DependencyInjection/UXIconsExtension.php

+12-15
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
use Symfony\Component\DependencyInjection\ContainerBuilder;
1919
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
2020
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
21-
use Symfony\Contracts\HttpClient\HttpClientInterface;
2221
use Symfony\UX\Icons\Iconify;
2322

2423
/**
@@ -87,7 +86,7 @@ public function getConfigTreeBuilder(): TreeBuilder
8786
->end()
8887
->arrayNode('iconify')
8988
->info('Configuration for the remote icon service.')
90-
->{interface_exists(HttpClientInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}()
89+
->canBeDisabled()
9190
->children()
9291
->booleanNode('on_demand')
9392
->info('Whether to download icons "on demand".')
@@ -164,26 +163,24 @@ protected function loadInternal(array $mergedConfig, ContainerBuilder $container
164163
->setArgument(1, $mergedConfig['ignore_not_found'])
165164
;
166165

167-
if ($mergedConfig['iconify']['enabled']) {
168-
$loader->load('iconify.php');
166+
$container->getDefinition('.ux_icons.iconify')
167+
->setArgument(1, $mergedConfig['iconify']['endpoint']);
169168

170-
$container->getDefinition('.ux_icons.iconify')
171-
->setArgument(1, $mergedConfig['iconify']['endpoint']);
169+
$container->getDefinition('.ux_icons.iconify_on_demand_registry')
170+
->setArgument(1, $iconSetAliases);
172171

173-
$container->getDefinition('.ux_icons.iconify_on_demand_registry')
174-
->setArgument(1, $iconSetAliases);
172+
$container->getDefinition('.ux_icons.command.lock')
173+
->setArgument(3, $mergedConfig['aliases'])
174+
->setArgument(4, $iconSetAliases);
175175

176-
$container->getDefinition('.ux_icons.command.lock')
177-
->setArgument(3, $mergedConfig['aliases'])
178-
->setArgument(4, $iconSetAliases);
179-
180-
if (!$mergedConfig['iconify']['on_demand']) {
181-
$container->removeDefinition('.ux_icons.iconify_on_demand_registry');
182-
}
176+
if (!$mergedConfig['iconify']['on_demand'] || !$mergedConfig['iconify']['enabled']) {
177+
$container->removeDefinition('.ux_icons.iconify_on_demand_registry');
183178
}
184179

185180
if (!$container->getParameter('kernel.debug')) {
186181
$container->removeDefinition('.ux_icons.command.import');
182+
$container->removeDefinition('.ux_icons.command.search');
183+
$container->removeDefinition('.ux_icons.command.lock');
187184
}
188185
}
189186
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Icons\Exception;
13+
14+
/**
15+
* @author Kevin Bond <[email protected]>
16+
*
17+
* @internal
18+
*/
19+
final class HttpClientNotInstalledException extends \LogicException
20+
{
21+
}

src/Icons/src/IconCacheWarmer.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function __construct(private CacheIconRegistry $registry, private IconFin
2828

2929
/**
3030
* @param callable(string,Icon):void|null $onSuccess
31-
* @param callable(string):void|null $onFailure
31+
* @param callable(string,\Exception):void|null $onFailure
3232
*/
3333
public function warm(?callable $onSuccess = null, ?callable $onFailure = null): void
3434
{
@@ -40,8 +40,8 @@ public function warm(?callable $onSuccess = null, ?callable $onFailure = null):
4040
$icon = $this->registry->get($name, refresh: true);
4141

4242
$onSuccess($name, $icon);
43-
} catch (IconNotFoundException) {
44-
$onFailure($name);
43+
} catch (IconNotFoundException $e) {
44+
$onFailure($name, $e);
4545
}
4646
}
4747
}

src/Icons/src/Iconify.php

+20-11
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\HttpClient\ScopingHttpClient;
1717
use Symfony\Contracts\Cache\CacheInterface;
1818
use Symfony\Contracts\HttpClient\HttpClientInterface;
19+
use Symfony\UX\Icons\Exception\HttpClientNotInstalledException;
1920
use Symfony\UX\Icons\Exception\IconNotFoundException;
2021

2122
/**
@@ -39,15 +40,10 @@ final class Iconify
3940

4041
public function __construct(
4142
private CacheInterface $cache,
42-
string $endpoint = self::API_ENDPOINT,
43-
?HttpClientInterface $http = null,
43+
private string $endpoint = self::API_ENDPOINT,
44+
private ?HttpClientInterface $httpClient = null,
4445
?int $maxIconsQueryLength = null,
4546
) {
46-
if (!class_exists(HttpClient::class)) {
47-
throw new \LogicException('You must install "symfony/http-client" to use Iconify. Try running "composer require symfony/http-client".');
48-
}
49-
50-
$this->http = ScopingHttpClient::forBaseUri($http ?? HttpClient::create(), $endpoint);
5147
$this->maxIconsQueryLength = min(self::MAX_ICONS_QUERY_LENGTH, $maxIconsQueryLength ?? self::MAX_ICONS_QUERY_LENGTH);
5248
}
5349

@@ -62,7 +58,7 @@ public function fetchIcon(string $prefix, string $name): Icon
6258
throw new IconNotFoundException(\sprintf('The icon "%s:%s" does not exist on iconify.design.', $prefix, $name));
6359
}
6460

65-
$response = $this->http->request('GET', \sprintf('/%s.json?icons=%s', $prefix, $name));
61+
$response = $this->http()->request('GET', \sprintf('/%s.json?icons=%s', $prefix, $name));
6662

6763
if (200 !== $response->getStatusCode()) {
6864
throw new IconNotFoundException(\sprintf('The icon "%s:%s" does not exist on iconify.design.', $prefix, $name));
@@ -112,7 +108,7 @@ public function fetchIcons(string $prefix, array $names): array
112108
throw new \InvalidArgumentException('The query string is too long.');
113109
}
114110

115-
$response = $this->http->request('GET', \sprintf('/%s.json', $prefix), [
111+
$response = $this->http()->request('GET', \sprintf('/%s.json', $prefix), [
116112
'headers' => [
117113
'Accept' => 'application/json',
118114
],
@@ -158,7 +154,7 @@ public function getIconSets(): array
158154

159155
public function searchIcons(string $prefix, string $query)
160156
{
161-
$response = $this->http->request('GET', '/search', [
157+
$response = $this->http()->request('GET', '/search', [
162158
'query' => [
163159
'query' => $query,
164160
'prefix' => $prefix,
@@ -205,9 +201,22 @@ public function chunk(string $prefix, array $names): iterable
205201
private function sets(): \ArrayObject
206202
{
207203
return $this->sets ??= $this->cache->get('iconify-sets', function () {
208-
$response = $this->http->request('GET', '/collections');
204+
$response = $this->http()->request('GET', '/collections');
209205

210206
return new \ArrayObject($response->toArray());
211207
});
212208
}
209+
210+
private function http(): HttpClientInterface
211+
{
212+
if (isset($this->http)) {
213+
return $this->http;
214+
}
215+
216+
if (!class_exists(HttpClient::class)) {
217+
throw new HttpClientNotInstalledException('You must install "symfony/http-client" to use icons from ux.symfony.com/icons. Try running "composer require symfony/http-client".');
218+
}
219+
220+
return $this->http = ScopingHttpClient::forBaseUri($this->httpClient ?? HttpClient::create(), $this->endpoint);
221+
}
213222
}

src/Icons/src/Registry/ChainIconRegistry.php

+8-2
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,16 @@ public function get(string $name): Icon
3434
foreach ($this->registries as $registry) {
3535
try {
3636
return $registry->get($name);
37-
} catch (IconNotFoundException) {
37+
} catch (IconNotFoundException $e) {
3838
}
3939
}
4040

41-
throw new IconNotFoundException(\sprintf('Icon "%s" not found.', $name));
41+
$message = \sprintf('Icon "%s" not found.', $name);
42+
43+
if (isset($e)) {
44+
$message .= " {$e->getMessage()}";
45+
}
46+
47+
throw new IconNotFoundException($message, previous: $e ?? null);
4248
}
4349
}

src/Icons/src/Registry/IconifyOnDemandRegistry.php

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

1212
namespace Symfony\UX\Icons\Registry;
1313

14+
use Symfony\UX\Icons\Exception\HttpClientNotInstalledException;
1415
use Symfony\UX\Icons\Exception\IconNotFoundException;
1516
use Symfony\UX\Icons\Icon;
1617
use Symfony\UX\Icons\Iconify;
@@ -36,6 +37,10 @@ public function get(string $name): Icon
3637
}
3738
[$prefix, $icon] = $parts;
3839

39-
return $this->iconify->fetchIcon($this->prefixAliases[$prefix] ?? $prefix, $icon);
40+
try {
41+
return $this->iconify->fetchIcon($this->prefixAliases[$prefix] ?? $prefix, $icon);
42+
} catch (HttpClientNotInstalledException $e) {
43+
throw new IconNotFoundException($e->getMessage());
44+
}
4045
}
4146
}

0 commit comments

Comments
 (0)