Skip to content

Commit b0f9505

Browse files
authored
Merge pull request #5084 from magento-tango/MC-22998
[tango] MAGETWO-99311: Asynchronous image resizing
2 parents b91e97f + aeac4b5 commit b0f9505

File tree

10 files changed

+379
-19
lines changed

10 files changed

+379
-19
lines changed

app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php

Lines changed: 125 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,37 @@
88
namespace Magento\MediaStorage\Console\Command;
99

1010
use Magento\Framework\App\Area;
11-
use Magento\Framework\App\ObjectManager;
1211
use Magento\Framework\App\State;
13-
use Magento\Framework\ObjectManagerInterface;
12+
use Magento\Framework\Console\Cli;
1413
use Magento\MediaStorage\Service\ImageResize;
14+
use Magento\MediaStorage\Service\ImageResizeScheduler;
1515
use Symfony\Component\Console\Helper\ProgressBar;
1616
use Symfony\Component\Console\Helper\ProgressBarFactory;
1717
use Symfony\Component\Console\Input\InputInterface;
1818
use Symfony\Component\Console\Output\OutputInterface;
19+
use Symfony\Component\Console\Input\InputOption;
20+
use Symfony\Component\Console\Command\Command;
21+
use Magento\Catalog\Model\ResourceModel\Product\Image as ProductImage;
1922

