Skip to content

Commit ee3207f

Browse files
authored
Merge pull request #1545 from algolia/feature/MAGE-837
Feature/mage 837 - virtual replica validator
2 parents b625777 + 755f346 commit ee3207f

File tree

11 files changed

+321
-157
lines changed

11 files changed

+321
-157
lines changed

Api/Product/ReplicaManagerInterface.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99

1010
interface ReplicaManagerInterface
1111
{
12+
public const SORT_ATTRIBUTE_PRICE = 'price';
13+
14+
public const SORT_KEY_ATTRIBUTE_NAME = 'attribute';
15+
public const SORT_KEY_VIRTUAL_REPLICA = 'virtualReplica';
16+
public const MAX_VIRTUAL_REPLICA_LIMIT = 20;
17+
18+
1219
/**
1320
* Configure replicas in Algolia based on the sorting configuration in Magento
1421
*
@@ -23,4 +30,21 @@ interface ReplicaManagerInterface
2330
* @throws NoSuchEntityException
2431
*/
2532
public function handleReplicas(string $primaryIndexName, int $storeId, array $primaryIndexSettings): void;
33+
34+
35+
/**
36+
* For standard Magento front end (e.g. Luma) replicas will likely only be needed if InstantSearch is enabled
37+
* Headless implementations may wish to override this behavior via plugin
38+
* @param int $storeId
39+
* @return bool
40+
*/
41+
public function isReplicaSyncEnabled(int $storeId): bool;
42+
43+
/**
44+
* Return the number of virtual replicas permitted per index
45+
* @link https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/in-depth/replicas/#differences
46+
*
47+
* @return int
48+
*/
49+
public function getMaxVirtualReplicasPerIndex() : int;
2650
}
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 Magento\Framework\Exception\LocalizedException;
6+
7+
class ReplicaLimitExceededException extends LocalizedException
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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Algolia\AlgoliaSearch\Exception;
4+
5+
class TooManyCustomerGroupsAsReplicasException extends ReplicaLimitExceededException
6+
{
7+
protected int $priceSortReplicaCount = 0;
8+
9+
public function withPriceSortReplicaCount(int $priceSortReplicaCount): TooManyCustomerGroupsAsReplicasException
10+
{
11+
$this->priceSortReplicaCount = $priceSortReplicaCount;
12+
return $this;
13+
}
14+
15+
public function getPriceSortReplicaCount(): int
16+
{
17+
return $this->priceSortReplicaCount;
18+
}
19+
}

Helper/ConfigHelper.php

Lines changed: 67 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
namespace Algolia\AlgoliaSearch\Helper;
44

5-
use Algolia\AlgoliaSearch\Model\Product\ReplicaManager;
5+
use Algolia\AlgoliaSearch\Api\Product\ReplicaManagerInterface;
66
use Magento;
7+
use Magento\Cookie\Helper\Cookie as CookieHelper;
8+
use Magento\Customer\Api\GroupExcludedWebsiteRepositoryInterface;
79
use Magento\Customer\Model\ResourceModel\Group\Collection as GroupCollection;
810
use Magento\Directory\Model\Currency as DirCurrency;
911
use Magento\Framework\App\Filesystem\DirectoryList;
@@ -13,9 +15,6 @@
1315
use Magento\Framework\Serialize\SerializerInterface;
1416
use Magento\Store\Model\ScopeInterface;
1517
use Magento\Store\Model\StoreManagerInterface;
16-
use Magento\Customer\Api\GroupExcludedWebsiteRepositoryInterface;
17-
use Magento\Cookie\Helper\Cookie as CookieHelper;
18-
1918

