Files
interview-demo-code/backend/src/console/Commands/ImagesWarmupCacheCommand.php
Nikita Kiselev 0e48b9d56d
Some checks failed
Telegram Mini App Shop Builder / Compute version metadata (push) Has been cancelled
Telegram Mini App Shop Builder / Run Frontend tests (push) Has been cancelled
Telegram Mini App Shop Builder / Run Backend tests (push) Has been cancelled
Telegram Mini App Shop Builder / Run PHP_CodeSniffer (push) Has been cancelled
Telegram Mini App Shop Builder / Build module. (push) Has been cancelled
Telegram Mini App Shop Builder / release (push) Has been cancelled
Squashed commit message
2026-03-11 22:17:44 +03:00

243 lines
9.9 KiB
PHP
Executable File

<?php
namespace Console\Commands;
use App\Services\SettingsService;
use Exception;
use Openguru\OpenCartFramework\ImageTool\ImageFactory;
use Openguru\OpenCartFramework\ImageTool\ImageUtils;
use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
use Openguru\OpenCartFramework\Support\Arr;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class ImagesWarmupCacheCommand extends MegaPayCommand
{
protected static $defaultName = 'images:warmup';
protected static $defaultDescription = 'Прогрев кеша изображений товаров';
private Builder $queryBuilder;
private ImageFactory $image;
private SettingsService $settings;
private LoggerInterface $logger;
public function __construct(
Builder $queryBuilder,
ImageFactory $image,
SettingsService $settings,
LoggerInterface $logger
) {
parent::__construct();
$this->queryBuilder = $queryBuilder;
$this->image = $image;
$this->settings = $settings;
$this->logger = $logger;
}
protected function configure(): void
{
$this->addArgument(
'product_id',
InputArgument::OPTIONAL,
'ID товара для прогрева кеша (если не указан, прогреваются все товары)'
);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$productId = $input->getArgument('product_id');
$io->title('Прогрев кеша изображений товаров');
// Получаем настройки
$aspectRatio = $this->settings->get('app.image_aspect_ratio', '1:1');
$cropAlgorithm = $this->settings->get('app.image_crop_algorithm', 'cover');
[$imageWidth, $imageHeight] = ImageUtils::aspectRatioToSize($aspectRatio);
$languageId = $this->settings->config()->getApp()->getLanguageId();
$io->section('Настройки');
$io->listing([
"Соотношение сторон: {$aspectRatio}",
"Алгоритм обрезки: {$cropAlgorithm}",
"Размер изображения: {$imageWidth}x{$imageHeight}",
"Размер миниатюры: 500x500",
"Размер большого изображения: 1000x1000",
]);
// Получаем список товаров
$products = $this->getProducts($productId, $languageId);
if (empty($products)) {
$io->warning('Товары не найдены');
return Command::SUCCESS;
}
$totalProducts = count($products);
$io->section("Найдено товаров: {$totalProducts}");
$stats = [
'products' => 0,
'main_images' => 0,
'additional_images' => 0,
'thumbnails' => 0,
'large_images' => 0,
'errors' => 0,
];
$progressBar = $io->createProgressBar($totalProducts);
$progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %message%');
$progressBar->setMessage('Обработка товаров...');
$progressBar->start();
foreach ($products as $product) {
$productId = $product['product_id'];
$productName = $product['product_name'] ?? "ID: {$productId}";
$progressBar->setMessage("Товар: {$productName}");
try {
// Прогреваем основное изображение товара
if (!empty($product['product_image'])) {
try {
$this->image->make($product['product_image'])
->crop($cropAlgorithm, $imageWidth, $imageHeight)
->url();
$stats['main_images']++;
} catch (Exception $e) {
$this->logger->error("Ошибка при прогреве основного изображения товара {$productId}: " . $e->getMessage());
$stats['errors']++;
}
}
// Получаем дополнительные изображения товара
$additionalImages = $this->getProductAdditionalImages($productId);
$processedAdditional = 0;
foreach ($additionalImages as $imagePath) {
if ($processedAdditional >= 2) {
break; // Ограничиваем до 2 дополнительных изображений, как в ProductsService
}
try {
$this->image->make($imagePath)
->crop($cropAlgorithm, $imageWidth, $imageHeight)
->url();
$stats['additional_images']++;
$processedAdditional++;
} catch (Exception $e) {
$this->logger->error("Ошибка при прогреве дополнительного изображения товара {$productId}: " . $e->getMessage());
$stats['errors']++;
}
}
// Прогреваем изображения для детальной страницы (миниатюры и большие)
$allImages = [];
if (!empty($product['product_image'])) {
$allImages[] = $product['product_image'];
}
$allImages = array_merge($allImages, $additionalImages);
foreach ($allImages as $imagePath) {
try {
// Миниатюра
$this->image->make($imagePath)
->contain(500, 500)
->url();
$stats['thumbnails']++;
// Большое изображение
$this->image->make($imagePath)
->resize(1000, 1000)
->url();
$stats['large_images']++;
} catch (Exception $e) {
$this->logger->error("Ошибка при прогреве изображений для детальной страницы товара {$productId}: " . $e->getMessage());
$stats['errors']++;
}
}
$stats['products']++;
} catch (Exception $e) {
$this->logger->error("Ошибка при обработке товара {$productId}: " . $e->getMessage());
$stats['errors']++;
}
$progressBar->advance();
}
$progressBar->setMessage('Завершено');
$progressBar->finish();
$io->newLine(2);
// Выводим статистику
$io->section('Статистика');
$io->table(
['Метрика', 'Значение'],
[
['Обработано товаров', $stats['products']],
['Основных изображений', $stats['main_images']],
['Дополнительных изображений', $stats['additional_images']],
['Миниатюр (500x500)', $stats['thumbnails']],
['Больших изображений (1000x1000)', $stats['large_images']],
['Ошибок', $stats['errors']],
]
);
if ($stats['errors'] > 0) {
$io->warning("Обнаружено {$stats['errors']} ошибок. Проверьте логи для подробностей.");
} else {
$io->success('Кеш изображений успешно прогрет!');
}
return Command::SUCCESS;
}
/**
* Получает список товаров для прогрева кеша
*/
private function getProducts(?string $productId, int $languageId): array
{
$query = $this->queryBuilder->newQuery()
->select([
'products.product_id' => 'product_id',
'products.image' => 'product_image',
'product_description.name' => 'product_name',
])
->from(db_table('product'), 'products')
->join(
db_table('product_description') . ' AS product_description',
function (JoinClause $join) use ($languageId) {
$join->on('products.product_id', '=', 'product_description.product_id')
->where('product_description.language_id', '=', $languageId);
}
)
->where('products.status', '=', 1)
->whereRaw('products.date_available < NOW()');
if ($productId !== null) {
$query->where('products.product_id', '=', (int) $productId);
}
return $query->orderBy('products.product_id', 'ASC')->get();
}
/**
* Получает дополнительные изображения товара
*/
private function getProductAdditionalImages(int $productId): array
{
$images = $this->queryBuilder->newQuery()
->select(['products_images.image' => 'image'])
->from(db_table('product_image'), 'products_images')
->where('products_images.product_id', '=', $productId)
->orderBy('products_images.sort_order')
->get();
return Arr::pluck($images, 'image');
}
}