Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Synchronize frequently bought together products in batches #31

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/services/synchronizer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<argument type="service" id="CommerceWeavers\SyliusAlsoBoughtPlugin\Provider\PlacedOrdersProviderInterface" />
<argument type="service" id="CommerceWeavers\SyliusAlsoBoughtPlugin\Mapper\BoughtTogetherProductsMapperInterface" />
<argument type="service" id="CommerceWeavers\SyliusAlsoBoughtPlugin\Saver\BoughtTogetherProductsInfoSaverInterface" />
<argument>%commerce_weavers_sylius_also_bought.batch_size_limit%</argument>
</service>
</services>
</container>
1 change: 1 addition & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ imports:

commerce_weavers_sylius_also_bought:
number_of_products_to_associate: 10 # default value
batch_size_limit: 1000 # default value
```

### 4. Add trait to enhance Sylius Product model
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public function prepend(ContainerBuilder $container): void
$config['number_of_products_to_associate'],
);

$container->setParameter(
'commerce_weavers_sylius_also_bought.batch_size_limit',
$config['batch_size_limit'],
);

$this->prependDoctrineMigrations($container);
$this->prependDoctrineMapping($container);
}
Expand Down
1 change: 1 addition & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ private function addConfiguration(ArrayNodeDefinition $node): void
$node
->children()
->integerNode('number_of_products_to_associate')->defaultValue(10)->end()
->integerNode('batch_size_limit')->defaultValue(1000)->end()
->end()
;
}
Expand Down
4 changes: 3 additions & 1 deletion src/Provider/PlacedOrdersProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ public function __construct(private OrderRepositoryInterface $orderRepository)
{
}

public function getSince(\DateTimeInterface $since): array
public function getSince(\DateTimeInterface $since, int $limit, int $offset): array
{
/** @var OrderInterface[] $result */
$result = $this->orderRepository
->createListQueryBuilder()
->andWhere('o.checkoutCompletedAt >= :date')
->setParameter('date', $since)
->setMaxResults($limit)
->setFirstResult($offset)
->getQuery()
->getResult()
;
Expand Down
2 changes: 1 addition & 1 deletion src/Provider/PlacedOrdersProviderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
interface PlacedOrdersProviderInterface
{
/** @return OrderInterface[] */
public function getSince(\DateTimeInterface $since): array;
public function getSince(\DateTimeInterface $since, int $limit, int $offset): array;
}
34 changes: 28 additions & 6 deletions src/Synchronizer/FrequentlyBoughtTogetherProductsSynchronizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,46 @@
use CommerceWeavers\SyliusAlsoBoughtPlugin\Model\SynchronizationResult;
use CommerceWeavers\SyliusAlsoBoughtPlugin\Provider\PlacedOrdersProviderInterface;
use CommerceWeavers\SyliusAlsoBoughtPlugin\Saver\BoughtTogetherProductsInfoSaverInterface;
use Sylius\Component\Core\Model\OrderInterface;

final class FrequentlyBoughtTogetherProductsSynchronizer implements FrequentlyBoughtTogetherProductsSynchronizerInterface
{
public function __construct(
private PlacedOrdersProviderInterface $placedOrdersProvider,
private BoughtTogetherProductsMapperInterface $boughtTogetherProductsMapper,
private BoughtTogetherProductsInfoSaverInterface $boughtTogetherProductsInfoSaver,
private int $batchSize = 1000,
) {
}

public function synchronize(\DateTimeInterface $since): SynchronizationResult
{
$orders = $this->placedOrdersProvider->getSince($since);
$offset = 0;

$numberOfOrders = 0;
$affectedProducts = [];

while (($orders = $this->placedOrdersProvider->getSince($since, $this->batchSize, $offset)) !== []) {
$affectedProducts = array_merge($affectedProducts, $this->getAffectedProducts($orders));
$numberOfOrders += count($orders);

$offset += $this->batchSize;
}

foreach ($affectedProducts as $productCode => $products) {
$this->boughtTogetherProductsInfoSaver->save($productCode, $products);
}

return new SynchronizationResult($numberOfOrders, array_keys($affectedProducts));
}

/**
* @param OrderInterface[] $orders
*
* @return array<string, array<string>>
*/
private function getAffectedProducts(array $orders): array
{
$affectedProducts = [];

foreach ($orders as $order) {
Expand All @@ -32,10 +58,6 @@ public function synchronize(\DateTimeInterface $since): SynchronizationResult
}
}

foreach ($affectedProducts as $productCode => $products) {
$this->boughtTogetherProductsInfoSaver->save($productCode, $products);
}

return new SynchronizationResult(count($orders), array_keys($affectedProducts));
return $affectedProducts;
}
}
19 changes: 19 additions & 0 deletions tests/Unit/DependencyInjection/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,25 @@ public function it_allows_to_define_number_of_products_to_associate(): void
'number_of_products_to_associate'
);
}
/** @test */
public function it_defines_batch_size_limit_by_default(): void
{
$this->assertProcessedConfigurationEquals(
[],
['batch_size_limit' => 1000],
'batch_size_limit'
);
}

/** @test */
public function it_allows_to_define_batch_size_limit(): void
{
$this->assertProcessedConfigurationEquals(
[['batch_size_limit' => 5]],
['batch_size_limit' => 5],
'batch_size_limit'
);
}

protected function getConfiguration(): Configuration
{
Expand Down
4 changes: 3 additions & 1 deletion tests/Unit/Provider/PlacedOrdersProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ public function testItProvidesOrdersPlacedSinceSpecificDate(): void

$orderRepository->createListQueryBuilder()->willReturn($queryBuilder->reveal())->shouldBeCalled();
$queryBuilder->andWhere('o.checkoutCompletedAt >= :date')->willReturn($queryBuilder->reveal())->shouldBeCalled();
$queryBuilder->setMaxResults(10)->willReturn($queryBuilder->reveal())->shouldBeCalled();
$queryBuilder->setFirstResult(0)->willReturn($queryBuilder->reveal())->shouldBeCalled();
$queryBuilder->setParameter('date', $since)->willReturn($queryBuilder->reveal())->shouldBeCalled();
$queryBuilder->getQuery()->willReturn($query->reveal())->shouldBeCalled();
$query->getResult()->willReturn([$firstOrder, $secondOrder]);

self::assertSame(
[$firstOrder, $secondOrder],
(new PlacedOrdersProvider($orderRepository->reveal()))->getSince($since)
(new PlacedOrdersProvider($orderRepository->reveal()))->getSince($since, 10, 0)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,41 @@ public function testProductsSynchronization(): void

$firstOrder = new Order();
$secondOrder = new Order();
$thirdOrder = new Order();

$synchronizer = new FrequentlyBoughtTogetherProductsSynchronizer(
$placedOrdersProvider->reveal(),
$productsMapper->reveal(),
$boughtTogetherProductsInfoSaver->reveal(),
2,
);

$placedOrdersProvider
->getSince(new \DateTimeImmutable('2020-01-01'))
->getSince(new \DateTimeImmutable('2020-01-01'), 2, 0)
->willReturn([$firstOrder, $secondOrder])
;

$placedOrdersProvider
->getSince(new \DateTimeImmutable('2020-01-01'), 2, 2)
->willReturn([$thirdOrder])
;

$placedOrdersProvider
->getSince(new \DateTimeImmutable('2020-01-01'), 2, 4)
->willReturn([])
;

$productsMapper->map($firstOrder)->willReturn(['P10273' => ['P12182', 'P12183'], 'P12182' => ['P10273', 'P12183'], 'P12183' => ['P10273', 'P12182']]);
$productsMapper->map($secondOrder)->willReturn(['P12182' => ['P10273'], 'P10273' => ['P12182']]);
$productsMapper->map($thirdOrder)->willReturn(['P13757' => ['P10273']]);

$boughtTogetherProductsInfoSaver->save('P10273', ['P12182', 'P12183', 'P12182'])->shouldBeCalled();
$boughtTogetherProductsInfoSaver->save('P12182', ['P10273', 'P12183', 'P10273'])->shouldBeCalled();
$boughtTogetherProductsInfoSaver->save('P12183', ['P10273', 'P12182'])->shouldBeCalled();
$boughtTogetherProductsInfoSaver->save('P13757', ['P10273'])->shouldBeCalled();

self::assertEquals(
new SynchronizationResult(2, ['P10273', 'P12182', 'P12183']),
new SynchronizationResult(3, ['P10273', 'P12182', 'P12183', 'P13757']),
$synchronizer->synchronize(new \DateTimeImmutable('2020-01-01')),
);
}
Expand Down
Loading