Skip to content

Commit ede58d8

Browse files
authored
Merge pull request #75 from Setono/data-consistency
Ensure higher data consistency on pre qualified callouts
2 parents 67b84d0 + 27c2840 commit ede58d8

File tree

3 files changed

+64
-23
lines changed

3 files changed

+64
-23
lines changed

src/Message/CommandHandler/UpdateProductsHandler.php

+38-22
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Setono\SyliusCatalogPromotionPlugin\Checker\PreQualification\PreQualificationCheckerInterface;
1313
use Setono\SyliusCatalogPromotionPlugin\DataProvider\ProductDataProviderInterface;
1414
use Setono\SyliusCatalogPromotionPlugin\Message\Command\UpdateProducts;
15+
use Setono\SyliusCatalogPromotionPlugin\Model\CatalogPromotionInterface;
1516
use Setono\SyliusCatalogPromotionPlugin\Model\CatalogPromotionUpdateInterface;
1617
use Setono\SyliusCatalogPromotionPlugin\Repository\CatalogPromotionRepositoryInterface;
1718
use Setono\SyliusCatalogPromotionPlugin\Workflow\CatalogPromotionUpdateWorkflow;
@@ -23,9 +24,9 @@ final class UpdateProductsHandler
2324
use ORMTrait;
2425

2526
/**
26-
* A cache of catalog promotions telling if the code (the key of the array) exists (the value)
27+
* A cache of catalog promotions where the index is the code
2728
*
28-
* @var array<string, bool>
29+
* @var array<string, CatalogPromotionInterface|null>
2930
*/
3031
private array $catalogPromotions = [];
3132

@@ -52,19 +53,33 @@ public function __invoke(UpdateProducts $message): void
5253
$error = null;
5354

5455
try {
55-
$catalogPromotions = $this->catalogPromotionRepository->findForProcessing($message->catalogPromotions);
56+
$initialProcessableCatalogPromotions = $this->catalogPromotionRepository->findForProcessing($message->catalogPromotions);
5657

5758
foreach ($this->productDataProvider->getProducts($message->productIds) as $product) {
58-
// Remove the catalog promotions
59-
// - we are processing and
60-
// - the ones that doesn't exist anymore
61-
// from the pre-qualified catalog promotions before we start the actual processing
62-
$preQualifiedCatalogPromotions = array_filter(
63-
$product->getPreQualifiedCatalogPromotions(),
64-
fn (string $code) => !in_array($code, $message->catalogPromotions, true) && $this->catalogPromotionExists($code),
65-
);
66-
67-
foreach ($catalogPromotions as $catalogPromotion) {
59+
$processableCatalogPromotions = $initialProcessableCatalogPromotions;
60+
61+
/**
62+
* This part of the code ensures that, while updating a specified set of catalog promotions,
63+
* we also update the catalog promotions that are already applied to the product.
64+
* While this behavior isn't strictly required as per the given task, it is implemented to achieve a
65+
* higher level of data consistency.
66+
*/
67+
if ([] !== $message->catalogPromotions) {
68+
$filteredPreQualifiedCatalogPromotions = array_filter(
69+
$product->getPreQualifiedCatalogPromotions(),
70+
static fn (string $code): bool => !in_array($code, $message->catalogPromotions, true),
71+
);
72+
73+
foreach ($filteredPreQualifiedCatalogPromotions as $preQualifiedCatalogPromotion) {
74+
$catalogPromotion = $this->getCatalogPromotion($preQualifiedCatalogPromotion);
75+
if (null !== $catalogPromotion) {
76+
$processableCatalogPromotions[] = $catalogPromotion;
77+
}
78+
}
79+
}
80+
81+
$preQualifiedCatalogPromotions = [];
82+
foreach ($processableCatalogPromotions as $catalogPromotion) {
6883
if ($this->preQualificationChecker->isPreQualified($product, $catalogPromotion)) {
6984
$preQualifiedCatalogPromotions[] = (string) $catalogPromotion->getCode();
7085
}
@@ -114,6 +129,15 @@ public function __invoke(UpdateProducts $message): void
114129
}
115130
}
116131

132+
private function getCatalogPromotion(string $code): ?CatalogPromotionInterface
133+
{
134+
if (!array_key_exists($code, $this->catalogPromotions)) {
135+
$this->catalogPromotions[$code] = $this->catalogPromotionRepository->findOneForProcessing($code);
136+
}
137+
138+
return $this->catalogPromotions[$code];
139+
}
140+
117141
private function getCatalogPromotionUpdate(int $id): CatalogPromotionUpdateInterface
118142
{
119143
$catalogPromotionUpdate = $this->getManager($this->catalogPromotionUpdateClass)->find($this->catalogPromotionUpdateClass, $id);
@@ -122,17 +146,9 @@ private function getCatalogPromotionUpdate(int $id): CatalogPromotionUpdateInter
122146
throw new UnrecoverableMessageHandlingException(sprintf('Catalog promotion update with id %s not found', $id));
123147
}
124148

149+
// Because the unit of work may have been cleared, we refresh the entity
125150
$this->getManager($this->catalogPromotionUpdateClass)->refresh($catalogPromotionUpdate);
126151

127152
return $catalogPromotionUpdate;
128153
}
129-
130-
private function catalogPromotionExists(string $code): bool
131-
{
132-
if (!array_key_exists($code, $this->catalogPromotions)) {
133-
$this->catalogPromotions[$code] = null !== $this->catalogPromotionRepository->findOneBy(['code' => $code]);
134-
}
135-
136-
return $this->catalogPromotions[$code];
137-
}
138154
}

