Skip to content

Commit 439fa40

Browse files
committed
MAGE-837 Implement validator for virtual replicas
1 parent 9722c7d commit 439fa40

File tree

7 files changed

+138
-23
lines changed

7 files changed

+138
-23
lines changed

Api/Product/ReplicaManagerInterface.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99

1010
interface ReplicaManagerInterface
1111
{
12-
public const REPLICA_TRANSFORM_MODE_STANDARD = 1;
13-
public const REPLICA_TRANSFORM_MODE_VIRTUAL = 2;
14-
public const REPLICA_TRANSFORM_MODE_ACTUAL = 3;
12+
public const SORT_ATTRIBUTE_PRICE = 'price';
1513

14+
public const SORT_KEY_ATTRIBUTE_NAME = 'attribute';
1615
public const SORT_KEY_VIRTUAL_REPLICA = 'virtualReplica';
16+
// https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/in-depth/replicas/#what-are-virtual-replicas
17+
public const MAX_VIRTUAL_REPLICA_LIMIT = 20;
18+
1719

1820
/**
1921
* Configure replicas in Algolia based on the sorting configuration in Magento
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Algolia\AlgoliaSearch\Exception;
4+
5+
use Algolia\AlgoliaSearch\Exceptions\AlgoliaException;
6+
7+
class ReplicaLimitExceededException extends AlgoliaException
8+
{
9+
protected int $replicaCount = 0;
10+
11+
public function withReplicaCount(int $replicaCount): ReplicaLimitExceededException
12+
{
13+
$this->replicaCount = $replicaCount;
14+
return $this;
15+
}
16+
17+
public function getReplicaCount(): int {
18+
return $this->replicaCount;
19+
}
20+
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Algolia\AlgoliaSearch\Exception;
4+
5+
use Algolia\AlgoliaSearch\Exception\ReplicaLimitExceededException;
6+
7+
class TooManyCustomerGroupsAsReplicasException extends ReplicaLimitExceededException
8+
{
9+
protected int $priceSortReplicaCount = 0;
10+
11+
public function withPriceSortReplicaCount(int $priceSortReplicaCount): TooManyCustomerGroupsAsReplicasException
12+
{
13+
$this->priceSortReplicaCount = $priceSortReplicaCount;
14+
return $this;
15+
}
16+
17+
public function getPriceSortReplicaCount(): int
18+
{
19+
return $this->priceSortReplicaCount;
20+
}
21+
}

Helper/ConfigHelper.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,6 @@ class ConfigHelper
146146
public const ENHANCED_QUEUE_ARCHIVE = 'algoliasearch_advanced/queue/enhanced_archive';
147147
public const NUMBER_OF_ELEMENT_BY_PAGE = 'algoliasearch_advanced/queue/number_of_element_by_page';
148148
public const ARCHIVE_LOG_CLEAR_LIMIT = 'algoliasearch_advanced/queue/archive_clear_limit';
149-
// https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/in-depth/replicas/#what-are-virtual-replicas
150-
public const MAX_VIRTUAL_REPLICA_LIMIT = 20;
151-
152-
public const SORT_ATTRIBUTE_PRICE = 'price';
153149

154150
/**
155151
* @var Magento\Framework\App\Config\ScopeConfigInterface
@@ -1125,7 +1121,7 @@ public function getSortingIndices(
11251121
$indexName = false;
11261122
$sortAttribute = false;
11271123
// Group pricing
1128-
if ($this->isCustomerGroupsEnabled($storeId) && $attr['attribute'] === self::SORT_ATTRIBUTE_PRICE) {
1124+
if ($this->isCustomerGroupsEnabled($storeId) && $attr[ReplicaManagerInterface::SORT_KEY_ATTRIBUTE_NAME] === ReplicaManagerInterface::SORT_ATTRIBUTE_PRICE) {
11291125
$websiteId = (int) $this->storeManager->getStore($storeId)->getWebsiteId();
11301126
$groupCollection = $this->groupCollection;
11311127
if (!is_null($currentCustomerGroupId)) {
@@ -1139,7 +1135,7 @@ public function getSortingIndices(
11391135
}
11401136
}
11411137
// Regular pricing
1142-
} elseif ($attr['attribute'] === self::SORT_ATTRIBUTE_PRICE) {
1138+
} elseif ($attr[ReplicaManagerInterface::SORT_KEY_ATTRIBUTE_NAME] === ReplicaManagerInterface::SORT_ATTRIBUTE_PRICE) {
11431139
$indexName = $originalIndexName . '_' . $attr['attribute'] . '_' . 'default' . '_' . $attr['sort'];
11441140
$sortAttribute = $attr['attribute'] . '.' . $currency . '.' . 'default';
11451141
// All other sort attributes
@@ -1158,7 +1154,7 @@ public function getSortingIndices(
11581154
$attrsToReturn = [];
11591155

11601156
foreach ($attrs as $attr) {
1161-
if ($attr['attribute'] == self::SORT_ATTRIBUTE_PRICE
1157+
if ($attr[ReplicaManagerInterface::SORT_KEY_ATTRIBUTE_NAME] == ReplicaManagerInterface::SORT_ATTRIBUTE_PRICE
11621158
&& count($attributesToAdd)
11631159
&& isset($attributesToAdd[$attr['sort']])) {
11641160
$attrsToReturn = array_merge($attrsToReturn, $attributesToAdd[$attr['sort']]);

Helper/Entity/ProductHelper.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ public function setSettings(string $indexName, string $indexNameTmp, int $storeI
373373
}
374374

375375
$this->replicaManager->handleReplicas($indexName, $storeId, $indexSettings);
376+
// TODO: Reevaluate whether we need to pre-bake the temp index replicas
376377
if ($saveToTmpIndicesToo) {
377378
$this->replicaManager->handleReplicas($indexNameTmp, $storeId, $indexSettings);
378379
}

Model/Product/ReplicaManager.php

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
namespace Algolia\AlgoliaSearch\Model\Product;
66

77
use Algolia\AlgoliaSearch\Api\Product\ReplicaManagerInterface;
8+
use Algolia\AlgoliaSearch\Exception\ReplicaLimitExceededException;
9+
use Algolia\AlgoliaSearch\Exception\TooManyCustomerGroupsAsReplicasException;
810
use Algolia\AlgoliaSearch\Exceptions\AlgoliaException;
911
use Algolia\AlgoliaSearch\Helper\AlgoliaHelper;
1012
use Algolia\AlgoliaSearch\Helper\ConfigHelper;
1113
use Algolia\AlgoliaSearch\Helper\Logger;
14+
use Algolia\AlgoliaSearch\Validator\VirtualReplicaValidatorFactory;
1215
use Algolia\AlgoliaSearch\Registry\ReplicaState;
1316
use Magento\Framework\Exception\LocalizedException;
1417
use Magento\Framework\Exception\NoSuchEntityException;
@@ -37,18 +40,17 @@ class ReplicaManager implements ReplicaManagerInterface
3740
public const REPLICA_TRANSFORM_MODE_VIRTUAL = 2;
3841
public const REPLICA_TRANSFORM_MODE_ACTUAL = 3;
3942

40-
public const SORT_KEY_VIRTUAL_REPLICA = 'virtualReplica';
41-
4243
protected const _DEBUG = true;
4344

4445
protected array $_algoliaReplicaConfig = [];
4546
protected array $_magentoReplicaPossibleConfig = [];
4647

4748
public function __construct(
48-
protected ConfigHelper $configHelper,
49-
protected AlgoliaHelper $algoliaHelper,
50-
protected ReplicaState $replicaState,
51-
protected Logger $logger
49+
protected ConfigHelper $configHelper,
50+
protected AlgoliaHelper $algoliaHelper,
51+
protected ReplicaState $replicaState,
52+
protected VirtualReplicaValidatorFactory $validatorFactory,
53+
protected Logger $logger
5254
)
5355
{}
5456

@@ -167,9 +169,7 @@ function ($sort) use ($mode) {
167169
$replica = $sort['name'];
168170
if (
169171
$mode === self::REPLICA_TRANSFORM_MODE_VIRTUAL
170-
|| array_key_exists(self::SORT_KEY_VIRTUAL_REPLICA, $sort)
171-
&& $sort[self::SORT_KEY_VIRTUAL_REPLICA]
172-
&& $mode === self::REPLICA_TRANSFORM_MODE_ACTUAL
172+
|| !empty($sort[self::SORT_KEY_VIRTUAL_REPLICA]) && $mode === self::REPLICA_TRANSFORM_MODE_ACTUAL
173173
) {
174174
$replica = "virtual($replica)";
175175
}
@@ -210,9 +210,10 @@ protected function getMagentoReplicaSettingsFromConfig(string $primaryIndexName,
210210
*/
211211
public function handleReplicas(string $primaryIndexName, int $storeId, array $primaryIndexSettings): void
212212
{
213-
// TODO: Determine if InstantSearch is a hard requirement (i.e. headless implementations may still need replicas)
213+
// TODO: InstantSearch enablement should not be a hard requirement (i.e. headless implementations may still need replicas) - add an override for this
214214
if ($this->configHelper->isInstantEnabled($storeId)
215-
&& $this->hasReplicaConfigurationChanged($primaryIndexName, $storeId)) {
215+
&& $this->hasReplicaConfigurationChanged($primaryIndexName, $storeId)
216+
&& $this->isReplicaConfigurationValid($primaryIndexName, $storeId)) {
216217
$addedReplicas = $this->setReplicasOnPrimaryIndex($primaryIndexName, $storeId);
217218
$this->configureRanking($primaryIndexName, $storeId, $addedReplicas, $primaryIndexSettings);
218219
}
@@ -262,6 +263,33 @@ protected function setReplicasOnPrimaryIndex(string $indexName, int $storeId): a
262263
return $replicasToRank;
263264
}
264265

266+
/**
267+
* @param string $primaryIndexName
268+
* @param int $storeId
269+
* @return bool
270+
* @throws LocalizedException
271+
* @throws NoSuchEntityException
272+
* @throws ReplicaLimitExceededException
273+
*/
274+
protected function isReplicaConfigurationValid(string $primaryIndexName, int $storeId): bool
275+
{
276+
$sortingIndices = $this->configHelper->getSortingIndices($primaryIndexName, $storeId);
277+
$validator = $this->validatorFactory->create();
278+
if (!$validator->isReplicaConfigurationValid($sortingIndices)) {
279+
// TODO: Implement revert settings via ReplicaState
280+
if ($validator->isTooManyCustomerGroups()) {
281+
throw (new TooManyCustomerGroupsAsReplicasException("You have too many customer groups to enable virtual replicas on the pricing sort."))
282+
->withReplicaCount($validator->getReplicaCount())
283+
->withPriceSortReplicaCount($validator->getPriceSortReplicaCount());
284+
}
285+
else {
286+
throw (new ReplicaLimitExceededException("Replica limit exceeded."))
287+
->withReplicaCount($validator->getReplicaCount());
288+
}
289+
}
290+
return true;
291+
}
292+
265293
/**
266294
* @param string[] $replicas
267295
* @return string[]
@@ -316,8 +344,7 @@ function($replica) use ($replicas) {
316344
foreach ($replicaDetails as $replica) {
317345
$replicaName = $replica['name'];
318346
// Virtual replicas - relevant sort
319-
if (array_key_exists(self::SORT_KEY_VIRTUAL_REPLICA, $replica)
320-
&& $replica[self::SORT_KEY_VIRTUAL_REPLICA]) {
347+
if (!empty($replica[self::SORT_KEY_VIRTUAL_REPLICA])) {
321348
$customRanking = array_key_exists('customRanking', $primaryIndexSettings)
322349
? $primaryIndexSettings['customRanking']
323350
: [];

Validator/VirtualReplicaValidator.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Algolia\AlgoliaSearch\Validator;
4+
5+
use Algolia\AlgoliaSearch\Api\Product\ReplicaManagerInterface;
6+
7+
class VirtualReplicaValidator
8+
{
9+
protected int $replicaCount = 0;
10+
protected int $priceSortReplicaCount = 0;
11+
protected bool $replicaLimitExceeded = false;
12+
protected bool $tooManyCustomerGroups = false;
13+
14+
public function isReplicaConfigurationValid(array $replicas): bool
15+
{
16+
foreach ($replicas as $replica) {
17+
// TODO: Implement replica limit override
18+
if (!empty($replica[ReplicaManagerInterface::SORT_KEY_VIRTUAL_REPLICA])) {
19+
$this->replicaCount++;
20+
if ($replica[ReplicaManagerInterface::SORT_KEY_ATTRIBUTE_NAME] == ReplicaManagerInterface::SORT_ATTRIBUTE_PRICE) {
21+
$this->priceSortReplicaCount++;
22+
}
23+
}
24+
25+
if ($this->replicaCount > ReplicaManagerInterface::MAX_VIRTUAL_REPLICA_LIMIT) {
26+
$this->replicaLimitExceeded = true;
27+
$this->tooManyCustomerGroups = $this->priceSortReplicaCount > ReplicaManagerInterface::MAX_VIRTUAL_REPLICA_LIMIT;
28+
}
29+
}
30+
return !$this->replicaLimitExceeded;
31+
}
32+
33+
public function isTooManyCustomerGroups(): bool
34+
{
35+
return $this->tooManyCustomerGroups;
36+
}
37+
38+
public function getReplicaCount(): int
39+
{
40+
return $this->replicaCount;
41+
}
42+
43+
public function getPriceSortReplicaCount(): int
44+
{
45+
return $this->priceSortReplicaCount;
46+
}
47+
}

0 commit comments

Comments
 (0)