diff --git a/spec/DataGenerator/Factory/Entity/ProductFactorySpec.php b/spec/DataGenerator/Factory/Entity/ProductFactorySpec.php
index cf0d17b9..a8fb88ed 100644
--- a/spec/DataGenerator/Factory/Entity/ProductFactorySpec.php
+++ b/spec/DataGenerator/Factory/Entity/ProductFactorySpec.php
@@ -17,6 +17,7 @@
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
+use Sylius\Component\Core\Model\TaxonInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
final class ProductFactorySpec extends ObjectBehavior
@@ -36,6 +37,7 @@ public function it_creates_product(
ProductVariantInterface $variant,
ChannelInterface $channel,
ProductInterface $product,
+ TaxonInterface $taxon,
): void {
$productFactory->createNew()->willReturn($product);
@@ -48,6 +50,7 @@ public function it_creates_product(
$variant,
$channel,
new DateTime(),
+ $taxon,
)
->shouldReturn($product);
}
diff --git a/spec/DataGenerator/Generator/Bulk/Collection/ProductTaxonCollectionBulkGeneratorSpec.php b/spec/DataGenerator/Generator/Bulk/Collection/ProductTaxonCollectionBulkGeneratorSpec.php
index a5fa718d..70160f79 100644
--- a/spec/DataGenerator/Generator/Bulk/Collection/ProductTaxonCollectionBulkGeneratorSpec.php
+++ b/spec/DataGenerator/Generator/Bulk/Collection/ProductTaxonCollectionBulkGeneratorSpec.php
@@ -47,6 +47,7 @@ public function it_generates_product_taxon_collection(
$offset = 0;
$taxons = [$taxon1, $taxon2];
+ $context->getQuantity()->willReturn(100);
$context->getIO()->willReturn($io);
$taxonRepository->getEntityCount()->willReturn($entityCount);
@@ -70,6 +71,7 @@ public function it_does_nothing_if_no_taxons_found(
$limit = 100;
$offset = 0;
+ $context->getQuantity()->willReturn(100);
$context->getIO()->willReturn($io);
$taxonRepository->getEntityCount()->willReturn($entityCount);
@@ -78,6 +80,13 @@ public function it_does_nothing_if_no_taxons_found(
$this->generate($context);
}
+ public function it_does_nothing_if_quantity_equals_to_zero(ProductTaxonGeneratorContextInterface $context,): void
+ {
+ $context->getQuantity()->willReturn(0)->shouldBeCalled();
+
+ $this->generate($context);
+ }
+
public function it_throws_exception_on_invalid_context(ContextInterface $context): void
{
$this->shouldThrow(InvalidContextException::class)
diff --git a/spec/DataGenerator/Generator/Bulk/Collection/WishlistProductCollectionBulkGeneratorSpec.php b/spec/DataGenerator/Generator/Bulk/Collection/WishlistProductCollectionBulkGeneratorSpec.php
index 0970ceb7..87dc1e93 100644
--- a/spec/DataGenerator/Generator/Bulk/Collection/WishlistProductCollectionBulkGeneratorSpec.php
+++ b/spec/DataGenerator/Generator/Bulk/Collection/WishlistProductCollectionBulkGeneratorSpec.php
@@ -49,6 +49,7 @@ public function it_generates_wishlist_product_collection(
$offset = 0;
$wishlists = [$wishlist1, $wishlist2];
+ $context->getQuantity()->willReturn(100);
$context->getIO()->willReturn($io);
$context->getChannel()->willReturn($channel);
$wishlistRepository->getEntityCount($channel)->willReturn($entityCount);
@@ -74,6 +75,7 @@ public function it_does_nothing_if_no_wishlists_found(
$limit = 100;
$offset = 0;
+ $context->getQuantity()->willReturn(100);
$context->getIO()->willReturn($io);
$context->getChannel()->willReturn($channel);
$wishlistRepository->getEntityCount($channel)->willReturn($entityCount);
@@ -83,6 +85,13 @@ public function it_does_nothing_if_no_wishlists_found(
$this->generate($context);
}
+ public function it_does_nothing_if_quantity_equals_to_zero(WishlistProductGeneratorContextInterface $context): void
+ {
+ $context->getQuantity()->willReturn(0)->shouldBeCalled();
+
+ $this->generate($context);
+ }
+
public function it_throws_exception_on_invalid_context(ContextInterface $context): void
{
$this->shouldThrow(InvalidContextException::class)
diff --git a/spec/DataGenerator/Generator/Entity/ProductGeneratorSpec.php b/spec/DataGenerator/Generator/Entity/ProductGeneratorSpec.php
index 5e1a152a..6ec4afbd 100644
--- a/spec/DataGenerator/Generator/Entity/ProductGeneratorSpec.php
+++ b/spec/DataGenerator/Generator/Entity/ProductGeneratorSpec.php
@@ -12,6 +12,7 @@
use BitBag\SyliusVueStorefront2Plugin\DataGenerator\ContextModel\Generator\GeneratorContextInterface;
use BitBag\SyliusVueStorefront2Plugin\DataGenerator\ContextModel\Generator\ProductGeneratorContextInterface;
+use BitBag\SyliusVueStorefront2Plugin\DataGenerator\Doctrine\Repository\TaxonRepositoryInterface;
use BitBag\SyliusVueStorefront2Plugin\DataGenerator\Exception\InvalidContextException;
use BitBag\SyliusVueStorefront2Plugin\DataGenerator\Factory\Entity\ChannelPricingFactoryInterface;
use BitBag\SyliusVueStorefront2Plugin\DataGenerator\Factory\Entity\ProductFactoryInterface;
@@ -24,6 +25,7 @@
use Sylius\Component\Core\Model\ChannelPricingInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
+use Sylius\Component\Core\Model\TaxonInterface;
final class ProductGeneratorSpec extends ObjectBehavior
{
@@ -31,8 +33,9 @@ public function let(
ProductFactoryInterface $productFactory,
ProductVariantFactoryInterface $productVariantFactory,
ChannelPricingFactoryInterface $channelPricingFactory,
+ TaxonRepositoryInterface $taxonRepository,
): void {
- $this->beConstructedWith($productFactory, $productVariantFactory, $channelPricingFactory);
+ $this->beConstructedWith($productFactory, $productVariantFactory, $channelPricingFactory, $taxonRepository);
}
public function it_is_initializable(): void
@@ -49,6 +52,8 @@ public function it_generates_product(
ProductVariantInterface $productVariant,
ProductGeneratorContextInterface $context,
ProductInterface $product,
+ TaxonRepositoryInterface $taxonRepository,
+ TaxonInterface $taxon,
): void {
$context->getChannel()->willReturn($channel);
$channel->getCode()->willReturn(Argument::type('string'));
@@ -61,11 +66,12 @@ public function it_generates_product(
->create(
Argument::type('string'),
Argument::type('string'),
- $channelPricing->getWrappedObject()
+ $channelPricing
)
->willReturn($productVariant);
- $context->getChannel()->willReturn($channel->getWrappedObject());
+ $context->getChannel()->willReturn($channel);
+ $taxonRepository->getRandomTaxon()->willReturn($taxon);
$productFactory
->create(
@@ -73,9 +79,10 @@ public function it_generates_product(
Argument::type('string'),
Argument::type('string'),
Argument::type('string'),
- $productVariant->getWrappedObject(),
- $channel->getWrappedObject(),
+ $productVariant,
+ $channel,
Argument::type(DateTime::class),
+ $taxon,
)
->willReturn($product);
diff --git a/src/DataGenerator/ConsoleCommand/BulkDataGeneratorInterface.php b/src/DataGenerator/ConsoleCommand/BulkDataGeneratorInterface.php
index 16df4b03..e492953e 100644
--- a/src/DataGenerator/ConsoleCommand/BulkDataGeneratorInterface.php
+++ b/src/DataGenerator/ConsoleCommand/BulkDataGeneratorInterface.php
@@ -14,7 +14,7 @@ interface BulkDataGeneratorInterface
{
const DEFAULT_TAXONS_QTY = 5000;
- const DEFAULT_MAX_TAXON_LEVEL = 20;
+ const DEFAULT_MAX_TAXON_LEVEL = 14;
const DEFAULT_MAX_CHILDREN_PER_TAXON_LEVEL = 5;
diff --git a/src/DataGenerator/Doctrine/Repository/TaxonRepository.php b/src/DataGenerator/Doctrine/Repository/TaxonRepository.php
index 1f5e8828..e654625b 100644
--- a/src/DataGenerator/Doctrine/Repository/TaxonRepository.php
+++ b/src/DataGenerator/Doctrine/Repository/TaxonRepository.php
@@ -10,6 +10,7 @@
namespace BitBag\SyliusVueStorefront2Plugin\DataGenerator\Doctrine\Repository;
+use BitBag\SyliusVueStorefront2Plugin\DataGenerator\Exception\NoTaxonFoundException;
use Sylius\Bundle\TaxonomyBundle\Doctrine\ORM\TaxonRepository as BaseTaxonRepository;
use Sylius\Component\Core\Model\TaxonInterface;
@@ -63,8 +64,27 @@ public function findBatch(
public function getEntityCount(): int
{
$queryBuilder = $this->createQueryBuilder('taxon')
+ ->andWhere('taxon.enabled = true')
->select('COUNT(taxon)');
return (int)$queryBuilder->getQuery()->getSingleScalarResult();
}
+
+ public function getRandomTaxon(): TaxonInterface
+ {
+ $randomOffset = max(0, rand(0, $this->getEntityCount() - 1));
+
+ $result = $this->createQueryBuilder('taxon')
+ ->where('taxon.enabled = true')
+ ->setFirstResult($randomOffset)
+ ->setMaxResults(1)
+ ->getQuery()
+ ->getOneOrNullResult();
+
+ if ($result instanceof TaxonInterface) {
+ return $result;
+ }
+
+ throw new NoTaxonFoundException();
+ }
}
diff --git a/src/DataGenerator/Doctrine/Repository/TaxonRepositoryInterface.php b/src/DataGenerator/Doctrine/Repository/TaxonRepositoryInterface.php
index bcb87e59..c89294f0 100644
--- a/src/DataGenerator/Doctrine/Repository/TaxonRepositoryInterface.php
+++ b/src/DataGenerator/Doctrine/Repository/TaxonRepositoryInterface.php
@@ -36,4 +36,6 @@ public function findBatch(
): array;
public function getEntityCount(): int;
+
+ public function getRandomTaxon(): TaxonInterface;
}
diff --git a/src/DataGenerator/Exception/NoTaxonFoundException.php b/src/DataGenerator/Exception/NoTaxonFoundException.php
new file mode 100644
index 00000000..577d62e4
--- /dev/null
+++ b/src/DataGenerator/Exception/NoTaxonFoundException.php
@@ -0,0 +1,17 @@
+getIO(),
$commandContext->getProductsQty(),
@@ -63,7 +67,7 @@ private function productGeneratorContext(
private function taxonGeneratorContext(
DataGeneratorCommandContextInterface $commandContext,
- ): TaxonGeneratorContext {
+ ): TaxonGeneratorContextInterface {
return new TaxonGeneratorContext(
$commandContext->getIO(),
$commandContext->getTaxonsQty(),
@@ -84,7 +88,7 @@ private function wishlistGeneratorContext(
private function productTaxonGeneratorContext(
DataGeneratorCommandContextInterface $commandContext,
- ): ProductTaxonGeneratorContext {
+ ): ProductTaxonGeneratorContextInterface {
return new ProductTaxonGeneratorContext(
$commandContext->getIO(),
$commandContext->getProductsPerTaxonQty(),
@@ -95,7 +99,7 @@ private function productTaxonGeneratorContext(
private function wishlistProductGeneratorContext(
DataGeneratorCommandContextInterface $commandContext,
- ): WishlistProductGeneratorContext {
+ ): WishlistProductGeneratorContextInterface {
return new WishlistProductGeneratorContext(
$commandContext->getIO(),
$commandContext->getProductsPerWishlistQty(),
diff --git a/src/DataGenerator/Factory/Entity/ProductFactory.php b/src/DataGenerator/Factory/Entity/ProductFactory.php
index b5cba541..e2b3d23f 100644
--- a/src/DataGenerator/Factory/Entity/ProductFactory.php
+++ b/src/DataGenerator/Factory/Entity/ProductFactory.php
@@ -15,6 +15,7 @@
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
+use Sylius\Component\Core\Model\TaxonInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
final class ProductFactory implements ProductFactoryInterface
@@ -34,6 +35,7 @@ public function create(
ProductVariantInterface $variant,
ChannelInterface $channel,
DateTimeInterface $createdAt,
+ TaxonInterface $mainTaxon,
): ProductInterface {
/** @var ProductInterface $product */
$product = $this->productFactory->createNew();
@@ -46,6 +48,7 @@ public function create(
$product->setCreatedAt($createdAt);
$product->addChannel($channel);
$product->addVariant($variant);
+ $product->setMainTaxon($mainTaxon);
return $product;
}
diff --git a/src/DataGenerator/Factory/Entity/ProductFactoryInterface.php b/src/DataGenerator/Factory/Entity/ProductFactoryInterface.php
index ed10713e..3bc118e1 100644
--- a/src/DataGenerator/Factory/Entity/ProductFactoryInterface.php
+++ b/src/DataGenerator/Factory/Entity/ProductFactoryInterface.php
@@ -14,6 +14,7 @@
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
+use Sylius\Component\Core\Model\TaxonInterface;
interface ProductFactoryInterface
{
@@ -25,5 +26,6 @@ public function create(
ProductVariantInterface $variant,
ChannelInterface $channel,
DateTimeInterface $createdAt,
+ TaxonInterface $mainTaxon,
): ProductInterface;
}
diff --git a/src/DataGenerator/Generator/Bulk/Collection/ProductTaxonCollectionBulkGenerator.php b/src/DataGenerator/Generator/Bulk/Collection/ProductTaxonCollectionBulkGenerator.php
index 555d1d07..4c4ea037 100644
--- a/src/DataGenerator/Generator/Bulk/Collection/ProductTaxonCollectionBulkGenerator.php
+++ b/src/DataGenerator/Generator/Bulk/Collection/ProductTaxonCollectionBulkGenerator.php
@@ -37,6 +37,10 @@ public function generate(ContextInterface $context): void
throw new InvalidContextException();
}
+ if ($context->getQuantity() === 0) {
+ return;
+ }
+
$io = $context->getIO();
$io->info(sprintf(
diff --git a/src/DataGenerator/Generator/Bulk/Collection/WishlistProductCollectionBulkGenerator.php b/src/DataGenerator/Generator/Bulk/Collection/WishlistProductCollectionBulkGenerator.php
index e5c17c36..b0c9119d 100644
--- a/src/DataGenerator/Generator/Bulk/Collection/WishlistProductCollectionBulkGenerator.php
+++ b/src/DataGenerator/Generator/Bulk/Collection/WishlistProductCollectionBulkGenerator.php
@@ -37,6 +37,10 @@ public function generate(ContextInterface $context): void
throw new InvalidContextException();
}
+ if ($context->getQuantity() === 0) {
+ return;
+ }
+
$io = $context->getIO();
$io->info(sprintf(
diff --git a/src/DataGenerator/Generator/Entity/ProductGenerator.php b/src/DataGenerator/Generator/Entity/ProductGenerator.php
index e52457e1..77d44bf3 100644
--- a/src/DataGenerator/Generator/Entity/ProductGenerator.php
+++ b/src/DataGenerator/Generator/Entity/ProductGenerator.php
@@ -12,6 +12,7 @@
use BitBag\SyliusVueStorefront2Plugin\DataGenerator\ContextModel\Generator\GeneratorContextInterface;
use BitBag\SyliusVueStorefront2Plugin\DataGenerator\ContextModel\Generator\ProductGeneratorContextInterface;
+use BitBag\SyliusVueStorefront2Plugin\DataGenerator\Doctrine\Repository\TaxonRepositoryInterface;
use BitBag\SyliusVueStorefront2Plugin\DataGenerator\Exception\InvalidContextException;
use BitBag\SyliusVueStorefront2Plugin\DataGenerator\Factory\Entity\ChannelPricingFactoryInterface;
use BitBag\SyliusVueStorefront2Plugin\DataGenerator\Factory\Entity\ProductFactoryInterface;
@@ -28,16 +29,20 @@ final class ProductGenerator implements GeneratorInterface
private ChannelPricingFactoryInterface $channelPricingFactory;
+ private TaxonRepositoryInterface $taxonRepository;
+
private Generator $faker;
public function __construct(
ProductFactoryInterface $productFactory,
ProductVariantFactoryInterface $productVariantFactory,
ChannelPricingFactoryInterface $channelPricingFactory,
+ TaxonRepositoryInterface $taxonRepository,
) {
$this->productFactory = $productFactory;
$this->productVariantFactory = $productVariantFactory;
$this->channelPricingFactory = $channelPricingFactory;
+ $this->taxonRepository = $taxonRepository;
$this->faker = Factory::create();
}
@@ -47,9 +52,10 @@ public function generate(GeneratorContextInterface $context): ProductInterface
throw new InvalidContextException();
}
+ $channel = $context->getChannel();
$channelPricing = $this->channelPricingFactory->create(
$this->faker->randomNumber(),
- $context->getChannel(),
+ $channel,
);
$uuid = $this->faker->uuid;
@@ -65,8 +71,9 @@ public function generate(GeneratorContextInterface $context): ProductInterface
$this->faker->sentence(15),
$this->faker->sentence(),
$variant,
- $context->getChannel(),
- $this->faker->dateTimeBetween('-1 year')
+ $channel,
+ $this->faker->dateTimeBetween('-1 year'),
+ $this->taxonRepository->getRandomTaxon(),
);
}
}
diff --git a/src/DataGenerator/Generator/Entity/TaxonGenerator.php b/src/DataGenerator/Generator/Entity/TaxonGenerator.php
index e9826276..c9ca0c12 100644
--- a/src/DataGenerator/Generator/Entity/TaxonGenerator.php
+++ b/src/DataGenerator/Generator/Entity/TaxonGenerator.php
@@ -44,7 +44,7 @@ public function generate(GeneratorContextInterface $context): TaxonInterface
}
$translation = TaxonTranslationFactory::create(
- $this->faker->sentence(3),
+ sprintf('%s %s', $this->faker->uuid, $this->faker->sentence(3)),
$context::DEFAULT_LOCALE,
);
diff --git a/src/DataGenerator/Generator/SimpleType/Integer/IntegerGenerator.php b/src/DataGenerator/Generator/SimpleType/Integer/IntegerGenerator.php
index cefb4425..66de2e80 100644
--- a/src/DataGenerator/Generator/SimpleType/Integer/IntegerGenerator.php
+++ b/src/DataGenerator/Generator/SimpleType/Integer/IntegerGenerator.php
@@ -37,6 +37,6 @@ public function generateBiased(
return $this->rand->rand($topValuesThreshold, $max);
}
- return $this->rand->rand($min, $topValuesThreshold - 1);
+ return $this->rand->rand($min, max(0, $topValuesThreshold - 1));
}
}
diff --git a/src/DataGenerator/Resources/services/generators.xml b/src/DataGenerator/Resources/services/generators.xml
index e866107b..af9b117a 100644
--- a/src/DataGenerator/Resources/services/generators.xml
+++ b/src/DataGenerator/Resources/services/generators.xml
@@ -18,6 +18,7 @@
+
compositePreFetcher = $preFetchedDataProvider;
+ }
+
+ public function getCollection(
+ string $resourceClass,
+ string $operationName = null,
+ array $context = [],
+ ): iterable {
+ $collection = parent::getCollection($resourceClass, $operationName, $context);
+
+ $ids = [];
+ /** @var ResourceInterface $item */
+ foreach ($collection as $item) {
+ $ids[] = $item->getId();
+ }
+
+ $this->compositePreFetcher->preFetchData($ids, $context);
+
+ return $collection;
+ }
+
+ public function getCachedData(
+ string $identifier,
+ array $context,
+ ): array {
+ return $this->compositePreFetcher->getPreFetchedData($identifier, $context);
+ }
+}
diff --git a/src/DataProvider/CachedCollectionDataProviderInterface.php b/src/DataProvider/CachedCollectionDataProviderInterface.php
new file mode 100644
index 00000000..2f4374a3
--- /dev/null
+++ b/src/DataProvider/CachedCollectionDataProviderInterface.php
@@ -0,0 +1,22 @@
+preFetchers = $preFetchers;
+ }
+
+ public function preFetchData(
+ array $parentIds,
+ array $context,
+ ): void {
+ $attributes = $context['attributes'] ?? null;
+ if ($attributes === null) {
+ return;
+ }
+
+ $attributes = $this->gatherAttributesToPreFetch($attributes);
+
+ foreach (array_keys($attributes) as $attribute) {
+ /** @var RestrictedPreFetcherInterface $preFetcher */
+ foreach ($this->preFetchers as $preFetcher) {
+ if ($preFetcher->supports($context, $attribute)) {
+ $preFetcher->preFetchData($parentIds, $context);
+
+ break;
+ }
+ }
+ }
+ }
+
+ public function getPreFetchedData(
+ string $identifier,
+ array $context,
+ ): array {
+ foreach ($this->preFetchers as $preFetcher) {
+ if ($preFetcher->supports($context)) {
+ return $preFetcher->getPreFetchedData($identifier, $context);
+ }
+ }
+
+ return [];
+ }
+
+ private function gatherAttributesToPreFetch(array $attributes): array
+ {
+ $filteredAttributes = $this->filterAttributes($attributes);
+
+ foreach ($filteredAttributes as $attribute => $fields) {
+ $isCollection = is_array($fields['collection'] ?? null);
+
+ if ($isCollection) {
+ $nestedAttributes = $this->gatherAttributesToPreFetch($fields['collection']);
+ } else {
+ $nestedAttributes = $this->gatherAttributesToPreFetch($fields['edges']['node']);
+ }
+
+ $filteredAttributes = array_merge($filteredAttributes, $nestedAttributes);
+ }
+
+ return $filteredAttributes;
+ }
+
+ private function filterAttributes(array $attributes): array
+ {
+ return array_filter(
+ $attributes,
+ static fn($attr) => is_array($attr['collection'] ?? $attr['edges'] ?? null)
+ );
+ }
+}
diff --git a/src/DataProvider/PreFetcher/PreFetcherInterface.php b/src/DataProvider/PreFetcher/PreFetcherInterface.php
new file mode 100644
index 00000000..0731b966
--- /dev/null
+++ b/src/DataProvider/PreFetcher/PreFetcherInterface.php
@@ -0,0 +1,24 @@
+repository = $repository;
+ }
+
+ public function preFetchData(
+ array $parentIds,
+ array $context,
+ ): void {
+ /** @var ChannelPricingInterface $result */
+ foreach ($this->repository->findByProductIds($parentIds, $context) as $result) {
+ $this->preFetchedData[$result->getProductVariant()?->getCode()][] = $result;
+ }
+ }
+
+ public function getPreFetchedData(
+ string $identifier,
+ ?array $context = [],
+ ): array {
+ return $this->preFetchedData[$identifier] ?? [];
+ }
+
+ public function supports(
+ array $context,
+ ?string $attribute = null,
+ ): bool {
+ $resourceClass = $context['resource_class'];
+
+ return is_a($resourceClass, ChannelPricingInterface::class, true)
+ || ($attribute === 'channelPricings' && is_a($resourceClass, ProductInterface::class, true));
+ }
+}
diff --git a/src/DataProvider/PreFetcher/Product/ProductAttributePreFetcher.php b/src/DataProvider/PreFetcher/Product/ProductAttributePreFetcher.php
new file mode 100644
index 00000000..ac4edbdb
--- /dev/null
+++ b/src/DataProvider/PreFetcher/Product/ProductAttributePreFetcher.php
@@ -0,0 +1,55 @@
+repository = $repository;
+ }
+
+ public function preFetchData(
+ array $parentIds,
+ array $context,
+ ): void {
+ /** @var ProductAttributeValueInterface $result */
+ foreach ($this->repository->findByProductIds($parentIds, $context) as $result) {
+ $this->preFetchedData[$result->getProduct()?->getCode()][] = $result;
+ }
+ }
+
+ public function getPreFetchedData(
+ string $identifier,
+ ?array $context = [],
+ ): array {
+ return $this->preFetchedData[$identifier] ?? [];
+ }
+
+ public function supports(
+ array $context,
+ ?string $attribute = null,
+ ): bool {
+ $resourceClass = $context['resource_class'];
+
+ return is_a($resourceClass, ProductAttributeValueInterface::class, true)
+ || ($attribute === 'attributes' && is_a($resourceClass, ProductInterface::class, true));
+ }
+}
diff --git a/src/DataProvider/PreFetcher/Product/ProductImagePreFetcher.php b/src/DataProvider/PreFetcher/Product/ProductImagePreFetcher.php
new file mode 100644
index 00000000..24c5c4ac
--- /dev/null
+++ b/src/DataProvider/PreFetcher/Product/ProductImagePreFetcher.php
@@ -0,0 +1,54 @@
+repository = $repository;
+ }
+
+ public function preFetchData(
+ array $parentIds,
+ array $context,
+ ): void {
+ foreach ($this->repository->findByProductIds($parentIds, $context) as $result) {
+ $this->preFetchedData[$result['code']][] = $result[0];
+ }
+ }
+
+ public function getPreFetchedData(
+ string $identifier,
+ ?array $context = [],
+ ): array {
+ return $this->preFetchedData[$identifier] ?? [];
+ }
+
+ public function supports(
+ array $context,
+ ?string $attribute = null,
+ ): bool {
+ $resourceClass = $context['resource_class'];
+
+ return is_a($resourceClass, ProductImageInterface::class, true)
+ || ($attribute === 'images' && is_a($resourceClass, ProductInterface::class, true));
+ }
+}
diff --git a/src/DataProvider/PreFetcher/Product/ProductOptionsPreFetcherInterface.php b/src/DataProvider/PreFetcher/Product/ProductOptionsPreFetcherInterface.php
new file mode 100644
index 00000000..2f8c3592
--- /dev/null
+++ b/src/DataProvider/PreFetcher/Product/ProductOptionsPreFetcherInterface.php
@@ -0,0 +1,26 @@
+repository = $repository;
+ }
+
+ public function preFetchData(
+ array $parentIds,
+ array $context,
+ ): void {
+ if ($this->isPrefetched === true) {
+ return;
+ }
+
+ $result = $this->repository->findOptionsByProductIds($parentIds, $context);
+
+ /** @var ProductVariantInterface $result */
+ foreach ($result as $variant) {
+ $this->prepareProductOptions($variant);
+ $this->prepareOptionValues($variant);
+ $this->prepareVariantOptionValues($variant);
+ }
+
+ $this->isPrefetched = true;
+ }
+
+ public function getPreFetchedData(
+ string $identifier,
+ array $context,
+ ): array {
+ return match ($context['property']) {
+ self::ELIGIBLE_ATTR_PRODUCT_OPTIONS => $this->productOptions[$identifier] ?? [],
+ self::ELIGIBLE_ATTR_PRODUCT_OPTION_VALUES => $this->optionValues[$identifier] ?? [],
+ self::ELIGIBLE_ATTR_VARIANT_OPTION_VALUES => $this->variantOptionValues[$identifier] ?? [],
+ default => [],
+ };
+ }
+
+ public function supports(
+ array $context,
+ ?string $attribute = null,
+ ): bool {
+ $resourceClass = $context['resource_class'];
+
+ return is_a($resourceClass, ProductOptionInterface::class, true)
+ || is_a($resourceClass, ProductOptionValueInterface::class, true)
+ || (in_array($attribute, self::ELIGIBLE_ATTRIBUTES, true)
+ && is_a($resourceClass, ProductInterface::class, true));
+ }
+
+ private function prepareProductOptions(ProductVariantInterface $variant): void
+ {
+ foreach ($variant->getOptionValues() as $optionValue) {
+ $option = $optionValue->getOption();
+ $this->productOptions[$variant->getProduct()?->getCode()][$option?->getCode()] = $option;
+ }
+ }
+
+ private function prepareOptionValues(ProductVariantInterface $variant): void
+ {
+ foreach ($variant->getOptionValues() as $optionValue) {
+ $this->optionValues[$optionValue->getOption()?->getCode()][$optionValue->getCode()] = $optionValue;
+ }
+ }
+
+ private function prepareVariantOptionValues(ProductVariantInterface $variant): void
+ {
+ $this->variantOptionValues[$variant->getCode()] = $variant->getOptionValues()->toArray();
+ }
+}
diff --git a/src/DataProvider/PreFetcher/Product/ProductVariantPreFetcher.php b/src/DataProvider/PreFetcher/Product/ProductVariantPreFetcher.php
new file mode 100644
index 00000000..6be0165c
--- /dev/null
+++ b/src/DataProvider/PreFetcher/Product/ProductVariantPreFetcher.php
@@ -0,0 +1,55 @@
+repository = $repository;
+ }
+
+ public function preFetchData(
+ array $parentIds,
+ array $context,
+ ): void {
+ /** @var ProductVariantInterface $result */
+ foreach ($this->repository->findByProductIds($parentIds, $context) as $result) {
+ $this->preFetchedData[$result->getProduct()?->getCode()][] = $result;
+ }
+ }
+
+ public function getPreFetchedData(
+ string $identifier,
+ ?array $context = [],
+ ): array {
+ return $this->preFetchedData[$identifier] ?? [];
+ }
+
+ public function supports(
+ array $context,
+ ?string $attribute = null,
+ ): bool {
+ $resourceClass = $context['resource_class'];
+
+ return is_a($resourceClass, ProductVariantInterface::class, true)
+ || ($attribute === 'variants' && is_a($resourceClass, ProductInterface::class, true));
+ }
+}
diff --git a/src/DataProvider/PreFetcher/RestrictedPreFetcherInterface.php b/src/DataProvider/PreFetcher/RestrictedPreFetcherInterface.php
new file mode 100644
index 00000000..34116a91
--- /dev/null
+++ b/src/DataProvider/PreFetcher/RestrictedPreFetcherInterface.php
@@ -0,0 +1,19 @@
+supports($resourceClass)) {
+ Assert::keyExists($identifiers, 'code');
+
+ /** @var ProductInterface[] $data */
+ $data = $this->cachedCollectionDataProvider->getCachedData($identifiers['code'], $context);
+
+ return new ArrayPaginator($data, 0, count($data));
+ }
+
+ return $this->decoratedSubresourceProvider->getSubresource($resourceClass, $identifiers, $context, $operationName);
+ }
+
+ private function supports(string $resourceClass): bool
+ {
+ foreach (self::ELIGIBLE_ENTITIES as $entity) {
+ if (is_a($resourceClass, $entity, true)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/DataProvider/SubresourceDataProviderInterface.php b/src/DataProvider/SubresourceDataProviderInterface.php
new file mode 100644
index 00000000..b4100a81
--- /dev/null
+++ b/src/DataProvider/SubresourceDataProviderInterface.php
@@ -0,0 +1,31 @@
+addSelect('ovs')
+ ->join('o.variants', 'ovs');
+ }
+ }
+}
diff --git a/src/Doctrine/Repository/Product/ChannelPricingRepository.php b/src/Doctrine/Repository/Product/ChannelPricingRepository.php
new file mode 100644
index 00000000..3c8c0f5b
--- /dev/null
+++ b/src/Doctrine/Repository/Product/ChannelPricingRepository.php
@@ -0,0 +1,49 @@
+entityManager = $entityManager;
+ }
+
+ public function findByProductIds(
+ array $productIds,
+ array $context,
+ ): array {
+ $channel = $context[ContextKeys::CHANNEL];
+ Assert::isInstanceOf($channel, ChannelInterface::class);
+
+ return $this->entityManager->createQueryBuilder()
+ ->from(ChannelPricingInterface::class, 'channelPricing')
+ ->leftJoin('channelPricing.productVariant', 'variant')
+ ->leftJoin('variant.product', 'product')
+ ->leftJoin(ChannelInterface::class, 'channel', Join::WITH, 'channel.code = channelPricing.channelCode')
+ ->addSelect('channelPricing')
+ ->andWhere('product.id IN (:productIds)')
+ ->andWhere('channel = :channel')
+ ->setParameter('productIds', $productIds)
+ ->setParameter('channel', $channel)
+ ->getQuery()
+ ->getResult();
+ }
+}
diff --git a/src/Doctrine/Repository/Product/ChannelPricingRepositoryInterface.php b/src/Doctrine/Repository/Product/ChannelPricingRepositoryInterface.php
new file mode 100644
index 00000000..acd199a1
--- /dev/null
+++ b/src/Doctrine/Repository/Product/ChannelPricingRepositoryInterface.php
@@ -0,0 +1,19 @@
+getEntityManager(), $decoratedRepository->getClassMetadata());
+ $this->decoratedRepository = $decoratedRepository;
+ }
+
+ public function findByProductIds(
+ array $productIds,
+ array $context,
+ ): array {
+ $locale = $context[ContextKeys::LOCALE_CODE] ?? 'en_US';
+
+ return $this->createQueryBuilder('value')
+ ->leftJoin('value.attribute', 'attribute')
+ ->leftJoin('attribute.translations', 'translation')
+ ->leftJoin('value.subject', 'product')
+ ->addSelect('attribute')
+ ->addSelect('translation')
+ ->andWhere('product.id IN (:productIds)')
+ ->andWhere('translation.locale = :locale')
+ ->andWhere('value.localeCode = :locale')
+ ->setParameter('productIds', $productIds)
+ ->setParameter('locale', $locale)
+ ->getQuery()
+ ->getResult();
+ }
+
+ public function findByJsonChoiceKey(string $choiceKey): array
+ {
+ return $this->decoratedRepository->findByJsonChoiceKey($choiceKey);
+ }
+}
diff --git a/src/Doctrine/Repository/Product/ProductAttributeValueRepositoryInterface.php b/src/Doctrine/Repository/Product/ProductAttributeValueRepositoryInterface.php
new file mode 100644
index 00000000..ec418854
--- /dev/null
+++ b/src/Doctrine/Repository/Product/ProductAttributeValueRepositoryInterface.php
@@ -0,0 +1,21 @@
+entityManager = $entityManager;
+ }
+
+ public function findByProductIds(
+ array $productIds,
+ array $context,
+ ): array {
+ return $this->entityManager->createQueryBuilder()
+ ->from(ProductImageInterface::class, 'image')
+ ->join('image.owner', 'product')
+ ->select('image')
+ ->addSelect('product.code')
+ ->andWhere('product.id IN (:productIds)')
+ ->setParameter('productIds', $productIds)
+ ->getQuery()
+ ->getResult();
+ }
+}
diff --git a/src/Doctrine/Repository/Product/ProductImageRepositoryInterface.php b/src/Doctrine/Repository/Product/ProductImageRepositoryInterface.php
new file mode 100644
index 00000000..f4a6772f
--- /dev/null
+++ b/src/Doctrine/Repository/Product/ProductImageRepositoryInterface.php
@@ -0,0 +1,19 @@
+createQueryBuilder('variant')
+ ->leftJoin('variant.product', 'product')
+ ->leftJoin('variant.channelPricings', 'channelPricing')
+ ->leftJoin(ChannelInterface::class, 'channel', Join::WITH, 'channel.code = channelPricing.channelCode')
+ ->leftJoin('channelPricing.appliedPromotions', 'appliedPromotion')
+ ->leftJoin('variant.translations', 'translation')
+ ->addSelect('translation')
+ ->addSelect('channelPricing')
+ ->addSelect('appliedPromotion')
+ ->andWhere('product.id IN (:productIds)')
+ ->andWhere('translation.locale = :locale')
+ ->andWhere('channel = :channel')
+ ->setParameter('productIds', $productIds)
+ ->setParameter('locale', $locale)
+ ->setParameter('channel', $channel)
+ ->getQuery()
+ ->getResult();
+ }
+
+ public function findOptionsByProductIds(
+ array $productIds,
+ array $context,
+ ): array {
+ $locale = $context[ContextKeys::LOCALE_CODE] ?? 'en_US';
+ $channel = $context[ContextKeys::CHANNEL];
+ Assert::isInstanceOf($channel, ChannelInterface::class);
+
+ return $this->createQueryBuilder('variant')
+ ->leftJoin('variant.product', 'product')
+ ->leftJoin('product.options', 'productOption')
+ ->leftJoin('variant.optionValues', 'optionValue')
+ ->leftJoin('optionValue.translations', 'optionValueTranslation')
+ ->leftJoin('optionValue.option', 'option')
+ ->leftJoin('option.translations', 'optionTranslation')
+ ->addSelect('product')
+ ->addSelect('productOption')
+ ->addSelect('optionValue')
+ ->addSelect('optionValueTranslation')
+ ->addSelect('option')
+ ->addSelect('optionTranslation')
+ ->andWhere('product.id IN (:productIds)')
+ ->andWhere('optionValueTranslation.locale = :locale')
+ ->andWhere(':channel MEMBER OF product.channels')
+ ->setParameter('productIds', $productIds)
+ ->setParameter('locale', $locale)
+ ->setParameter('channel', $channel)
+ ->getQuery()
+ ->getResult();
+ }
+}
diff --git a/src/Doctrine/Repository/Product/ProductVariantRepositoryInterface.php b/src/Doctrine/Repository/Product/ProductVariantRepositoryInterface.php
new file mode 100644
index 00000000..db4ee3d4
--- /dev/null
+++ b/src/Doctrine/Repository/Product/ProductVariantRepositoryInterface.php
@@ -0,0 +1,24 @@
+refreshTokenRepository->findOneBy(['refreshToken' => $refreshTokenString]);
-
Assert::notNull($refreshToken);
-
- /** @var RefreshTokenInterface $refreshToken */
$this->validateRefreshToken($refreshToken, $refreshTokenString);
/** @var ShopUserInterface $user */
diff --git a/src/Resources/services/data_providers.xml b/src/Resources/services/data_providers.xml
index 55471bda..78544db6 100644
--- a/src/Resources/services/data_providers.xml
+++ b/src/Resources/services/data_providers.xml
@@ -40,5 +40,71 @@ We are hiring developers from all over the world. Join us and start your new, ex
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Resources/services/doctrine_orm.xml b/src/Resources/services/doctrine_orm.xml
index 53849a30..64975c29 100644
--- a/src/Resources/services/doctrine_orm.xml
+++ b/src/Resources/services/doctrine_orm.xml
@@ -21,6 +21,7 @@ We are hiring developers from all over the world. Join us and start your new, ex
+
@@ -30,9 +31,37 @@ We are hiring developers from all over the world. Join us and start your new, ex
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %sylius.model.product_variant.class%
+
+
+
+
+
+
+
+
diff --git a/src/Resources/services/extension.xml b/src/Resources/services/extension.xml
index 18226f83..1344b27b 100644
--- a/src/Resources/services/extension.xml
+++ b/src/Resources/services/extension.xml
@@ -18,5 +18,13 @@ We are hiring developers from all over the world. Join us and start your new, ex
>
+
+
+
+
diff --git a/tests/Application/config/packages/doctrine_migrations.yaml b/tests/Application/config/packages/doctrine_migrations.yaml
index 7ffde0f7..1d0a6140 100644
--- a/tests/Application/config/packages/doctrine_migrations.yaml
+++ b/tests/Application/config/packages/doctrine_migrations.yaml
@@ -4,4 +4,4 @@ doctrine_migrations:
table_name: sylius_migrations
migrations_paths:
- 'migrations': '%kernel.project_dir%/../../migrations'
+ 'App\Migrations': '%kernel.project_dir%/../../migrations'
diff --git a/tests/Integration/DataGenerator/TaxonRepositoryTest.php b/tests/Integration/DataGenerator/TaxonRepositoryTest.php
index 043b85b4..25b6012f 100644
--- a/tests/Integration/DataGenerator/TaxonRepositoryTest.php
+++ b/tests/Integration/DataGenerator/TaxonRepositoryTest.php
@@ -12,6 +12,7 @@
use ApiTestCase\JsonApiTestCase;
use BitBag\SyliusVueStorefront2Plugin\DataGenerator\Doctrine\Repository\TaxonRepositoryInterface;
+use Sylius\Component\Core\Model\TaxonInterface;
final class TaxonRepositoryTest extends JsonApiTestCase
{
@@ -136,6 +137,17 @@ public function test_find_batch_with_offset_exceeded(): void
$this->assertCount(0, $taxons);
}
+ public function test_getting_random_shop_user(): void
+ {
+ $this->loadFixtures();
+
+ $repository = $this->getContainer()
+ ->get('bitbag.sylius_vue_storefront2_plugin.data_generator.repository.taxon_repository');
+
+ $taxon = $repository->getRandomTaxon();
+ $this->assertInstanceOf(TaxonInterface::class, $taxon);
+ }
+
public function test_getting_entity_count(): void
{
$this->loadFixtures();