From d52978ecb829d915b2d6d994630a351f799c4063 Mon Sep 17 00:00:00 2001 From: Sascha Date: Mon, 7 Feb 2022 10:42:51 +0000 Subject: [PATCH 1/3] Add Batch Update for Product Urls --- .../Command/RegenerateProductUrlCommand.php | 9 +- .../Service/RegenerateProductUrl.php | 159 ++++++++++++------ 2 files changed, 116 insertions(+), 52 deletions(-) diff --git a/Iazel/RegenProductUrl/Console/Command/RegenerateProductUrlCommand.php b/Iazel/RegenProductUrl/Console/Command/RegenerateProductUrlCommand.php index cbf68d2..28be6df 100644 --- a/Iazel/RegenProductUrl/Console/Command/RegenerateProductUrlCommand.php +++ b/Iazel/RegenProductUrl/Console/Command/RegenerateProductUrlCommand.php @@ -59,14 +59,12 @@ protected function configure(): void 'store', 's', InputOption::VALUE_REQUIRED, - 'Regenerate for one specific store view', - Store::DEFAULT_STORE_ID + 'Regenerate for one specific store view' ) ->addArgument( 'pids', InputArgument::IS_ARRAY, - 'Product IDs to regenerate', - [] + 'Product IDs to regenerate' ); } @@ -86,6 +84,7 @@ public function execute(InputInterface $input, OutputInterface $output) } $storeId = $input->getOption('store'); + $stores = $this->storeManager->getStores(false); if (!is_numeric($storeId)) { @@ -93,7 +92,7 @@ public function execute(InputInterface $input, OutputInterface $output) } $this->regenerateProductUrl->setOutput($output); - $this->regenerateProductUrl->execute($input->getArgument('pids'), (int) $storeId); + $this->regenerateProductUrl->execute($input->getArgument('pids'), (int) $storeId, $output->isVerbose()); } /** diff --git a/Iazel/RegenProductUrl/Service/RegenerateProductUrl.php b/Iazel/RegenProductUrl/Service/RegenerateProductUrl.php index 2fc6f35..16976d8 100644 --- a/Iazel/RegenProductUrl/Service/RegenerateProductUrl.php +++ b/Iazel/RegenProductUrl/Service/RegenerateProductUrl.php @@ -5,11 +5,13 @@ namespace Iazel\RegenProductUrl\Service; use Exception; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\Catalog\Model\Product\Visibility; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; +use Magento\Store\Api\Data\StoreInterface; use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; use Magento\UrlRewrite\Model\UrlPersistInterface; @@ -18,6 +20,8 @@ class RegenerateProductUrl { + const BATCH_SIZE = 500; + /** * @var OutputInterface|null */ @@ -52,10 +56,10 @@ class RegenerateProductUrl /** * Constructor. * - * @param CollectionFactory $collectionFactory + * @param CollectionFactory $collectionFactory * @param ProductUrlRewriteGenerator $urlRewriteGenerator - * @param UrlPersistInterface $urlPersist - * @param StoreManagerInterface $storeManager + * @param UrlPersistInterface $urlPersist + * @param StoreManagerInterface $storeManager */ public function __construct( CollectionFactory $collectionFactory, @@ -70,23 +74,25 @@ public function __construct( } /** - * @param int[] $productIds - * @param int $storeId + * @param int[]|nul $productIds + * @param int|nul $storeId + * @param bool $verbose * * @return void + * @throws \Magento\Framework\Exception\NoSuchEntityException */ - public function execute(array $productIds, int $storeId): void + public function execute(?array $productIds = null, ?int $storeId = null, bool $verbose = false): void { $this->regeneratedCount = 0; - $stores = $this->storeManager->getStores(false); + + $stores = isNull($storeId) + ? [$this->storeManager->getStore($storeId)] + : $this->storeManager->getStores(false); foreach ($stores as $store) { $regeneratedForStore = 0; - // If store has been given through option, skip other stores - if ($storeId !== Store::DEFAULT_STORE_ID and (int) $store->getId() !== $storeId) { - continue; - } + $this->log(sprintf('Start regenerating for store %s (%d)', $store->getName(), $store->getId())); $collection = $this->collectionFactory->create(); $collection @@ -96,58 +102,74 @@ public function execute(array $productIds, int $storeId): void ->addFieldToFilter('status', ['eq' => Status::STATUS_ENABLED]) ->addFieldToFilter('visibility', ['gt' => Visibility::VISIBILITY_NOT_VISIBLE]); - if (!empty($productIds)) { + if (is_null($productIds)) { $collection->addIdFilter($productIds); } $collection->addAttributeToSelect(['url_path', 'url_key']); - $list = $collection->load(); + + $deleteProducts = []; /** @var Product $product */ - foreach ($list as $product) { + foreach ($collection as $product) { + $deleteProducts[] = $product->getId(); + if (count($deleteProducts) >= self::BATCH_SIZE) { + $this->deleteUrls($deleteProducts, $store); + } + } + + if (count($deleteProducts)) { + $this->deleteUrls($deleteProducts, $store, true); + } + + $newUrls = []; + try { + /** @var Product $product */ + foreach ($collection as $product) { + if ($verbose) { + $this->log( + sprintf( + 'Regenerating urls for %s (%s) in store (%s)', + $product->getSku(), + $product->getId(), + $store->getName() + ) + ); + } + + $product->setStoreId($store->getId()); + + $newUrls = array_merge($newUrls, $this->urlRewriteGenerator->generate($product)); + if (count($newUrls) >= self::BATCH_SIZE) { + $regeneratedForStore += $this->replaceUrls($newUrls); + } + + } + + if (count($newUrls)) { + $regeneratedForStore += $this->replaceUrls($newUrls, true); + } + + } catch (Exception $e) { $this->log( sprintf( - 'Regenerating urls for %s (%s) in store (%s)', - $product->getSku(), + 'Duplicated url for store ID %d, product %d (%s) - %s Generated URLs:' . + PHP_EOL . '%s' . PHP_EOL, + $store->getId(), $product->getId(), - $store->getName() + $product->getSku(), + $e->getMessage(), + implode(PHP_EOL, array_keys($newUrls)) ) ); - $product->setStoreId($store->getId()); - - $this->urlPersist->deleteByData( - [ - UrlRewrite::ENTITY_ID => $product->getId(), - UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE, - UrlRewrite::REDIRECT_TYPE => 0, - UrlRewrite::STORE_ID => $store->getId() - ] - ); - - $newUrls = $this->urlRewriteGenerator->generate($product); - try { - $this->urlPersist->replace($newUrls); - $regeneratedForStore += count($newUrls); - } catch (Exception $e) { - $this->log( - sprintf( - 'Duplicated url for store ID %d, product %d (%s) - %s Generated URLs:' . - PHP_EOL . '%s' . PHP_EOL, - $store->getId(), - $product->getId(), - $product->getSku(), - $e->getMessage(), - implode(PHP_EOL, array_keys($newUrls)) - ) - ); - } } $this->log( sprintf( - 'Done regenerating. Regenerated %d urls for store %s', + 'Done regenerating. Regenerated %d urls for store %s (%d)', $regeneratedForStore, - $store->getName() + $store->getName(), + $store->getId() ) ); $this->regeneratedCount += $regeneratedForStore; @@ -183,4 +205,47 @@ private function log(string $message): void $this->output->writeln($message); } } + + /** + * Add product ulrs + * + * @param array $urls + * @param bool $last + * + * @return int + * @throws \Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException + */ + private function replaceUrls(array &$urls, bool $last = false): int + { + $this->log(sprintf('replaceUrls%s batch: %d', $last ? ' last' : '', count($urls))); + $this->urlPersist->replace($urls); + $count = count($urls); + $urls = []; + + return $count; + } + + /** + * Remove old product urls + * + * @param array $productIds + * @param StoreInterface $store + * @param bool $last + * + * @return int + */ + private function deleteUrls(array &$productIds, StoreInterface $store, bool $last = false): int + { + $this->log(sprintf('deleteUrls%s batch: %d', $last ? 'last' : '', count($productIds))); + $this->urlPersist->deleteByData([ + UrlRewrite::ENTITY_ID => $productIds, + UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE, + UrlRewrite::REDIRECT_TYPE => 0, + UrlRewrite::STORE_ID => $store->getId() + ]); + $count = count($productIds); + $productIds = []; + + return $count; + } } From 16bd5aa8cfbf7bafa87390357dd5fd426ad3bbcd Mon Sep 17 00:00:00 2001 From: Sascha Date: Mon, 7 Feb 2022 10:50:14 +0000 Subject: [PATCH 2/3] Code CleanUp --- Iazel/RegenProductUrl/Service/RegenerateProductUrl.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Iazel/RegenProductUrl/Service/RegenerateProductUrl.php b/Iazel/RegenProductUrl/Service/RegenerateProductUrl.php index 16976d8..b22b7b4 100644 --- a/Iazel/RegenProductUrl/Service/RegenerateProductUrl.php +++ b/Iazel/RegenProductUrl/Service/RegenerateProductUrl.php @@ -143,13 +143,11 @@ public function execute(?array $productIds = null, ?int $storeId = null, bool $v if (count($newUrls) >= self::BATCH_SIZE) { $regeneratedForStore += $this->replaceUrls($newUrls); } - } if (count($newUrls)) { $regeneratedForStore += $this->replaceUrls($newUrls, true); } - } catch (Exception $e) { $this->log( sprintf( From 5e4d0fd9d2d74a735ec96546916539735700ed04 Mon Sep 17 00:00:00 2001 From: Sascha Date: Mon, 7 Feb 2022 13:02:02 +0000 Subject: [PATCH 3/3] Wrong NULL check function https://github.com/elgentos/regenerate-catalog-urls/pull/52/files/16bd5aa8cfbf7bafa87390357dd5fd426ad3bbcd#r800540098 --- Iazel/RegenProductUrl/Service/RegenerateProductUrl.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Iazel/RegenProductUrl/Service/RegenerateProductUrl.php b/Iazel/RegenProductUrl/Service/RegenerateProductUrl.php index b22b7b4..932a7a1 100644 --- a/Iazel/RegenProductUrl/Service/RegenerateProductUrl.php +++ b/Iazel/RegenProductUrl/Service/RegenerateProductUrl.php @@ -85,7 +85,7 @@ public function execute(?array $productIds = null, ?int $storeId = null, bool $v { $this->regeneratedCount = 0; - $stores = isNull($storeId) + $stores = is_null($storeId) ? [$this->storeManager->getStore($storeId)] : $this->storeManager->getStores(false);