2023
/**
2124
* Resizes product images according to theme view definitions.
22-
*
23-
* @package Magento\MediaStorage\Console\Command
2425
*/
25-
class ImagesResizeCommand extends \Symfony\Component\Console\Command\Command
26+
class ImagesResizeCommand extends Command
2627
{
28+
/**
29+
* Asynchronous image resize mode
30+
*/
31+
const ASYNC_RESIZE = 'async';
32+
33+
/**
34+
* @var ImageResizeScheduler
35+
*/
36+
private $imageResizeScheduler;
37+
2738
/**
2839
* @var ImageResize
2940
*/
30-
private $resize;
41+
private $imageResize;
3142

3243
/**
3344
* @var State
@@ -39,24 +50,32 @@ class ImagesResizeCommand extends \Symfony\Component\Console\Command\Command
3950
*/
4051
private $progressBarFactory;
4152

53+
/**
54+
* @var ProductImage
55+
*/
56+
private $productImage;
57+
4258
/**
4359
* @param State $appState
44-
* @param ImageResize $resize
45-
* @param ObjectManagerInterface $objectManager
60+
* @param ImageResize $imageResize
61+
* @param ImageResizeScheduler $imageResizeScheduler
4662
* @param ProgressBarFactory $progressBarFactory
63+
* @param ProductImage $productImage
4764
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
4865
*/
4966
public function __construct(
5067
State $appState,
51-
ImageResize $resize,
52-
ObjectManagerInterface $objectManager,
53-
ProgressBarFactory $progressBarFactory = null
68+
ImageResize $imageResize,
69+
ImageResizeScheduler $imageResizeScheduler,
70+
ProgressBarFactory $progressBarFactory,
71+
ProductImage $productImage
5472
) {
5573
parent::__construct();
56-
$this->resize = $resize;
5774
$this->appState = $appState;
58-
$this->progressBarFactory = $progressBarFactory
59-
?: ObjectManager::getInstance()->get(ProgressBarFactory::class);
75+
$this->imageResize = $imageResize;
76+
$this->imageResizeScheduler = $imageResizeScheduler;
77+
$this->progressBarFactory = $progressBarFactory;
78+
$this->productImage = $productImage;
6079
}
6180

6281
/**
@@ -65,7 +84,25 @@ public function __construct(
6584
protected function configure()
6685
{
6786
$this->setName('catalog:images:resize')
68-
->setDescription('Creates resized product images');
87+
->setDescription('Creates resized product images')
88+
->setDefinition($this->getOptionsList());
89+
}
90+
91+
/**
92+
* Image resize command options list
93+
*
94+
* @return array
95+
*/
96+
private function getOptionsList() : array
97+
{
98+
return [
99+
new InputOption(
100+
self::ASYNC_RESIZE,
101+
'a',
102+
InputOption::VALUE_NONE,
103+
'Resize image in asynchronous mode'
104+
),
105+
];
69106
}
70107

71108
/**
@@ -74,11 +111,25 @@ protected function configure()
74111
* @param OutputInterface $output
75112
*/
76113
protected function execute(InputInterface $input, OutputInterface $output)
114+
{
115+
$result = $input->getOption(self::ASYNC_RESIZE) ?
116+
$this->executeAsync($output) : $this->executeSync($output);
117+
118+
return $result;
119+
}
120+
121+
/**
122+
* Run resize in synchronous mode
123+
*
124+
* @param OutputInterface $output
125+
* @return int
126+
*/
127+
private function executeSync(OutputInterface $output): int
77128
{
78129
try {
79130
$errors = [];
80131
$this->appState->setAreaCode(Area::AREA_GLOBAL);
81-
$generator = $this->resize->resizeFromThemes();
132+
$generator = $this->imageResize->resizeFromThemes();
82133

83134
/** @var ProgressBar $progress */
84135
$progress = $this->progressBarFactory->create(
@@ -111,7 +162,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
111162
} catch (\Exception $e) {
112163
$output->writeln("<error>{$e->getMessage()}</error>");
113164
// we must have an exit code higher than zero to indicate something was wrong
114-
return \Magento\Framework\Console\Cli::RETURN_FAILURE;
165+
return Cli::RETURN_FAILURE;
115166
}
116167

117168
$output->write(PHP_EOL);
@@ -124,6 +175,62 @@ protected function execute(InputInterface $input, OutputInterface $output)
124175
$output->writeln("<info>Product images resized successfully</info>");
125176
}
126177

127-
return \Magento\Framework\Console\Cli::RETURN_SUCCESS;
178+
return Cli::RETURN_SUCCESS;
179+
}
180+
181+
/**
182+
* Schedule asynchronous image resizing
183+
*
184+
* @param OutputInterface $output
185+
* @return int
186+
*/
187+
private function executeAsync(OutputInterface $output): int
188+
{
189+
try {
190+
$errors = [];
191+
$this->appState->setAreaCode(Area::AREA_GLOBAL);
192+
193+
/** @var ProgressBar $progress */
194+
$progress = $this->progressBarFactory->create(
195+
[
196+
'output' => $output,
197+
'max' => $this->productImage->getCountUsedProductImages()
198+
]
199+
);
200+
$progress->setFormat(
201+
"%current%/%max% [%bar%] %percent:3s%% %elapsed% %memory:6s% \t| <info>%message%</info>"
202+
);
203+
204+
if ($output->getVerbosity() !== OutputInterface::VERBOSITY_NORMAL) {
205+
$progress->setOverwrite(false);
206+
}
207+
208+
$productImages = $this->productImage->getUsedProductImages();
209+
foreach ($productImages as $image) {
210+
$result = $this->imageResizeScheduler->schedule($image['filepath']);
211+
212+
if (!$result) {
213+
$errors[$image['filepath']] = 'Error image scheduling: ' . $image['filepath'];
214+
}
215+
$progress->setMessage($image['filepath']);
216+
$progress->advance();
217+
}
218+
} catch (\Exception $e) {
219+
$output->writeln("<error>{$e->getMessage()}</error>");
220+
// we must have an exit code higher than zero to indicate something was wrong
221+
return Cli::RETURN_FAILURE;
222+
}
223+
224+
$output->write(PHP_EOL);
225+
if (count($errors)) {
226+
$output->writeln("<info>Product images resized with errors:</info>");
227+
foreach ($errors as $error) {
228+
$output->writeln("<error>{$error}</error>");
229+
}
230+
} else {
231+
$output->writeln("<info>Product images scheduled successfully</info>");
232+
}
233+
234+
return Cli::RETURN_SUCCESS;
128235
}
129236
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\MediaStorage\Model;
9+
10+
use Magento\AsynchronousOperations\Api\Data\OperationInterface;
11+
use Magento\Framework\Serialize\SerializerInterface;
12+
use Psr\Log\LoggerInterface;
13+
use Magento\MediaStorage\Service\ImageResize;
14+
use Magento\Framework\EntityManager\EntityManager;
15+
use Magento\Framework\Exception\NotFoundException;
16+
17+
/**
18+
* Consumer for image resize
19+
*/
20+
class ConsumerImageResize
21+
{
22+
/**
23+
* @var SerializerInterface
24+
*/
25+
private $serializer;
26+
27+
/**
28+
* @var LoggerInterface
29+
*/
30+
private $logger;
31+
32+
/**
33+
* @var ImageResize
34+
*/
35+
private $resize;
36+
37+
/**
38+
* @var EntityManager
39+
*/
40+
private $entityManager;
41+
42+
/**
43+
* @param ImageResize $resize
44+
* @param LoggerInterface $logger
45+
* @param SerializerInterface $serializer
46+
* @param EntityManager $entityManager
47+
*/
48+
public function __construct(
49+
ImageResize $resize,
50+
LoggerInterface $logger,
51+
SerializerInterface $serializer,
52+
EntityManager $entityManager
53+
) {
54+
$this->resize = $resize;
55+
$this->logger = $logger;
56+
$this->serializer = $serializer;
57+
$this->entityManager = $entityManager;
58+
}
59+
60+
/**
61+
* Image resize
62+
*
63+
* @param OperationInterface $operation
64+
* @return void
65+
* @throws \Exception
66+
*/
67+
public function process(OperationInterface $operation): void
68+
{
69+
try {
70+
$serializedData = $operation->getSerializedData();
71+
$data = $this->serializer->unserialize($serializedData);
72+
$this->resize->resizeFromImageName($data['filename']);
73+
} catch (NotFoundException $e) {
74+
$this->logger->critical($e->getMessage());
75+
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
76+
$errorCode = $e->getCode();
77+
$message = $e->getMessage();
78+
} catch (\Exception $e) {
79+
$this->logger->critical($e->getMessage());
80+
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
81+
$errorCode = $e->getCode();
82+
$message = __('Sorry, something went wrong during image resize. Please see log for details.');
83+
}
84+
85+
$operation->setStatus($status ?? OperationInterface::STATUS_TYPE_COMPLETE)
86+
->setErrorCode($errorCode ?? null)
87+
->setResultMessage($message ?? null);
88+
89+
$this->entityManager->save($operation);
90+
}
91+
}

0 commit comments

Comments
 (0)