Skip to content

Commit 11fd8ce

Browse files
authored
Merge pull request #1966 from magento-performance/MAGETWO-84480
[Performance] MAGETWO-84480: Add cache for getimagesize() function for product images
2 parents f28dbc0 + 59bd8fc commit 11fd8ce

File tree

2 files changed

+143
-90
lines changed

2 files changed

+143
-90
lines changed

app/code/Magento/Catalog/Model/Product/Image.php

Lines changed: 85 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Magento\Framework\App\Filesystem\DirectoryList;
1010
use Magento\Framework\App\ObjectManager;
1111
use Magento\Framework\Image as MagentoImage;
12+
use Magento\Framework\Serialize\SerializerInterface;
1213

1314
/**
1415
* @method string getFile()
@@ -172,6 +173,16 @@ class Image extends \Magento\Framework\Model\AbstractModel
172173
*/
173174
private $imageAsset;
174175

176+
/**
177+
* @var string
178+
*/
179+
private $cachePrefix = 'IMG_INFO';
180+
181+
/**
182+
* @var SerializerInterface
183+
*/
184+
private $serializer;
185+
175186
/**
176187
* Constructor
177188
*
@@ -190,6 +201,7 @@ class Image extends \Magento\Framework\Model\AbstractModel
190201
* @param array $data
191202
* @param \Magento\Catalog\Model\View\Asset\ImageFactory|null $viewAssetImageFactory
192203
* @param \Magento\Catalog\Model\View\Asset\PlaceholderFactory|null $viewAssetPlaceholderFactory
204+
* @param SerializerInterface|null $serializer
193205
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
194206
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
195207
*/
@@ -208,7 +220,8 @@ public function __construct(
208220
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
209221
array $data = [],
210222
\Magento\Catalog\Model\View\Asset\ImageFactory $viewAssetImageFactory = null,
211-
\Magento\Catalog\Model\View\Asset\PlaceholderFactory $viewAssetPlaceholderFactory = null
223+
\Magento\Catalog\Model\View\Asset\PlaceholderFactory $viewAssetPlaceholderFactory = null,
224+
SerializerInterface $serializer = null
212225
) {
213226
$this->_storeManager = $storeManager;
214227
$this->_catalogProductMediaConfig = $catalogProductMediaConfig;
@@ -223,6 +236,7 @@ public function __construct(
223236
->get(\Magento\Catalog\Model\View\Asset\ImageFactory::class);
224237
$this->viewAssetPlaceholderFactory = $viewAssetPlaceholderFactory ?: ObjectManager::getInstance()
225238
->get(\Magento\Catalog\Model\View\Asset\PlaceholderFactory::class);
239+
$this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class);
226240
}
227241

228242
/**
@@ -356,86 +370,6 @@ public function setSize($size)
356370
return $this;
357371
}
358372

359-
/**
360-
* @param string|null $file
361-
* @return bool
362-
*/
363-
protected function _checkMemory($file = null)
364-
{
365-
return $this->_getMemoryLimit() > $this->_getMemoryUsage() + $this->_getNeedMemoryForFile(
366-
$file
367-
)
368-
|| $this->_getMemoryLimit() == -1;
369-
}
370-
371-
/**
372-
* @return string
373-
*/
374-
protected function _getMemoryLimit()
375-
{
376-
$memoryLimit = trim(strtoupper(ini_get('memory_limit')));
377-
378-
if (!isset($memoryLimit[0])) {
379-
$memoryLimit = "128M";
380-
}
381-
382-
if (substr($memoryLimit, -1) == 'K') {
383-
return substr($memoryLimit, 0, -1) * 1024;
384-
}
385-
if (substr($memoryLimit, -1) == 'M') {
386-
return substr($memoryLimit, 0, -1) * 1024 * 1024;
387-
}
388-
if (substr($memoryLimit, -1) == 'G') {
389-
return substr($memoryLimit, 0, -1) * 1024 * 1024 * 1024;
390-
}
391-
return $memoryLimit;
392-
}
393-
394-
/**
395-
* @return int
396-
*/
397-
protected function _getMemoryUsage()
398-
{
399-
if (function_exists('memory_get_usage')) {
400-
return memory_get_usage();
401-
}
402-
return 0;
403-
}
404-
405-
/**
406-
* @param string|null $file
407-
* @return float|int
408-
* @SuppressWarnings(PHPMD.NPathComplexity)
409-
*/
410-
protected function _getNeedMemoryForFile($file = null)
411-
{
412-
$file = $file === null ? $this->getBaseFile() : $file;
413-
if (!$file) {
414-
return 0;
415-
}
416-
417-
if (!$this->_mediaDirectory->isExist($file)) {
418-
return 0;
419-
}
420-
421-
$imageInfo = getimagesize($this->_mediaDirectory->getAbsolutePath($file));
422-
423-
if (!isset($imageInfo[0]) || !isset($imageInfo[1])) {
424-
return 0;
425-
}
426-
if (!isset($imageInfo['channels'])) {
427-
// if there is no info about this parameter lets set it for maximum
428-
$imageInfo['channels'] = 4;
429-
}
430-
if (!isset($imageInfo['bits'])) {
431-
// if there is no info about this parameter lets set it for maximum
432-
$imageInfo['bits'] = 8;
433-
}
434-
return round(
435-
($imageInfo[0] * $imageInfo[1] * $imageInfo['bits'] * $imageInfo['channels'] / 8 + Pow(2, 16)) * 1.65
436-
);
437-
}
438-
439373
/**
440374
* Convert array of 3 items (decimal r, g, b) to string of their hex values
441375
*
@@ -472,9 +406,7 @@ public function setBaseFile($file)
472406
'filePath' => $file,
473407
]
474408
);
475-
if ($file == 'no_selection' || !$this->_fileExists($this->imageAsset->getSourceFile())
476-
|| !$this->_checkMemory($this->imageAsset->getSourceFile())
477-
) {
409+
if ($file == 'no_selection' || !$this->_fileExists($this->imageAsset->getSourceFile())) {
478410
$this->_isBaseFilePlaceholder = true;
479411
$this->imageAsset = $this->viewAssetPlaceholderFactory->create(
480412
[
@@ -682,11 +614,14 @@ public function getDestinationSubdir()
682614
}
683615

684616
/**
685-
* @return bool|void
617+
* @return bool
686618
*/
687619
public function isCached()
688620
{
689-
return file_exists($this->imageAsset->getPath());
621+
return (
622+
is_array($this->loadImageInfoFromCache($this->imageAsset->getPath())) ||
623+
file_exists($this->imageAsset->getPath())
624+
);
690625
}
691626