2019
class ConfigHelper
2120
{
@@ -147,70 +146,6 @@ class ConfigHelper
147146
public const ENHANCED_QUEUE_ARCHIVE = 'algoliasearch_advanced/queue/enhanced_archive';
148147
public const NUMBER_OF_ELEMENT_BY_PAGE = 'algoliasearch_advanced/queue/number_of_element_by_page';
149148
public const ARCHIVE_LOG_CLEAR_LIMIT = 'algoliasearch_advanced/queue/archive_clear_limit';
150-
// https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/in-depth/replicas/#what-are-virtual-replicas
151-
public const MAX_VIRTUAL_REPLICA_LIMIT = 20;
152-
153-
public const SORT_ATTRIBUTE_PRICE = 'price';
154-
155-
/**
156-
* @var Magento\Framework\App\Config\ScopeConfigInterface
157-
*/
158-
protected $configInterface;
159-
160-
/**
161-
* @var Currency
162-
*/
163-
protected $currency;
164-
165-
/**
166-
* @var StoreManagerInterface
167-
*/
168-
protected $storeManager;
169-
170-
/**
171-
* @var DirCurrency
172-
*/
173-
protected $dirCurrency;
174-
175-
/**
176-
* @var DirectoryList
177-
*/
178-
protected $directoryList;
179-
180-
/**
181-
* @var Magento\Framework\Module\ResourceInterface
182-
*/
183-
protected $moduleResource;
184-
185-
/**
186-
* @var Magento\Framework\App\ProductMetadataInterface
187-
*/
188-
protected $productMetadata;
189-
190-
/**
191-
* @var Magento\Framework\Event\ManagerInterface
192-
*/
193-
protected $eventManager;
194-
195-
/**
196-
* @var SerializerInterface
197-
*/
198-
protected $serializer;
199-
200-
/**
201-
* @var GroupCollection
202-
*/
203-
protected $groupCollection;
204-
205-
/**
206-
* @var GroupExcludedWebsiteRepositoryInterface
207-
*/
208-
protected $groupExcludedWebsiteRepository;
209-
210-
/**
211-
* @var CookieHelper
212-
*/
213-
protected $cookieHelper;
214149

215150
/**
216151
* @var array<int,<array<string, mixed>>>
@@ -233,32 +168,21 @@ class ConfigHelper
233168
234169
*/
235170
public function __construct(
236-
Magento\Framework\App\Config\ScopeConfigInterface $configInterface,
237-
StoreManagerInterface $storeManager,
238-
Currency $currency,
239-
DirCurrency $dirCurrency,
240-
DirectoryList $directoryList,
241-
Magento\Framework\Module\ResourceInterface $moduleResource,
242-
Magento\Framework\App\ProductMetadataInterface $productMetadata,
243-
Magento\Framework\Event\ManagerInterface $eventManager,
244-
SerializerInterface $serializer,
245-
GroupCollection $groupCollection,
246-
GroupExcludedWebsiteRepositoryInterface $groupExcludedWebsiteRepository,
247-
CookieHelper $cookieHelper
248-
) {
249-
$this->configInterface = $configInterface;
250-
$this->currency = $currency;
251-
$this->storeManager = $storeManager;
252-
$this->dirCurrency = $dirCurrency;
253-
$this->directoryList = $directoryList;
254-
$this->moduleResource = $moduleResource;
255-
$this->productMetadata = $productMetadata;
256-
$this->eventManager = $eventManager;
257-
$this->serializer = $serializer;
258-
$this->groupCollection = $groupCollection;
259-
$this->groupExcludedWebsiteRepository = $groupExcludedWebsiteRepository;
260-
$this->cookieHelper = $cookieHelper;
261-
}
171+
protected Magento\Framework\App\Config\ScopeConfigInterface $configInterface,
172+
protected Magento\Framework\App\Config\Storage\WriterInterface $configWriter,
173+
protected StoreManagerInterface $storeManager,
174+
protected Currency $currency,
175+
protected DirCurrency $dirCurrency,
176+
protected DirectoryList $directoryList,
177+
protected Magento\Framework\Module\ResourceInterface $moduleResource,
178+
protected Magento\Framework\App\ProductMetadataInterface $productMetadata,
179+
protected Magento\Framework\Event\ManagerInterface $eventManager,
180+
protected SerializerInterface $serializer,
181+
protected GroupCollection $groupCollection,
182+
protected GroupExcludedWebsiteRepositoryInterface $groupExcludedWebsiteRepository,
183+
protected CookieHelper $cookieHelper
184+
)
185+
{}
262186

