Skip to content
Snippets Groups Projects
Commit 9626058d authored by Alvaro Hurtado's avatar Alvaro Hurtado Committed by David Galeano
Browse files

#3450193: Boost the performance in the SVG generation

parent ece1d0f3
No related branches found
Tags 8.x-1.0
1 merge request!13450193 WIP
Pipeline #184949 passed
......@@ -4,4 +4,5 @@ services:
arguments: [
'@http_client',
'@logger.channel.default',
'@file_system'
]
......@@ -2,6 +2,7 @@
namespace Drupal\iconify_icons;
use Drupal\Core\File\FileSystemInterface;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\RequestException;
use Psr\Log\LoggerInterface;
......@@ -13,6 +14,10 @@ class IconifyService implements IconifyServiceInterface {
public const SEARCH_API_ENDPOINT = 'https://api.iconify.design/search';
public const COLLECTIONS_API_ENDPOINT = 'https://api.iconify.design/collections';
// Arguments [$collection, $icon_name].
public const DESIGN_DOWNLOAD_API_ENDPOINT = 'https://api.iconify.design/%s/%s.svg';
/**
* The HTTP client.
*
......@@ -27,6 +32,13 @@ class IconifyService implements IconifyServiceInterface {
*/
protected $logger;
/**
* The fileSystem service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* IconifyService constructor.
*
......@@ -34,10 +46,13 @@ class IconifyService implements IconifyServiceInterface {
* The HTTP client.
* @param \Psr\Log\LoggerInterface $logger
* The Logger service.
* @param \Drupal\Core\File\FileSystemInterface $fileSystem
* The FileSystem service.
*/
public function __construct(ClientInterface $http_client, LoggerInterface $logger) {
public function __construct(ClientInterface $http_client, LoggerInterface $logger, FileSystemInterface $fileSystem) {
$this->httpClient = $http_client;
$this->logger = $logger;
$this->fileSystem = $fileSystem;
}
/**
......@@ -106,23 +121,98 @@ class IconifyService implements IconifyServiceInterface {
}
}
/**
* Calculates a file system path to cache an icon based on its parameters.
*
* @param string $collection
* The collection.
* @param string $icon_name
* The icon name.
* @param array $query_options
* Query options.
*
* @return string
* The Path in drupal file system.
*/
protected function iconSettingsToPath(string $collection, string $icon_name, array $query_options) {
return 'public://iconify-icons/' . implode('/', [
$collection,
$icon_name,
$query_options['width'],
$query_options['height'],
$query_options['download'],
$query_options['box'],
$query_options['color'],
$query_options['flip'],
$query_options['rotate'],
]);
}
/**
* Gets the icon from cache and return empty string if it doesn't exist.
*
* @param string $collection
* The collection.
* @param string $icon_name
* The icon name.
* @param array $query_options
* Query options.
*
* @return string
* The svg or '' if it doesn't exist yet.
*/
protected function getIconFromCache(string $collection, string $icon_name, array $query_options): string {
// Translate settings to path.
$path = $this->iconSettingsToPath($collection, $icon_name, $query_options);
if ($this->fileSystem->realpath($path . '/' . $icon_name . '.svg')) {
return file_get_contents($path . '/' . $icon_name . '.svg');
}
return '';
}
/**
* Qualifies the default parameters and sets if any are missing.
*
* @param array $parameters
* The parameters to qualify.
*
* @return array
* The qualified parameters.
*/
protected function setDefaultParameters(array $parameters): array {
return [
'width' => $parameters['width'] ?? 25,
'height' => $parameters['height'] ?? 25,
'color' => $parameters['color'] ?? '#000',
'flip' => $parameters['flip'] ?? '',
'rotate' => $parameters['rotate'] ?? '',
'download' => $parameters['download'] ?? FALSE,
'box' => $parameters['box'] ?? FALSE,
];
}
/**
* {@inheritdoc}
*/
public function generateSvg(string $collection, string $icon_name, array $parameters = []): string {
$endpoint = "https://api.iconify.design/{$collection}/{$icon_name}.svg";
$parameters = $this->setDefaultParameters($parameters);
if ($icon = $this->getIconFromCache($collection, $icon_name, $parameters)) {
return $icon;
}
try {
$response = $this->httpClient->request('GET', $endpoint, [
'query' => [
'width' => !empty($parameters) ? $parameters['width'] : 25,
'height' => !empty($parameters) ? $parameters['height'] : 25,
'color' => !empty($parameters) ? $parameters['color'] : '#000',
'flip' => !empty($parameters) ? $parameters['flip'] : '',
'rotate' => !empty($parameters) ? $parameters['rotate'] : '',
],
$response = $this->httpClient->request('GET', sprintf($this::DESIGN_DOWNLOAD_API_ENDPOINT, $collection, $icon_name), [
'query' => $parameters,
]);
return $response->getBody();
$directory = $this->iconSettingsToPath($collection, $icon_name, $parameters);
$icon = $response->getBody()->getContents();
if ($this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)) {
$filepath = $directory . '/' . $icon_name . '.svg';
$this->fileSystem->saveData($icon, $filepath, FileSystemInterface::EXISTS_REPLACE);
}
return $icon;
}
catch (RequestException $e) {
// Handle request exception (e.g., log error, return empty array)
......
......@@ -2,10 +2,13 @@
namespace Drupal\Tests\iconify_icons\Unit;
use Drupal\Core\File\FileSystemInterface;
use Drupal\iconify_icons\IconifyService;
use Drupal\Tests\UnitTestCase;
use GuzzleHttp\ClientInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Log\LoggerInterface;
/**
* Test description.
......@@ -35,21 +38,28 @@ class IconifyServiceTest extends UnitTestCase {
*/
protected $iconifyService;
/**
* The fileSystem service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->httpClient = $this->createMock('GuzzleHttp\ClientInterface');
$this->logger = $this->createMock('Psr\Log\LoggerInterface');
$this->iconifyService = new IconifyService($this->httpClient, $this->logger);
$this->httpClient = $this->createMock(ClientInterface::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->fileSystem = $this->createMock(FileSystemInterface::class);
$this->iconifyService = new IconifyService($this->httpClient, $this->logger, $this->fileSystem);
}
/**
* Tests getIcons method.
*/
public function testGetIcons() {
$query = 'query_example';
$collection = 'collection_test';
$limit = 10;
......@@ -111,4 +121,31 @@ class IconifyServiceTest extends UnitTestCase {
$this->assertEquals($ok_response, $assert);
}
/**
* Tests generateSvg method.
*/
public function testGenerateSvg() {
$ok_response = 'SVG content';
$collection = 'collection_1';
$icon_name = 'icon_1';
$stream = $this->createMock(StreamInterface::class);
$stream
->method('getContents')
->willReturn($ok_response);
$response = $this->createMock(ResponseInterface::class);
$response
->method('getBody')
->willReturn($stream);
$this->httpClient
->method('request')
->with('GET', sprintf($this->iconifyService::DESIGN_DOWNLOAD_API_ENDPOINT, $collection, $icon_name))
->willReturn($response);
$assert = $this->iconifyService->generateSvg($collection, $icon_name);
$this->assertEquals($ok_response, $assert);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment