diff --git a/config/services/synchronizer.xml b/config/services/synchronizer.xml index 2956669..5352ab1 100644 --- a/config/services/synchronizer.xml +++ b/config/services/synchronizer.xml @@ -13,6 +13,7 @@ + %commerce_weavers_sylius_also_bought.batch_size_limit% diff --git a/docs/installation.md b/docs/installation.md index ac3046a..0eca3eb 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -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 diff --git a/src/DependencyInjection/CommerceWeaversSyliusAlsoBoughtExtension.php b/src/DependencyInjection/CommerceWeaversSyliusAlsoBoughtExtension.php index d94501a..950bdf2 100644 --- a/src/DependencyInjection/CommerceWeaversSyliusAlsoBoughtExtension.php +++ b/src/DependencyInjection/CommerceWeaversSyliusAlsoBoughtExtension.php @@ -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); } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 7e45c7a..99812df 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -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() ; } diff --git a/src/Provider/PlacedOrdersProvider.php b/src/Provider/PlacedOrdersProvider.php index 524d1bf..7820875 100644 --- a/src/Provider/PlacedOrdersProvider.php +++ b/src/Provider/PlacedOrdersProvider.php @@ -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() ; diff --git a/src/Provider/PlacedOrdersProviderInterface.php b/src/Provider/PlacedOrdersProviderInterface.php index 687c3a8..c525087 100644 --- a/src/Provider/PlacedOrdersProviderInterface.php +++ b/src/Provider/PlacedOrdersProviderInterface.php @@ -9,5 +9,5 @@ interface PlacedOrdersProviderInterface { /** @return OrderInterface[] */ - public function getSince(\DateTimeInterface $since): array; + public function getSince(\DateTimeInterface $since, int $limit, int $offset): array; } diff --git a/src/Synchronizer/FrequentlyBoughtTogetherProductsSynchronizer.php b/src/Synchronizer/FrequentlyBoughtTogetherProductsSynchronizer.php index 0be8a2a..e7bb7d1 100644 --- a/src/Synchronizer/FrequentlyBoughtTogetherProductsSynchronizer.php +++ b/src/Synchronizer/FrequentlyBoughtTogetherProductsSynchronizer.php @@ -8,6 +8,7 @@ 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 { @@ -15,13 +16,38 @@ 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> + */ + private function getAffectedProducts(array $orders): array + { $affectedProducts = []; foreach ($orders as $order) { @@ -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; } } diff --git a/tests/Unit/DependencyInjection/ConfigurationTest.php b/tests/Unit/DependencyInjection/ConfigurationTest.php index 1ebaa81..fc2399d 100644 --- a/tests/Unit/DependencyInjection/ConfigurationTest.php +++ b/tests/Unit/DependencyInjection/ConfigurationTest.php @@ -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 { diff --git a/tests/Unit/Provider/PlacedOrdersProviderTest.php b/tests/Unit/Provider/PlacedOrdersProviderTest.php index 7333276..26750b5 100644 --- a/tests/Unit/Provider/PlacedOrdersProviderTest.php +++ b/tests/Unit/Provider/PlacedOrdersProviderTest.php @@ -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) ); } } diff --git a/tests/Unit/Synchronizer/FrequentlyBoughtTogetherProductsSynchronizerTest.php b/tests/Unit/Synchronizer/FrequentlyBoughtTogetherProductsSynchronizerTest.php index cb9b899..f99dc1f 100644 --- a/tests/Unit/Synchronizer/FrequentlyBoughtTogetherProductsSynchronizerTest.php +++ b/tests/Unit/Synchronizer/FrequentlyBoughtTogetherProductsSynchronizerTest.php @@ -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')), ); }