Skip to content

Commit 67b84d0

Browse files
authored
Merge pull request #74 from Setono/fix-calculator
Fixing bug in ProductVariantPricesCalculator
2 parents 78dcdcd + 985ff74 commit 67b84d0

File tree

2 files changed

+82
-10
lines changed

2 files changed

+82
-10
lines changed

src/Calculator/ProductVariantPricesCalculator.php

+20-10
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ public function __construct(private readonly RuntimePromotionsApplicatorInterfac
2727

2828
public function calculate(ProductVariantInterface $productVariant, array $context): int
2929
{
30-
$hash = spl_object_hash($productVariant);
31-
if (!isset($this->computedPriceCache[$hash])) {
32-
$this->computedPriceCache[$hash] = $this->getPrice($productVariant, $context);
30+
$cacheKey = self::generateCacheKey($productVariant, $context);
31+
if (!array_key_exists($cacheKey, $this->computedPriceCache)) {
32+
$this->computedPriceCache[$cacheKey] = $this->getPrice($productVariant, $context);
3333
}
3434

35-
return $this->computedPriceCache[$hash];
35+
return $this->computedPriceCache[$cacheKey];
3636
}
3737

3838
public function calculateOriginal(ProductVariantInterface $productVariant, array $context): int
@@ -57,15 +57,14 @@ private function getPrice(ProductVariantInterface $productVariant, array $contex
5757
}
5858

5959
/**
60-
* @psalm-assert ChannelInterface $context['channel']
61-
*
6260
* @return array{price: int, originalPrice: int, minimumPrice: int}
6361
*/
6462
private function getPersistedPrices(ProductVariantInterface $productVariant, array $context): array
6563
{
66-
$hash = spl_object_hash($productVariant);
64+
$cacheKey = self::generateCacheKey($productVariant, $context);
6765

68-
if (!isset($this->persistedPricesCache[$hash])) {
66+
if (!isset($this->persistedPricesCache[$cacheKey])) {
67+
// todo remove these assertions when this issue is fixed: https://github.com/vimeo/psalm/issues/11248
6968
Assert::keyExists($context, 'channel');
7069
Assert::isInstanceOf($context['channel'], ChannelInterface::class);
7170

@@ -80,14 +79,14 @@ private function getPersistedPrices(ProductVariantInterface $productVariant, arr
8079
throw MissingChannelConfigurationException::createForProductVariantChannelPricing($productVariant, $context['channel']);
8180
}
8281

83-
$this->persistedPricesCache[$hash] = [
82+
$this->persistedPricesCache[$cacheKey] = [
8483
'price' => $price,
8584
'originalPrice' => $channelPricing->getOriginalPrice() ?? $price,
8685
'minimumPrice' => self::getMinimumPrice($channelPricing),
8786
];
8887
}
8988

90-
return $this->persistedPricesCache[$hash];
89+
return $this->persistedPricesCache[$cacheKey];
9190
}
9291

9392
private static function getMinimumPrice(ChannelPricingInterface $channelPricing): int
@@ -104,4 +103,15 @@ private static function getMinimumPrice(ChannelPricingInterface $channelPricing)
104103

105104
return $minimumPrice;
106105
}
106+
107+
/**
108+
* @psalm-assert ChannelInterface $context['channel']
109+
*/
110+
private static function generateCacheKey(ProductVariantInterface $productVariant, array $context): string
111+
{
112+
Assert::keyExists($context, 'channel');
113+
Assert::isInstanceOf($context['channel'], ChannelInterface::class);
114+
115+
return sprintf('%s%s', (string) $context['channel']->getCode(), (string) $productVariant->getCode());
116+
}
107117
}

tests/Calculator/ProductVariantPricesCalculatorTest.php

+62
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
use Setono\SyliusCatalogPromotionPlugin\Calculator\ProductVariantPricesCalculator;
1111
use Setono\SyliusCatalogPromotionPlugin\Model\ProductInterface;
1212
use Sylius\Component\Core\Model\Channel;
13+
use Sylius\Component\Core\Model\ChannelInterface;
14+
use Sylius\Component\Core\Model\ChannelPricing;
1315
use Sylius\Component\Core\Model\ChannelPricingInterface;
1416
use Sylius\Component\Core\Model\ProductVariantInterface;
1517

@@ -33,6 +35,7 @@ public function it_delegates_to_decorated(): void
3335
$product->hasPreQualifiedCatalogPromotions()->willReturn(false);
3436

3537
$productVariant = $this->prophesize(ProductVariantInterface::class);
38+
$productVariant->getCode()->willReturn('product_variant_1');
3639
$productVariant->getChannelPricingForChannel($channel)->willReturn($channelPricing->reveal());
3740
$productVariant->getProduct()->willReturn($product->reveal());
3841

@@ -65,6 +68,7 @@ public function it_calculates_catalog_promotion(): void
6568
$product->getPreQualifiedCatalogPromotions()->willReturn(['promo1', 'promo2']);
6669

6770
$productVariant = $this->prophesize(ProductVariantInterface::class);
71+
$productVariant->getCode()->willReturn('product_variant_1');
6872
$productVariant->getChannelPricingForChannel($channel)->willReturn($channelPricing->reveal());
6973
$productVariant->getProduct()->willReturn($product->reveal());
7074

@@ -99,6 +103,7 @@ public function it_respects_minimum_price(): void
99103
$product->getPreQualifiedCatalogPromotions()->willReturn(['promo1']);
100104

101105
$productVariant = $this->prophesize(ProductVariantInterface::class);
106+
$productVariant->getCode()->willReturn('product_variant_1');
102107
$productVariant->getChannelPricingForChannel($channel)->willReturn($channelPricing->reveal());
103108
$productVariant->getProduct()->willReturn($product->reveal());
104109

@@ -111,4 +116,61 @@ public function it_respects_minimum_price(): void
111116
'channel' => $channel,
112117
]));
113118
}
119+
120+
/**
121+
* @test
122+
*/
123+
public function it_works_with_different_channels(): void
124+
{
125+
$channel1 = self::createChannel('channel1');
126+
$channel2 = self::createChannel('channel2');
127+
128+
$channelPricing1 = self::createChannelPricing(100);
129+
$channelPricing2 = self::createChannelPricing(200);
130+
131+
$product = $this->prophesize(ProductInterface::class);
132+
$product->hasPreQualifiedCatalogPromotions()->willReturn(true);
133+
$product->getPreQualifiedCatalogPromotions()->willReturn(['promo1']);
134+
135+
$productVariant = $this->prophesize(ProductVariantInterface::class);
136+
$productVariant->getCode()->willReturn('product_variant_1');
137+
$productVariant->getChannelPricingForChannel($channel1)->willReturn($channelPricing1);
138+
$productVariant->getChannelPricingForChannel($channel2)->willReturn($channelPricing2);
139+
$productVariant->getProduct()->willReturn($product->reveal());
140+
141+
$runtimePromotionsApplicator = $this->prophesize(RuntimePromotionsApplicatorInterface::class);
142+
$runtimePromotionsApplicator->apply($product->reveal(), 100, 100)->willReturn(80);
143+
$runtimePromotionsApplicator->apply($product->reveal(), 200, 200)->willReturn(160);
144+
145+
$calculator = new ProductVariantPricesCalculator($runtimePromotionsApplicator->reveal());
146+
147+
$this->assertSame(80, $calculator->calculate($productVariant->reveal(), [
148+
'channel' => $channel1,
149+
]));
150+
151+
$this->assertSame(160, $calculator->calculate($productVariant->reveal(), [
152+
'channel' => $channel2,
153+
]));
154+
}
155+
156+
private static function createChannel(string $code): ChannelInterface
157+
{
158+
$channel = new Channel();
159+
$channel->setCode($code);
160+
161+
return $channel;
162+
}
163+
164+
private static function createChannelPricing(int $price, int $originalPrice = null, int $minimumPrice = null): ChannelPricingInterface
165+
{
166+
$channelPricing = new ChannelPricing();
167+
$channelPricing->setPrice($price);
168+
$channelPricing->setOriginalPrice($originalPrice);
169+
170+
if (method_exists($channelPricing, 'setMinimumPrice')) {
171+
$channelPricing->setMinimumPrice($minimumPrice);
172+
}
173+
174+
return $channelPricing;
175+
}
114176
}

0 commit comments

Comments
 (0)