Skip to content

Commit 959f78f

Browse files
Merge pull request #8 from aligent/feature/BEG-81_category_indexing
BEG-81: Add re-caching of category URLs
2 parents 1fc0dfd + e8306de commit 959f78f

File tree

10 files changed

+521
-7
lines changed

10 files changed

+521
-7
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php
2+
/*
3+
* Copyright (c) Aligent Consulting. All rights reserved.
4+
*/
5+
6+
declare(strict_types=1);
7+
8+
namespace Aligent\PrerenderIo\Model\Indexer\Category;
9+
10+
use Aligent\PrerenderIo\Api\PrerenderClientInterface;
11+
use Aligent\PrerenderIo\Helper\Config;
12+
use Aligent\PrerenderIo\Model\Url\GetUrlsForCategories;
13+
use Magento\Framework\App\DeploymentConfig;
14+
use Magento\Framework\Exception\FileSystemException;
15+
use Magento\Framework\Exception\LocalizedException;
16+
use Magento\Framework\Exception\RuntimeException;
17+
use Magento\Framework\Indexer\ActionInterface as IndexerActionInterface;
18+
use Magento\Framework\Indexer\DimensionalIndexerInterface;
19+
use Magento\Framework\Indexer\DimensionProviderInterface;
20+
use Magento\Framework\Mview\ActionInterface as MviewActionInterface;
21+
use Magento\Store\Model\StoreDimensionProvider;
22+
23+
class CategoryIndexer implements IndexerActionInterface, MviewActionInterface, DimensionalIndexerInterface
24+
{
25+
private const INDEXER_ID = 'prerender_io_category';
26+
private const DEPLOYMENT_CONFIG_INDEXER_BATCHES = 'indexer/batch_size/';
27+
28+
/** @var DimensionProviderInterface */
29+
private DimensionProviderInterface $dimensionProvider;
30+
/** @var GetUrlsForCategories */
31+
private GetUrlsForCategories $getUrlsForCategories;
32+
/** @var PrerenderClientInterface */
33+
private PrerenderClientInterface $prerenderClient;
34+
/** @var DeploymentConfig */
35+
private DeploymentConfig $eploymentConfig;
36+
/** @var Config */
37+
private Config $prerenderConfigHelper;
38+
/** @var int|null */
39+
private ?int $batchSize;
40+
41+
/**
42+
*
43+
* @param DimensionProviderInterface $dimensionProvider
44+
* @param GetUrlsForCategories $getUrlsForCategories
45+
* @param PrerenderClientInterface $prerenderClient
46+
* @param DeploymentConfig $deploymentConfig
47+
* @param Config $prerenderConfigHelper
48+
* @param int|null $batchSize
49+
*/
50+
public function __construct(
51+
DimensionProviderInterface $dimensionProvider,
52+
GetUrlsForCategories $getUrlsForCategories,
53+
PrerenderClientInterface $prerenderClient,
54+
DeploymentConfig $deploymentConfig,
55+
Config $prerenderConfigHelper,
56+
?int $batchSize = 1000
57+
) {
58+
$this->dimensionProvider = $dimensionProvider;
59+
$this->getUrlsForCategories = $getUrlsForCategories;
60+
$this->prerenderClient = $prerenderClient;
61+
$this->deploymentConfig = $deploymentConfig;
62+
$this->batchSize = $batchSize;
63+
$this->prerenderConfigHelper = $prerenderConfigHelper;
64+
}
65+
66+
/**
67+
* Execute full indexation
68+
*
69+
* @return void
70+
*/
71+
public function executeFull(): void
72+
{
73+
$this->executeList([]);
74+
}
75+
76+
/**
77+
* Execute partial indexation by ID list
78+
*
79+
* @param int[] $ids
80+
* @return void
81+
*/
82+
public function executeList(array $ids): void
83+
{
84+
foreach ($this->dimensionProvider->getIterator() as $dimension) {
85+
try {
86+
$this->executeByDimensions($dimension, new \ArrayIterator($ids));
87+
} catch (FileSystemException|RuntimeException $e) {
88+
continue;
89+
}
90+
}
91+
}
92+
93+
/**
94+
* Execute partial indexation by ID
95+
*
96+
* @param int $id
97+
* @return void
98+
* @throws LocalizedException
99+
*/
100+
public function executeRow($id): void
101+
{
102+
if (!$id) {
103+
throw new LocalizedException(
104+
__('Cannot recache url for an undefined product.')
105+
);
106+
}
107+
$this->executeList([$id]);
108+
}
109+
110+
/**
111+
* Execute materialization on ids entities
112+
*
113+
* @param int[] $ids
114+
* @return void
115+
*/
116+
public function execute($ids): void
117+
{
118+
$this->executeList($ids);
119+
}
120+
121+
/**
122+
* Execute indexing per dimension (store)
123+
*
124+
* @param arry $dimensions
125+
* @param \Traversable $entityIds
126+
* @throws FileSystemException
127+
* @throws RuntimeException
128+
*/
129+
public function executeByDimensions(array $dimensions, \Traversable $entityIds): void
130+
{
131+
if (count($dimensions) > 1 || !isset($dimensions[StoreDimensionProvider::DIMENSION_NAME])) {
132+
throw new \InvalidArgumentException('Indexer "' . self::INDEXER_ID . '" supports only Store dimension');
133+
}
134+
$storeId = (int)$dimensions[StoreDimensionProvider::DIMENSION_NAME]->getValue();
135+
136+
if (!$this->prerenderConfigHelper->isRecacheEnabled($storeId)) {
137+
return;
138+
}
139+
140+
$entityIds = iterator_to_array($entityIds);
141+
// get urls for the products
142+
$urls = $this->getUrlsForCategories->execute($entityIds, $storeId);
143+
144+
$this->batchSize = $this->deploymentConfig->get(
145+
self::DEPLOYMENT_CONFIG_INDEXER_BATCHES . self::INDEXER_ID . '/partial_reindex'
146+
) ?? $this->batchSize;
147+
148+
$urlBatches = array_chunk($urls, $this->batchSize);
149+
foreach ($urlBatches as $batchUrls) {
150+
$this->prerenderClient->recacheUrls($batchUrls, $storeId);
151+
}
152+
}
153+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<?php
2+
/*
3+
* Copyright (c) Aligent Consulting. All rights reserved.
4+
*/
5+
6+
declare(strict_types=1);
7+
8+
namespace Aligent\PrerenderIo\Model\Indexer\Category;
9+
10+
use Aligent\PrerenderIo\Api\PrerenderClientInterface;
11+
use Aligent\PrerenderIo\Helper\Config;
12+
use Aligent\PrerenderIo\Model\Indexer\DataProvider\ProductCategories;
13+
use Aligent\PrerenderIo\Model\Url\GetUrlsForCategories;
14+
use Magento\Framework\App\DeploymentConfig;
15+
use Magento\Framework\Exception\FileSystemException;
16+
use Magento\Framework\Exception\LocalizedException;
17+
use Magento\Framework\Exception\RuntimeException;
18+
use Magento\Framework\Indexer\ActionInterface as IndexerActionInterface;
19+
use Magento\Framework\Indexer\DimensionalIndexerInterface;
20+
use Magento\Framework\Indexer\DimensionProviderInterface;
21+
use Magento\Framework\Mview\ActionInterface as MviewActionInterface;
22+
use Magento\Store\Model\StoreDimensionProvider;
23+
24+
class ProductIndexer implements IndexerActionInterface, MviewActionInterface, DimensionalIndexerInterface
25+
{
26+
private const INDEXER_ID = 'prerender_io_category_product';
27+
private const DEPLOYMENT_CONFIG_INDEXER_BATCHES = 'indexer/batch_size/';
28+
29+
/** @var DimensionProviderInterface */
30+
private DimensionProviderInterface $dimensionProvider;
31+
/** @var ProductCategories */
32+
private ProductCategories $productCategoriesDataProvider;
33+
/** @var GetUrlsForCategories */
34+
private GetUrlsForCategories $getUrlsForCategories;
35+
/** @var PrerenderClientInterface */
36+
private PrerenderClientInterface $prerenderClient;
37+
/** @var DeploymentConfig */
38+
private DeploymentConfig $eploymentConfig;
39+
/** @var Config */
40+
private Config $prerenderConfigHelper;
41+
/** @var int|null */
42+
private ?int $batchSize;
43+
44+
/**
45+
*
46+
* @param DimensionProviderInterface $dimensionProvider
47+
* @param ProductCategories $productCategoriesDataProvider
48+
* @param GetUrlsForCategories $getUrlsForCategories
49+
* @param PrerenderClientInterface $prerenderClient
50+
* @param DeploymentConfig $deploymentConfig
51+
* @param Config $prerenderConfigHelper
52+
* @param int|null $batchSize
53+
*/
54+
public function __construct(
55+
DimensionProviderInterface $dimensionProvider,
56+
ProductCategories $productCategoriesDataProvider,
57+
GetUrlsForCategories $getUrlsForCategories,
58+
PrerenderClientInterface $prerenderClient,
59+
DeploymentConfig $deploymentConfig,
60+
Config $prerenderConfigHelper,
61+
?int $batchSize = 1000
62+
) {
63+
$this->dimensionProvider = $dimensionProvider;
64+
$this->productCategoriesDataProvider = $productCategoriesDataProvider;
65+
$this->getUrlsForCategories = $getUrlsForCategories;
66+
$this->prerenderClient = $prerenderClient;
67+
$this->deploymentConfig = $deploymentConfig;
68+
$this->batchSize = $batchSize;
69+
$this->prerenderConfigHelper = $prerenderConfigHelper;
70+
}
71+
72+
/**
73+
* Execute full indexation
74+
*
75+
* @return void
76+
*/
77+
public function executeFull(): void
78+
{
79+
$this->executeList([]);
80+
}
81+
82+
/**
83+
* Execute partial indexation by ID list
84+
*
85+
* @param int[] $ids
86+
* @return void
87+
*/
88+
public function executeList(array $ids): void
89+
{
90+
foreach ($this->dimensionProvider->getIterator() as $dimension) {
91+
try {
92+
$this->executeByDimensions($dimension, new \ArrayIterator($ids));
93+
} catch (FileSystemException|RuntimeException $e) {
94+
continue;
95+
}
96+
}
97+
}
98+
99+
/**
100+
* Execute partial indexation by ID
101+
*
102+
* @param int $id
103+
* @return void
104+
* @throws LocalizedException
105+
*/
106+
public function executeRow($id): void
107+
{
108+
if (!$id) {
109+
throw new LocalizedException(
110+
__('Cannot recache url for an undefined product.')
111+
);
112+
}
113+
$this->executeList([$id]);
114+
}
115+
116+
/**
117+
* Execute materialization on ids entities
118+
*
119+
* @param int[] $ids
120+
* @return void
121+
*/
122+
public function execute($ids): void
123+
{
124+
$this->executeList($ids);
125+
}
126+
127+
/**
128+
* Execute indexing per dimension (store)
129+
*
130+
* @param arry $dimensions
131+
* @param \Traversable $entityIds
132+
* @throws FileSystemException
133+
* @throws RuntimeException
134+
*/
135+
public function executeByDimensions(array $dimensions, \Traversable $entityIds): void
136+
{
137+
if (count($dimensions) > 1 || !isset($dimensions[StoreDimensionProvider::DIMENSION_NAME])) {
138+
throw new \InvalidArgumentException('Indexer "' . self::INDEXER_ID . '" supports only Store dimension');
139+
}
140+
$storeId = (int)$dimensions[StoreDimensionProvider::DIMENSION_NAME]->getValue();
141+
142+
if (!$this->prerenderConfigHelper->isRecacheEnabled($storeId)) {
143+
return;
144+
}
145+
146+
$entityIds = iterator_to_array($entityIds);
147+
// get list of category ids for the products
148+
$categoryIds = $this->productCategoriesDataProvider->getCategoryIdsForProducts($entityIds, $storeId);
149+
150+
// get urls for the products
151+
$urls = $this->getUrlsForCategories->execute($categoryIds, $storeId);
152+
153+
$this->batchSize = $this->deploymentConfig->get(
154+
self::DEPLOYMENT_CONFIG_INDEXER_BATCHES . self::INDEXER_ID . '/partial_reindex'
155+
) ?? $this->batchSize;
156+
157+
$urlBatches = array_chunk($urls, $this->batchSize);
158+
foreach ($urlBatches as $batchUrls) {
159+
$this->prerenderClient->recacheUrls($batchUrls, $storeId);
160+
}
161+
}
162+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
/*
3+
* Copyright (c) Aligent Consulting. All rights reserved.
4+
*/
5+
6+
declare(strict_types=1);
7+
8+
namespace Aligent\PrerenderIo\Model\Indexer\DataProvider;
9+
10+
use Magento\Catalog\Model\Product;
11+
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
12+
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory;
13+
14+
class ProductCategories
15+
{
16+
/** @var ProductCollectionFactory */
17+
private ProductCollectionFactory $productCollectionFactory;
18+
/** @var CategoryCollectionFactory */
19+
private CategoryCollectionFactory $categoryCollectionFactory;
20+
21+
/**
22+
* @param ProductCollectionFactory $productCollectionFactory
23+
* @param CategoryCollectionFactory $categoryCollectionFactory
24+
*/
25+
public function __construct(
26+
ProductCollectionFactory $productCollectionFactory,
27+
CategoryCollectionFactory $categoryCollectionFactory
28+
) {
29+
$this->productCollectionFactory = $productCollectionFactory;
30+
$this->categoryCollectionFactory = $categoryCollectionFactory;
31+
}
32+
33+
/**
34+
* Get complete list of categories containing one or more of the given products
35+
*
36+
* @param array $productIds
37+
* @param int $storeId
38+
* @return array
39+
*/
40+
public function getCategoryIdsForProducts(array $productIds, int $storeId): array
41+
{
42+
// if array of product ids is empty, just load all categories
43+
if (empty($productIds)) {
44+
$categoryCollection = $this->categoryCollectionFactory->create();
45+
$categoryCollection->setStoreId($storeId);
46+
return $categoryCollection->getAllIds();
47+
}
48+
49+
$productCollection = $this->productCollectionFactory->create();
50+
$productCollection->addIdFilter($productIds);
51+
$productCollection->setStoreId($storeId);
52+
// add category information
53+
$productCollection->addCategoryIds();
54+
55+
$categoryIds = [];
56+
/** @var Product $product */
57+
foreach ($productCollection->getItems() as $product) {
58+
$categoryIds[] = $product->getCategoryIds();
59+
}
60+
return array_unique(array_merge(...$categoryIds));
61+
}
62+
}

Model/Indexer/Product/ProductIndexer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
use Aligent\PrerenderIo\Api\PrerenderClientInterface;
1111
use Aligent\PrerenderIo\Helper\Config;
12-
use Aligent\PrerenderIo\Model\Product\GetUrlsForProducts;
12+
use Aligent\PrerenderIo\Model\Url\GetUrlsForProducts;
1313
use Magento\Framework\App\DeploymentConfig;
1414
use Magento\Framework\Exception\FileSystemException;
1515
use Magento\Framework\Exception\LocalizedException;

0 commit comments

Comments
 (0)