diff --git a/src/Icons/src/Iconify.php b/src/Icons/src/Iconify.php
index 757dacb245c..e29f2c69f5c 100644
--- a/src/Icons/src/Iconify.php
+++ b/src/Icons/src/Iconify.php
@@ -85,6 +85,54 @@ public function fetchIcon(string $prefix, string $name): Icon
]);
}
+ public function fetchIcons(string $prefix, array $names): array
+ {
+ if (!isset($this->sets()[$prefix])) {
+ throw new IconNotFoundException(\sprintf('The icon set "%s" does not exist on iconify.design.', $prefix));
+ }
+
+ // Sort to enforce cache hits
+ sort($names);
+ $queryString = implode(',', $names);
+ if (!preg_match('#^[a-z0-9-,]+$#', $queryString)) {
+ throw new \InvalidArgumentException('Invalid icon names.');
+ }
+
+ // URL must be 500 chars max (iconify limit)
+ // -39 chars: https://api.iconify.design/XXX.json?icons=
+ // -safe margin
+ if (450 < \strlen($prefix.$queryString)) {
+ throw new \InvalidArgumentException('The query string is too long.');
+ }
+
+ $response = $this->http->request('GET', \sprintf('/%s.json', $prefix), [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ 'query' => [
+ 'icons' => strtolower($queryString),
+ ],
+ ]);
+
+ if (200 !== $response->getStatusCode()) {
+ throw new IconNotFoundException(\sprintf('The icon set "%s" does not exist on iconify.design.', $prefix));
+ }
+
+ $data = $response->toArray();
+
+ $icons = [];
+ foreach ($data['icons'] as $iconName => $iconData) {
+ $height = $iconData['height'] ?? $data['height'] ??= $this->sets()[$prefix]['height'] ?? null;
+ $width = $iconData['width'] ?? $data['width'] ??= $this->sets()[$prefix]['width'] ?? null;
+
+ $icons[$iconName] = new Icon($iconData['body'], [
+ 'viewBox' => \sprintf('0 0 %d %d', $width ?? $height, $height ?? $width),
+ ]);
+ }
+
+ return $icons;
+ }
+
public function hasIconSet(string $prefix): bool
{
return isset($this->sets()[$prefix]);
diff --git a/src/Icons/tests/Unit/IconifyTest.php b/src/Icons/tests/Unit/IconifyTest.php
index b940683660e..3fafce3abcc 100644
--- a/src/Icons/tests/Unit/IconifyTest.php
+++ b/src/Icons/tests/Unit/IconifyTest.php
@@ -16,6 +16,7 @@
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\JsonMockResponse;
use Symfony\UX\Icons\Exception\IconNotFoundException;
+use Symfony\UX\Icons\Icon;
use Symfony\UX\Icons\Iconify;
/**
@@ -162,6 +163,71 @@ public function testFetchIconThrowsWhenStatusCodeNot200(): void
$iconify->fetchIcon('bi', 'heart');
}
+ public function testFetchIcons(): void
+ {
+ $iconify = new Iconify(
+ cache: new NullAdapter(),
+ endpoint: 'https://example.com',
+ http: new MockHttpClient([
+ new JsonMockResponse([
+ 'bi' => [],
+ ]),
+ new JsonMockResponse([
+ 'icons' => [
+ 'heart' => [
+ 'body' => '',
+ 'height' => 17,
+ ],
+ 'bar' => [
+ 'body' => '',
+ 'height' => 17,
+ ],
+ ],
+ ]),
+ ]),
+ );
+
+ $icons = $iconify->fetchIcons('bi', ['heart', 'bar']);
+
+ $this->assertCount(2, $icons);
+ $this->assertSame(['heart', 'bar'], array_keys($icons));
+ $this->assertContainsOnlyInstancesOf(Icon::class, $icons);
+ }
+
+ public function testFetchIconsThrowsWithInvalidIconNames(): void
+ {
+ $iconify = new Iconify(
+ cache: new NullAdapter(),
+ endpoint: 'https://example.com',
+ http: new MockHttpClient([
+ new JsonMockResponse([
+ 'bi' => [],
+ ]),
+ ]),
+ );
+
+ $this->expectException(\InvalidArgumentException::class);
+
+ $iconify->fetchIcons('bi', ['à', 'foo']);
+ }
+
+ public function testFetchIconsThrowsWithTooManyIcons(): void
+ {
+ $iconify = new Iconify(
+ cache: new NullAdapter(),
+ endpoint: 'https://example.com',
+ http: new MockHttpClient([
+ new JsonMockResponse([
+ 'bi' => [],
+ ]),
+ ]),
+ );
+
+ $this->expectException(\InvalidArgumentException::class);
+
+ $iconify->fetchIcons('bi', array_fill(0, 50, '1234567890'));
+ }
+
public function testGetMetadata(): void
{
$responseFile = __DIR__.'/../Fixtures/Iconify/collections.json';