src/Repository/CatalogPromotionRepository.php

+24-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Setono\SyliusCatalogPromotionPlugin\Repository;
66

7+
use Doctrine\ORM\QueryBuilder;
78
use Setono\SyliusCatalogPromotionPlugin\Model\CatalogPromotionInterface;
89
use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository;
910
use Webmozart\Assert\Assert;
@@ -12,7 +13,8 @@ class CatalogPromotionRepository extends EntityRepository implements CatalogProm
1213
{
1314
public function findForProcessing(array $catalogPromotions = []): array
1415
{
15-
$qb = $this->createQueryBuilder('o');
16+
$qb = $this->createProcessingQueryBuilder();
17+
1618
if ([] !== $catalogPromotions) {
1719
$qb->andWhere('o.code IN (:catalogPromotions)')
1820
->setParameter('catalogPromotions', $catalogPromotions)
@@ -27,11 +29,32 @@ public function findForProcessing(array $catalogPromotions = []): array
2729
return $objs;
2830
}
2931

32+
public function findOneForProcessing(string $code): ?CatalogPromotionInterface
33+
{
34+
$obj = $this->createProcessingQueryBuilder()
35+
->andWhere('o.code = :code')
36+
->setParameter('code', $code)
37+
->getQuery()
38+
->getOneOrNullResult()
39+
;
40+
Assert::nullOrIsInstanceOf($obj, CatalogPromotionInterface::class);
41+
42+
return $obj;
43+
}
44+
3045
public function findOneByCode(string $code): ?CatalogPromotionInterface
3146
{
3247
$obj = $this->findOneBy(['code' => $code]);
3348
Assert::nullOrIsInstanceOf($obj, CatalogPromotionInterface::class);
3449

3550
return $obj;
3651
}
52+
53+
private function createProcessingQueryBuilder(): QueryBuilder
54+
{
55+
return $this->createQueryBuilder('o')
56+
->select('o, r')
57+
->leftJoin('o.rules', 'r') // important to use left join because we might have 0 rules on a catalog promotion
58+
;
59+
}
3760
}

src/Repository/CatalogPromotionRepositoryInterface.php

+2
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@ interface CatalogPromotionRepositoryInterface extends RepositoryInterface
1616
*/
1717
public function findForProcessing(array $catalogPromotions = []): array;
1818

19+
public function findOneForProcessing(string $code): ?CatalogPromotionInterface;
20+
1921
public function findOneByCode(string $code): ?CatalogPromotionInterface;
2022
}

0 commit comments

Comments
 (0)