Skip to content

Commit df5978f

Browse files
Kocalsmnandre
authored andcommitted
[LazyImage] Cache BlurHash, close #2
1 parent e923134 commit df5978f

File tree

7 files changed

+189
-5
lines changed

7 files changed

+189
-5
lines changed

src/LazyImage/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"require-dev": {
3737
"intervention/image": "^2.5",
3838
"kornrunner/blurhash": "^1.1",
39+
"symfony/cache-contracts": "^2.2",
3940
"symfony/framework-bundle": "^5.4|^6.0|^7.0",
4041
"symfony/phpunit-bridge": "^5.2|^6.0|^7.0",
4142
"symfony/twig-bundle": "^5.4|^6.0|^7.0",

src/LazyImage/doc/index.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,28 @@ The ``data_uri_thumbnail`` function receives 3 arguments:
103103
- the width of the BlurHash to generate
104104
- the height of the BlurHash to generate
105105

106+
Performance considerations
107+
~~~~~~~~~~~~~~~~~~~~~~~~~~
108+
106109
You should try to generate small BlurHash images as generating the image
107110
can be CPU-intensive. Instead, you can rely on the browser scaling
108111
abilities by generating a small image and using the ``width`` and
109112
``height`` HTML attributes to scale up the image.
110113

114+
You can also configure a cache pool to store the generated BlurHash,
115+
this way you can avoid generating the same BlurHash multiple times:
116+
117+
.. code-block:: yaml
118+
119+
# config/packages/lazy_image.yaml
120+
framework:
121+
cache:
122+
pools:
123+
cache.lazy_image: cache.adapter.redis # or any other cache adapter depending on your needs
124+
125+
lazy_image:
126+
cache: cache.lazy_image # the cache pool to use
127+
111128
Extend the default behavior
112129
~~~~~~~~~~~~~~~~~~~~~~~~~~~
113130

src/LazyImage/src/BlurHash/BlurHash.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,9 @@
2121
*/
2222
class BlurHash implements BlurHashInterface
2323
{
24-
private $imageManager;
25-
26-
public function __construct(?ImageManager $imageManager = null)
27-
{
28-
$this->imageManager = $imageManager;
24+
public function __construct(
25+
private ?ImageManager $imageManager = null,
26+
) {
2927
}
3028

3129
public function createDataUriThumbnail(string $filename, int $width, int $height, int $encodingWidth = 75, int $encodingHeight = 75): string
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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\LazyImage\BlurHash;
13+
14+
use Symfony\Contracts\Cache\CacheInterface;
15+
16+
/**
17+
* Decorate a BlurHashInterface to cache the result of the encoding, for performance purposes.
18+
*
19+
* @author Hugo Alliaume <[email protected]>
20+
*
21+
* @final
22+
*/
23+
class CachedBlurHash implements BlurHashInterface
24+
{
25+
public function __construct(
26+
private BlurHashInterface $blurHash,
27+
private CacheInterface $cache,
28+
) {
29+
}
30+
31+
public function createDataUriThumbnail(string $filename, int $width, int $height, int $encodingWidth = 75, int $encodingHeight = 75): string
32+
{
33+
return $this->blurHash->createDataUriThumbnail($filename, $width, $height, $encodingWidth, $encodingHeight);
34+
}
35+
36+
public function encode(string $filename, int $encodingWidth = 75, int $encodingHeight = 75): string
37+
{
38+
return $this->cache->get(
39+
'blurhash.'.hash('xxh3', $filename.$encodingWidth.$encodingHeight),
40+
fn () => $this->blurHash->encode($filename, $encodingWidth, $encodingHeight)
41+
);
42+
}
43+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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\LazyImage\DependencyInjection;
13+
14+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
15+
use Symfony\Component\Config\Definition\ConfigurationInterface;
16+
17+
/**
18+
* @author Hugo Alliaume <[email protected]>
19+
*
20+
* @internal
21+
*/
22+
final class Configuration implements ConfigurationInterface
23+
{
24+
public function getConfigTreeBuilder(): TreeBuilder
25+
{
26+
$treeBuilder = new TreeBuilder('ux_lazy_image');
27+
$rootNode = $treeBuilder->getRootNode();
28+
$rootNode
29+
->children()
30+
->scalarNode('cache')->end()
31+
->end()
32+
;
33+
34+
return $treeBuilder;
35+
}
36+
}

src/LazyImage/src/DependencyInjection/LazyImageExtension.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
2222
use Symfony\UX\LazyImage\BlurHash\BlurHash;
2323
use Symfony\UX\LazyImage\BlurHash\BlurHashInterface;
24+
use Symfony\UX\LazyImage\BlurHash\CachedBlurHash;
2425
use Symfony\UX\LazyImage\Twig\BlurHashExtension;
2526

2627
/**
@@ -32,6 +33,9 @@ class LazyImageExtension extends Extension implements PrependExtensionInterface
3233
{
3334
public function load(array $configs, ContainerBuilder $container)
3435
{
36+
$configuration = new Configuration();
37+
$config = $this->processConfiguration($configuration, $configs);
38+
3539
if (class_exists(ImageManager::class)) {
3640
$container
3741
->setDefinition('lazy_image.image_manager', new Definition(ImageManager::class))
@@ -47,6 +51,17 @@ public function load(array $configs, ContainerBuilder $container)
4751

4852
$container->setAlias(BlurHashInterface::class, 'lazy_image.blur_hash')->setPublic(false);
4953

54+
if (isset($config['cache'])) {
55+
$container
56+
->setDefinition('lazy_image.cached_blur_hash', new Definition(CachedBlurHash::class))
57+
->setDecoratedService('lazy_image.blur_hash')
58+
->addArgument(new Reference('lazy_image.cached_blur_hash.inner'))
59+
->addArgument(new Reference($config['cache']))
60+
;
61+
62+
$container->setAlias(BlurHashInterface::class, 'lazy_image.blur_hash')->setPublic(false);
63+
}
64+
5065
$container
5166
->setDefinition('twig.extension.blur_hash', new Definition(BlurHashExtension::class))
5267
->addArgument(new Reference('lazy_image.blur_hash'))

src/LazyImage/tests/BlurHash/BlurHashTest.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
namespace Symfony\UX\LazyImage\Tests;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
16+
use Symfony\Component\Config\Loader\LoaderInterface;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
1518
use Symfony\UX\LazyImage\BlurHash\BlurHashInterface;
19+
use Symfony\UX\LazyImage\BlurHash\CachedBlurHash;
1620
use Symfony\UX\LazyImage\Tests\Kernel\TwigAppKernel;
1721

1822
/**
@@ -37,6 +41,76 @@ public function testEncode()
3741
);
3842
}
3943

44+
public function testEnsureCacheIsNotUsedWhenNotConfigured()
45+
{
46+
$kernel = new TwigAppKernel('test', true);
47+
$kernel->boot();
48+
$container = $kernel->getContainer()->get('test.service_container');
49+
50+
/** @var BlurHashInterface $blurHash */
51+
$blurHash = $container->get('test.lazy_image.blur_hash');
52+
53+
static::assertNotInstanceOf(CachedBlurHash::class, $blurHash);
54+
}
55+
56+
public function testEnsureCacheIsUsedWhenConfigured()
57+
{
58+
$kernel = new class('test', true) extends TwigAppKernel {
59+
public function registerContainerConfiguration(LoaderInterface $loader)
60+
{
61+
parent::registerContainerConfiguration($loader);
62+
63+
$loader->load(static function (ContainerBuilder $container) {
64+
$container->loadFromExtension('framework', [
65+
'cache' => [
66+
'pools' => [
67+
'cache.lazy_image' => [
68+
'adapter' => 'cache.adapter.array',
69+
],
70+
],
71+
],
72+
]);
73+
74+
$container->loadFromExtension('lazy_image', [
75+
'cache' => 'cache.lazy_image',
76+
]);
77+
78+
$container->setAlias('test.cache.lazy_image', 'cache.lazy_image')->setPublic(true);
79+
});
80+
}
81+
};
82+
83+
$kernel->boot();
84+
$container = $kernel->getContainer()->get('test.service_container');
85+
86+
/** @var BlurHashInterface $blurHash */
87+
$blurHash = $container->get('test.lazy_image.blur_hash');
88+
89+
static::assertInstanceOf(CachedBlurHash::class, $blurHash);
90+
}
91+
92+
public function testEncodeShouldBeCalledOnceWhenCached()
93+
{
94+
$blurHash = $this->createMock(BlurHashInterface::class);
95+
$blurHash->expects($this->once())->method('encode')->with(__DIR__.'/../Fixtures/logo.png')->willReturn('L54ec*~q_3?bofoffQWB9F9FD%IU');
96+
$cache = new ArrayAdapter();
97+
$cachedBlurHash = new CachedBlurHash($blurHash, $cache);
98+
99+
$this->assertEmpty($cache->getValues());
100+
101+
$this->assertSame(
102+
'L54ec*~q_3?bofoffQWB9F9FD%IU',
103+
$cachedBlurHash->encode(__DIR__.'/../Fixtures/logo.png')
104+
);
105+
106+
$this->assertNotEmpty($cache->getValues());
107+
108+
$this->assertSame(
109+
'L54ec*~q_3?bofoffQWB9F9FD%IU',
110+
$cachedBlurHash->encode(__DIR__.'/../Fixtures/logo.png')
111+
);
112+
}
113+
40114
public function testCreateDataUriThumbnail()
41115
{
42116
$kernel = new TwigAppKernel('test', true);

0 commit comments

Comments
 (0)