692627
/**
@@ -856,6 +791,7 @@ public function clearCache()
856791
$this->_mediaDirectory->delete($directory);
857792

858793
$this->_coreFileStorageDatabase->deleteFolder($this->_mediaDirectory->getAbsolutePath($directory));
794+
$this->clearImageInfoFromCache();
859795
}
860796

861797
/**
@@ -890,7 +826,7 @@ public function getResizedImageInfo()
890826
$image = $this->imageAsset->getPath();
891827
}
892828

893-
$imageProperties = getimagesize($image);
829+
$imageProperties = $this->getimagesize($image);
894830

895831
return $imageProperties;
896832
} finally {
@@ -932,4 +868,66 @@ private function getMiscParams()
932868

933869
return $miscParams;
934870
}
871+
872+
/**
873+
* Get image size
874+
*
875+
* @param string $imagePath
876+
* @return array
877+
*/
878+
private function getImageSize($imagePath)
879+
{
880+
$imageInfo = $this->loadImageInfoFromCache($imagePath);
881+
if (!isset($imageInfo['size'])) {
882+
$imageSize = getimagesize($imagePath);
883+
$this->saveImageInfoToCache(['size' => $imageSize], $imagePath);
884+
return $imageSize;
885+
} else {
886+
return $imageInfo['size'];
887+
}
888+
}
889+
890+
/**
891+
* Save image data to cache
892+
*
893+
* @param array $imageInfo
894+
* @param string $imagePath
895+
* @return void
896+
*/
897+
private function saveImageInfoToCache(array $imageInfo, string $imagePath)
898+
{
899+
$imagePath = $this->cachePrefix . $imagePath;
900+
$this->_cacheManager->save(
901+
$this->serializer->serialize($imageInfo),
902+
$imagePath,
903+
[$this->cachePrefix]
904+
);
905+
}
906+
907+
/**
908+
* Load image data from cache
909+
*
910+
* @param string $imagePath
911+
* @return array|false
912+
*/
913+
private function loadImageInfoFromCache(string $imagePath)
914+
{
915+
$imagePath = $this->cachePrefix . $imagePath;
916+
$cacheData = $this->_cacheManager->load($imagePath);
917+
if (!$cacheData) {
918+
return false;
919+
} else {
920+
return $this->serializer->unserialize($cacheData);
921+
}
922+
}
923+
924+
/**
925+
* Clear image data from cache
926+
*
927+
* @return void
928+
*/
929+
private function clearImageInfoFromCache()
930+
{
931+
$this->_cacheManager->clean([$this->cachePrefix]);
932+
}
935933
}

app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@
55
*/
66
namespace Magento\Catalog\Test\Unit\Model\Product;
77

8-
use Magento\Catalog\Model\View\Asset\Image\ContextFactory;
98
use Magento\Catalog\Model\View\Asset\ImageFactory;
109
use Magento\Catalog\Model\View\Asset\PlaceholderFactory;
1110
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
1211
use Magento\Framework\App\Filesystem\DirectoryList;
13-
use Magento\Framework\View\Asset\ContextInterface;
1412

