diff --git a/src/Sylius/Bundle/CoreBundle/Doctrine/ORM/ProductRepository.php b/src/Sylius/Bundle/CoreBundle/Doctrine/ORM/ProductRepository.php index 28cdd88771..ccaff0047a 100644 --- a/src/Sylius/Bundle/CoreBundle/Doctrine/ORM/ProductRepository.php +++ b/src/Sylius/Bundle/CoreBundle/Doctrine/ORM/ProductRepository.php @@ -247,11 +247,16 @@ class ProductRepository extends BaseProductRepository implements ProductReposito public function findByTaxon(TaxonInterface $taxon): array { - return $this - ->createQueryBuilder('product') - ->distinct() - ->innerJoin('product.productTaxons', 'productTaxon') + $subQuery = $this->createQueryBuilder('sub') + ->select('sub.id') + ->innerJoin('sub.productTaxons', 'productTaxon') ->andWhere('productTaxon.taxon = :taxon') + ; + + $queryBuilder = $this->createQueryBuilder('product'); + + return $queryBuilder + ->andWhere($queryBuilder->expr()->in('product.id', $subQuery->getDQL())) ->setParameter('taxon', $taxon) ->getQuery() ->getResult() diff --git a/tests/DataFixtures/ORM/resources/product_repository_find_by_taxon.yml b/tests/DataFixtures/ORM/resources/product_repository_find_by_taxon.yml new file mode 100644 index 0000000000..aca7f3e7f0 --- /dev/null +++ b/tests/DataFixtures/ORM/resources/product_repository_find_by_taxon.yml @@ -0,0 +1,37 @@ +Sylius\Component\Core\Model\Taxon: + find_by_taxon_mugs: + code: "find_by_taxon_mugs" + find_by_taxon_books: + code: "find_by_taxon_books" + +Sylius\Component\Core\Model\Product: + find_by_taxon_product_mugs_only: + currentLocale: "en_US" + fallbackLocale: "en_US" + code: "FIND_BY_TAXON_MUGS_ONLY" + find_by_taxon_product_mugs_and_books: + currentLocale: "en_US" + fallbackLocale: "en_US" + code: "FIND_BY_TAXON_MUGS_AND_BOOKS" + find_by_taxon_product_books_only: + currentLocale: "en_US" + fallbackLocale: "en_US" + code: "FIND_BY_TAXON_BOOKS_ONLY" + +Sylius\Component\Core\Model\ProductTaxon: + find_by_taxon_pt_mugs_only: + product: "@find_by_taxon_product_mugs_only" + taxon: "@find_by_taxon_mugs" + position: 0 + find_by_taxon_pt_mugs_and_books_mugs: + product: "@find_by_taxon_product_mugs_and_books" + taxon: "@find_by_taxon_mugs" + position: 1 + find_by_taxon_pt_mugs_and_books_books: + product: "@find_by_taxon_product_mugs_and_books" + taxon: "@find_by_taxon_books" + position: 0 + find_by_taxon_pt_books_only: + product: "@find_by_taxon_product_books_only" + taxon: "@find_by_taxon_books" + position: 1 diff --git a/tests/Functional/Repository/ProductRepositoryTest.php b/tests/Functional/Repository/ProductRepositoryTest.php new file mode 100644 index 0000000000..7a75a4f666 --- /dev/null +++ b/tests/Functional/Repository/ProductRepositoryTest.php @@ -0,0 +1,130 @@ + */ + private ProductRepositoryInterface $productRepository; + + /** @var TaxonRepositoryInterface */ + private TaxonRepositoryInterface $taxonRepository; + + protected function setUp(): void + { + parent::setUp(); + + $this->loadFixtures(); + + /** @var EntityManagerInterface $entityManager */ + $entityManager = self::getContainer()->get('doctrine.orm.entity_manager'); + $entityManager->clear(); + + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = self::getContainer()->get('sylius.repository.product'); + $this->productRepository = $productRepository; + + /** @var TaxonRepositoryInterface $taxonRepository */ + $taxonRepository = self::getContainer()->get('sylius.repository.taxon'); + $this->taxonRepository = $taxonRepository; + } + + #[Test] + public function it_finds_products_belonging_to_a_taxon(): void + { + /** @var TaxonInterface $mugsTaxon */ + $mugsTaxon = $this->taxonRepository->findOneBy(['code' => 'find_by_taxon_mugs']); + + $products = $this->productRepository->findByTaxon($mugsTaxon); + + self::assertCount(2, $products); + self::assertEqualsCanonicalizing( + ['FIND_BY_TAXON_MUGS_ONLY', 'FIND_BY_TAXON_MUGS_AND_BOOKS'], + $this->getProductCodes($products), + ); + } + + #[Test] + public function it_does_not_return_products_from_other_taxons(): void + { + /** @var TaxonInterface $booksTaxon */ + $booksTaxon = $this->taxonRepository->findOneBy(['code' => 'find_by_taxon_books']); + + $products = $this->productRepository->findByTaxon($booksTaxon); + + self::assertCount(2, $products); + self::assertEqualsCanonicalizing( + ['FIND_BY_TAXON_MUGS_AND_BOOKS', 'FIND_BY_TAXON_BOOKS_ONLY'], + $this->getProductCodes($products), + ); + } + + #[Test] + public function it_returns_the_full_product_taxons_collection_not_only_filtered_ones(): void + { + /** @var TaxonInterface $mugsTaxon */ + $mugsTaxon = $this->taxonRepository->findOneBy(['code' => 'find_by_taxon_mugs']); + + $products = $this->productRepository->findByTaxon($mugsTaxon); + + $productInBothTaxons = null; + foreach ($products as $product) { + if ($product->getCode() === 'FIND_BY_TAXON_MUGS_AND_BOOKS') { + $productInBothTaxons = $product; + + break; + } + } + + self::assertNotNull($productInBothTaxons); + self::assertCount( + 2, + $productInBothTaxons->getProductTaxons(), + 'Product assigned to two taxons must expose both in its productTaxons collection, not just the filtered one.', + ); + } + + /** + * @param ProductInterface[] $products + * + * @return string[] + */ + private function getProductCodes(array $products): array + { + return array_map( + static fn (ProductInterface $product): string => (string) $product->getCode(), + $products, + ); + } + + private function loadFixtures(): void + { + /** @var LoaderInterface $fixtureLoader */ + $fixtureLoader = self::getContainer()->get('fidry_alice_data_fixtures.loader.doctrine'); + + $fixtureLoader->load([ + __DIR__ . '/../../DataFixtures/ORM/resources/product_repository_find_by_taxon.yml', + ], [], [], PurgeMode::createDeleteMode()); + } +}