263187

264188
/**
@@ -519,6 +443,10 @@ public function getAutocompleteSections($storeId = null)
519443
return [];
520444
}
521445

446+
protected function serialize(array $value): string {
447+
return $this->serializer->serialize($value);
448+
}
449+
522450
/**
523451
* @param $value
524452
* @return array|bool|float|int|mixed|string|null
@@ -1033,16 +961,14 @@ protected function getCustomerGroupSortPriceOverride(
1033961
$attrName = $origAttr['attribute'];
1034962
$sortDir = $origAttr['sort'];
1035963
$groupIndexNameSuffix = 'group_' . $customerGroupId;
1036-
$groupIndexName =
1037-
$originalIndexName . '_' . $attrName . '_' . $groupIndexNameSuffix . '_' . $sortDir;
1038-
$groupSortAttribute = $attrName . '.' . $currency . '.' . $groupIndexNameSuffix;
1039-
$newAttr = [
1040-
'attribute' => $attrName,
1041-
'name' => $groupIndexName,
1042-
'sort' => $sortDir,
1043-
'sortLabel' => $origAttr['sortLabel']
1044-
];
964+
$groupIndexName = $originalIndexName . '_' . $attrName . '_' . $groupIndexNameSuffix . '_' . $sortDir;
965+
966+
$newAttr = array_merge(
967+
$origAttr,
968+
['name' => $groupIndexName]
969+
);
1045970

971+
$groupSortAttribute = $attrName . '.' . $currency . '.' . $groupIndexNameSuffix;
1046972
$newAttr['ranking'] = $this->getSortAttributingRankingSetting($groupSortAttribute, $sortDir);
1047973
return $this->decorateSortAttribute($newAttr);
1048974
}
@@ -1090,6 +1016,7 @@ protected function isGroupPricingExcludedFromWebsite(int $customerGroupId, int $
10901016
/**
10911017
* Augment sorting configuration with corresponding replica indices, ranking,
10921018
* and (as needed) customer group pricing
1019+
* TODO: MAGE-941 Remove the $originalIndexName param - this should never be needed as tmp indices cannot have attached replicas
10931020
*
10941021
* @param string $originalIndexName
10951022
* @param ?int $storeId
@@ -1126,7 +1053,7 @@ public function getSortingIndices(
11261053
$indexName = false;
11271054
$sortAttribute = false;
11281055
// Group pricing
1129-
if ($this->isCustomerGroupsEnabled($storeId) && $attr['attribute'] === self::SORT_ATTRIBUTE_PRICE) {
1056+
if ($this->isCustomerGroupsEnabled($storeId) && $attr[ReplicaManagerInterface::SORT_KEY_ATTRIBUTE_NAME] === ReplicaManagerInterface::SORT_ATTRIBUTE_PRICE) {
11301057
$websiteId = (int) $this->storeManager->getStore($storeId)->getWebsiteId();
11311058
$groupCollection = $this->groupCollection;
11321059
if (!is_null($currentCustomerGroupId)) {
@@ -1140,7 +1067,7 @@ public function getSortingIndices(
11401067
}
11411068
}
11421069
// Regular pricing
1143-
} elseif ($attr['attribute'] === self::SORT_ATTRIBUTE_PRICE) {
1070+
} elseif ($attr[ReplicaManagerInterface::SORT_KEY_ATTRIBUTE_NAME] === ReplicaManagerInterface::SORT_ATTRIBUTE_PRICE) {
11441071
$indexName = $originalIndexName . '_' . $attr['attribute'] . '_' . 'default' . '_' . $attr['sort'];
11451072
$sortAttribute = $attr['attribute'] . '.' . $currency . '.' . 'default';
11461073
// All other sort attributes
@@ -1159,7 +1086,7 @@ public function getSortingIndices(
11591086
$attrsToReturn = [];
11601087

11611088
foreach ($attrs as $attr) {
1162-
if ($attr['attribute'] == self::SORT_ATTRIBUTE_PRICE
1089+
if ($attr[ReplicaManagerInterface::SORT_KEY_ATTRIBUTE_NAME] == ReplicaManagerInterface::SORT_ATTRIBUTE_PRICE
11631090
&& count($attributesToAdd)
11641091
&& isset($attributesToAdd[$attr['sort']])) {
11651092
$attrsToReturn = array_merge($attrsToReturn, $attributesToAdd[$attr['sort']]);
@@ -1177,26 +1104,42 @@ public function getSortingIndices(
11771104

11781105
/***
11791106
* @param $storeId
1180-
* @return array|bool|float|int|mixed|string|null
1107+
* @return array<string,<array<string, mixed>>>
11811108
*/
1182-
public function getSorting($storeId = null)
1109+
public function getSorting($storeId = null): array
11831110
{
11841111
return $this->unserialize($this->getRawSortingValue($storeId));
11851112
}
11861113