1513
/**
1614
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -73,10 +71,24 @@ class ImageTest extends \PHPUnit\Framework\TestCase
7371
*/
7472
private $viewAssetPlaceholderFactory;
7573

74+
/**
75+
* @var \Magento\Framework\Serialize\SerializerInterface|\PHPUnit_Framework_MockObject_MockObject
76+
*/
77+
private $serializer;
78+
79+
/**
80+
* @var \Magento\Framework\App\CacheInterface|\PHPUnit_Framework_MockObject_MockObject
81+
*/
82+
private $cacheManager;
83+
7684
protected function setUp()
7785
{
7886
$objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
7987
$this->context = $this->createMock(\Magento\Framework\Model\Context::class);
88+
$this->cacheManager = $this->getMockBuilder(\Magento\Framework\App\CacheInterface::class)
89+
->disableOriginalConstructor()
90+
->getMockForAbstractClass();
91+
$this->context->expects($this->any())->method('getCacheManager')->will($this->returnValue($this->cacheManager));
8092

8193
$this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManager::class)
8294
->disableOriginalConstructor()
@@ -112,17 +124,36 @@ protected function setUp()
112124
->disableOriginalConstructor()
113125
->setMethods(['create'])
114126
->getMock();
127+
$this->serializer = $this->getMockBuilder(
128+
\Magento\Framework\Serialize\SerializerInterface::class
129+
)->getMockForAbstractClass();
130+
$this->serializer->expects($this->any())
131+
->method('serialize')
132+
->willReturnCallback(
133+
function ($value) {
134+
return json_encode($value);
135+
}
136+
);
137+
$this->serializer->expects($this->any())
138+
->method('unserialize')
139+
->willReturnCallback(
140+
function ($value) {
141+
return json_decode($value, true);
142+
}
143+
);
115144

116145
$this->image = $objectManager->getObject(
117146
\Magento\Catalog\Model\Product\Image::class,
118147
[
148+
'context' => $this->context,
119149
'storeManager' => $this->storeManager,
120150
'catalogProductMediaConfig' => $this->config,
121151
'coreFileStorageDatabase' => $this->coreFileHelper,
122152
'filesystem' => $this->filesystem,
123153
'imageFactory' => $this->factory,
124154
'viewAssetImageFactory' => $this->viewAssetImageFactory,
125-
'viewAssetPlaceholderFactory' => $this->viewAssetPlaceholderFactory
155+
'viewAssetPlaceholderFactory' => $this->viewAssetPlaceholderFactory,
156+
'serializer' => $this->serializer
126157
]
127158
);
128159

@@ -354,12 +385,16 @@ public function testIsCached()
354385
$this->testSetGetBaseFile();
355386
$absolutePath = dirname(dirname(__DIR__)) . '/_files/catalog/product/watermark/somefile.png';
356387
$this->imageAsset->expects($this->any())->method('getPath')->willReturn($absolutePath);
388+
$this->cacheManager->expects($this->once())->method('load')->willReturn(
389+
json_encode(['size' => ['image data']])
390+
);
357391
$this->assertTrue($this->image->isCached());
358392
}
359393

360394
public function testClearCache()
361395
{
362396
$this->coreFileHelper->expects($this->once())->method('deleteFolder')->will($this->returnValue(true));
397+
$this->cacheManager->expects($this->once())->method('clean');
363398
$this->image->clearCache();
364399
}
365400

@@ -383,4 +418,24 @@ public function testIsBaseFilePlaceholder()
383418
{
384419
$this->assertFalse($this->image->isBaseFilePlaceholder());
385420
}
421+
422+
public function testGetResizedImageInfoWithCache()
423+
{
424+
$absolutePath = dirname(dirname(__DIR__)) . '/_files/catalog/product/watermark/somefile.png';
425+
$this->imageAsset->expects($this->any())->method('getPath')->willReturn($absolutePath);
426+
$this->cacheManager->expects($this->once())->method('load')->willReturn(
427+
json_encode(['size' => ['image data']])
428+
);
429+
$this->cacheManager->expects($this->never())->method('save');
430+
$this->assertEquals(['image data'], $this->image->getResizedImageInfo());
431+
}
432+
433+
public function testGetResizedImageInfoEmptyCache()
434+
{
435+
$absolutePath = dirname(dirname(__DIR__)) . '/_files/catalog/product/watermark/somefile.png';
436+
$this->imageAsset->expects($this->any())->method('getPath')->willReturn($absolutePath);
437+
$this->cacheManager->expects($this->once())->method('load')->willReturn(false);
438+
$this->cacheManager->expects($this->once())->method('save');
439+
$this->assertTrue(is_array($this->image->getResizedImageInfo()));
440+
}
386441
}

0 commit comments

Comments
 (0)