Skip to content

Commit

Permalink
Merge pull request #52 from Scarbous/master
Browse files Browse the repository at this point in the history
Add Batch Update for Product Urls
  • Loading branch information
peterjaap authored Feb 7, 2022
2 parents a98ac76 + 5e4d0fd commit 1dac64d
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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'
);
}

Expand All @@ -86,14 +84,15 @@ public function execute(InputInterface $input, OutputInterface $output)
}

$storeId = $input->getOption('store');

$stores = $this->storeManager->getStores(false);

if (!is_numeric($storeId)) {
$storeId = $this->getStoreIdByCode($storeId, $stores);
}

$this->regenerateProductUrl->setOutput($output);
$this->regenerateProductUrl->execute($input->getArgument('pids'), (int) $storeId);
$this->regenerateProductUrl->execute($input->getArgument('pids'), (int) $storeId, $output->isVerbose());
}

/**
Expand Down
157 changes: 110 additions & 47 deletions Iazel/RegenProductUrl/Service/RegenerateProductUrl.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -18,6 +20,8 @@

class RegenerateProductUrl
{
const BATCH_SIZE = 500;

/**
* @var OutputInterface|null
*/
Expand Down Expand Up @@ -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,
Expand All @@ -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 = is_null($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
Expand All @@ -96,58 +102,72 @@ 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(),
'<error>Duplicated url for store ID %d, product %d (%s) - %s Generated URLs:' .
PHP_EOL . '%s</error>' . 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(
'<error>Duplicated url for store ID %d, product %d (%s) - %s Generated URLs:' .
PHP_EOL . '%s</error>' . 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;
Expand Down Expand Up @@ -183,4 +203,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;
}
}

0 comments on commit 1dac64d

Please sign in to comment.