11871114
/**
1188-
* @param $storeId
1189-
* @return mixed
1115+
* @param int|null $storeId
1116+
* @return string
11901117
*/
1191-
public function getRawSortingValue($storeId = null)
1118+
public function getRawSortingValue(?int $storeId = null): string
11921119
{
1193-
return $this->configInterface->getValue(
1120+
return (string) $this->configInterface->getValue(
11941121
self::SORTING_INDICES,
11951122
ScopeInterface::SCOPE_STORE,
11961123
$storeId
11971124
);
11981125
}
11991126

1127+
/**
1128+
* @param array $sorting
1129+
* @param string|null $scope
1130+
* @param int|null $scopeId
1131+
* @return void
1132+
*/
1133+
public function setSorting(array $sorting, ?string $scope = null, ?int $scopeId = null): void
1134+
{
1135+
$this->configWriter->save(
1136+
self::SORTING_INDICES,
1137+
$this->serialize($sorting),
1138+
$scope,
1139+
$scopeId
1140+
);
1141+
}
1142+
12001143
/**
12011144
* @param $storeId
12021145
* @return string
@@ -1213,11 +1156,21 @@ public function getCurrencyCode($storeId = null)
12131156
* @param $storeId
12141157
* @return bool
12151158
*/
1216-
public function isCustomerGroupsEnabled($storeId = null)
1159+
public function isCustomerGroupsEnabled($storeId = null): bool
12171160
{
12181161
return $this->configInterface->isSetFlag(self::CUSTOMER_GROUPS_ENABLE, ScopeInterface::SCOPE_STORE, $storeId);
12191162
}
12201163

1164+
public function setCustomerGroupsEnabled(bool $val, ?string $scope = null, ?int $scopeId = null): void
1165+
{
1166+
$this->configWriter->save(
1167+
self::CUSTOMER_GROUPS_ENABLE,
1168+
$val ? '1' : '0',
1169+
$scope,
1170+
$scopeId
1171+
);
1172+
}
1173+
12211174
/**
12221175
* @param $storeId
12231176
* @return bool
@@ -1869,7 +1822,7 @@ public function useVirtualReplica(?int $storeId = null): bool
18691822
return (bool) count(array_filter(
18701823
$this->getSorting($storeId),
18711824
function ($sort) {
1872-
return $sort[ReplicaManager::SORT_KEY_VIRTUAL_REPLICA];
1825+
return $sort[ReplicaManagerInterface::SORT_KEY_VIRTUAL_REPLICA];
18731826
}
18741827
));
18751828
}

0 commit comments

Comments
 (0)