Skip to content

Commit

Permalink
added Persoo bundle for product search
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasLudvik committed Jan 19, 2024
1 parent 8ec15d1 commit e40970b
Show file tree
Hide file tree
Showing 11 changed files with 290 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Shopsys\FrontendApiBundle\DependencyInjection;

use Shopsys\FrontendApiBundle\Model\Resolver\Products\Search\Exception\ProductSearchResultsProviderPriorityNotSetException;
use Shopsys\FrontendApiBundle\Model\Resolver\Products\Search\ProductSearchResultsProviderResolver;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class RegisterProductsSearchResultsProvidersCompilerPass implements CompilerPassInterface
{
/**
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
*/
public function process(ContainerBuilder $container): void
{
$productSearchResultsProviderResolverDefinition = $container->getDefinition(ProductSearchResultsProviderResolver::class);
$productSearchResultsProvidersDefinitions = $container->findTaggedServiceIds('shopsys.frontend_api.products_search_results_provider');

foreach ($productSearchResultsProvidersDefinitions as $serviceId => $tags) {
$priority = null;

foreach ($tags as $tag) {
if (array_key_exists('priority', $tag)) {
$priority = $tag['priority'];
}
}

if (!is_int($priority)) {
throw new ProductSearchResultsProviderPriorityNotSetException(sprintf('Service "%s" has not defined required tag priority or its type is not integer.', $serviceId));
}

$productSearchResultsProviderResolverDefinition->addMethodCall(
'registerProductSearchResultsProvider',
[
$serviceId,
$priority,
],
);
}
}
}
8 changes: 4 additions & 4 deletions src/Model/Product/ProductRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ public function __construct(protected readonly FrameworkProductRepository $produ
*/
public function getSellableByUuid(string $uuid, int $domainId, PricingGroup $pricingGroup): Product
{
$qb = $this->productRepository->getAllSellableQueryBuilder($domainId, $pricingGroup);
$qb->andWhere('p.uuid = :uuid');
$qb->setParameter('uuid', $uuid);
$queryBuilder = $this->productRepository->getAllSellableQueryBuilder($domainId, $pricingGroup);
$queryBuilder->andWhere('p.uuid = :uuid');
$queryBuilder->setParameter('uuid', $uuid);

$product = $qb->getQuery()->getOneOrNullResult();
$product = $queryBuilder->getQuery()->getOneOrNullResult();

if ($product === null) {
throw new ProductNotFoundException(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Shopsys\FrontendApiBundle\Model\Resolver\Products\Search\Exception;

use Exception;

class NoProductSearchResultsProviderEnabledOnDomainException extends Exception
{
/**
* @param int $domainId
*/
public function __construct(int $domainId)
{
parent::__construct(sprintf('No product search results provider enabled on domain with id "%d".', $domainId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Shopsys\FrontendApiBundle\Model\Resolver\Products\Search\Exception;

use Exception;

class ProductSearchResultsProviderPriorityNotSetException extends Exception
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Shopsys\FrontendApiBundle\Model\Resolver\Products\Search\Exception;

use Exception;

class ProductSearchResultsProviderWithSamePriorityAlreadyExistsException extends Exception
{
/**
* @param string $serviceId
* @param int $priority
*/
public function __construct(string $serviceId, int $priority)
{
parent::__construct(
sprintf('Cannot register ProductSearchResultsProvider "%s" with priority "%d" as provider with same priority already exists.', $serviceId, $priority),
);
}
}
45 changes: 45 additions & 0 deletions src/Model/Resolver/Products/Search/ProductSearchQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Shopsys\FrontendApiBundle\Model\Resolver\Products\Search;

use Overblog\GraphQLBundle\Definition\Argument;
use Shopsys\FrameworkBundle\Component\Domain\Domain;
use Shopsys\FrontendApiBundle\Component\Validation\PageSizeValidator;
use Shopsys\FrontendApiBundle\Model\Product\Connection\ProductConnection;
use Shopsys\FrontendApiBundle\Model\Product\Filter\ProductFilterFacade;
use Shopsys\FrontendApiBundle\Model\Resolver\AbstractQuery;

class ProductSearchQuery extends AbstractQuery
{
/**
* @param \Shopsys\FrontendApiBundle\Model\Resolver\Products\Search\ProductSearchResultsProviderResolver $productSearchResultsProviderResolver
* @param \Shopsys\FrontendApiBundle\Model\Product\Filter\ProductFilterFacade $productFilterFacade
* @param \Shopsys\FrameworkBundle\Component\Domain\Domain $domain
*/
public function __construct(
protected readonly ProductSearchResultsProviderResolver $productSearchResultsProviderResolver,
protected readonly ProductFilterFacade $productFilterFacade,
protected readonly Domain $domain,
) {
}

/**
* @param \Overblog\GraphQLBundle\Definition\Argument $argument
* @return \Shopsys\FrontendApiBundle\Model\Product\Connection\ProductConnection
*/
public function productsSearchQuery(Argument $argument): ProductConnection
{
PageSizeValidator::checkMaxPageSize($argument);
$this->setDefaultFirstOffsetIfNecessary($argument);

$productFilterData = $this->productFilterFacade->getValidatedProductFilterDataForAll(
$argument,
);

$productSearchResultsProvider = $this->productSearchResultsProviderResolver->getProductsSearchResultsProviderByDomainId($this->domain->getId());

return $productSearchResultsProvider->getProductsSearchResults($argument, $productFilterData);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,44 @@

declare(strict_types=1);

namespace Shopsys\FrontendApiBundle\Model\Resolver\Products;
namespace Shopsys\FrontendApiBundle\Model\Resolver\Products\Search;

use Overblog\GraphQLBundle\Definition\Argument;
use Shopsys\FrameworkBundle\Model\Product\Filter\ProductFilterData;
use Shopsys\FrameworkBundle\Model\Product\Filter\ProductFilterDataFactory;
use Shopsys\FrameworkBundle\Model\Product\Listing\ProductListOrderingConfig;
use Shopsys\FrontendApiBundle\Model\Product\Connection\ProductConnection;
use Shopsys\FrontendApiBundle\Model\Product\Connection\ProductConnectionFactory;
use Shopsys\FrontendApiBundle\Model\Product\Filter\ProductFilterFacade;
use Shopsys\FrontendApiBundle\Model\Product\ProductFacade;
use Shopsys\FrontendApiBundle\Model\Resolver\AbstractQuery;
use Shopsys\FrontendApiBundle\Model\Resolver\Products\ProductOrderingModeProvider;

class ProductSearchQuery extends AbstractQuery
class ProductSearchResultsProvider implements ProductSearchResultsProviderInterface
{
/**
* @param \Shopsys\FrontendApiBundle\Model\Product\Filter\ProductFilterFacade $productFilterFacade
* @param \Shopsys\FrameworkBundle\Model\Product\Filter\ProductFilterDataFactory $productFilterDataFactory
* @param \Shopsys\FrontendApiBundle\Model\Product\Connection\ProductConnectionFactory $productConnectionFactory
* @param \Shopsys\FrontendApiBundle\Model\Product\ProductFacade $productFacade
* @param \Shopsys\FrontendApiBundle\Model\Resolver\Products\ProductOrderingModeProvider $productOrderingModeProvider
*/
public function __construct(
protected readonly ProductFilterFacade $productFilterFacade,
protected readonly ProductFilterDataFactory $productFilterDataFactory,
protected readonly ProductConnectionFactory $productConnectionFactory,
protected readonly ProductFacade $productFacade,
protected readonly ProductOrderingModeProvider $productOrderingModeProvider,
) {
}

/**
* @param \Overblog\GraphQLBundle\Definition\Argument $argument
* @param \Shopsys\FrameworkBundle\Model\Product\Filter\ProductFilterData $productFilterData
* @return \Shopsys\FrontendApiBundle\Model\Product\Connection\ProductConnection
*/
public function productsSearchQuery(Argument $argument): ProductConnection
{
public function getProductsSearchResults(
Argument $argument,
ProductFilterData $productFilterData,
): ProductConnection {
$search = $argument['search'] ?? '';

$productFilterData = $this->productFilterFacade->getValidatedProductFilterDataForAll(
$argument,
);

return $this->productConnectionFactory->createConnectionForAll(
function ($offset, $limit) use ($search, $productFilterData) {
return $this->productFacade->getFilteredProductsOnCurrentDomain(
Expand All @@ -51,7 +53,16 @@ function ($offset, $limit) use ($search, $productFilterData) {
$this->productFacade->getFilteredProductsCountOnCurrentDomain($productFilterData, $search),
$argument,
$productFilterData,
ProductsQuery::getOrderingModeFromArgument($argument),
$this->productOrderingModeProvider->getOrderingModeFromArgument($argument),
);
}

/**
* @param int $domainId
* @return bool
*/
public function isEnabledOnDomain(int $domainId): bool
{
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Shopsys\FrontendApiBundle\Model\Resolver\Products\Search;

use Overblog\GraphQLBundle\Definition\Argument;
use Shopsys\FrameworkBundle\Model\Product\Filter\ProductFilterData;
use Shopsys\FrontendApiBundle\Model\Product\Connection\ProductConnection;

interface ProductSearchResultsProviderInterface
{
/**
* @param \Overblog\GraphQLBundle\Definition\Argument $argument
* @param \Shopsys\FrameworkBundle\Model\Product\Filter\ProductFilterData $productFilterData
* @return \Shopsys\FrontendApiBundle\Model\Product\Connection\ProductConnection
*/
public function getProductsSearchResults(
Argument $argument,
ProductFilterData $productFilterData,
): ProductConnection;

/**
* @param int $domainId
* @return bool
*/
public function isEnabledOnDomain(int $domainId): bool;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace Shopsys\FrontendApiBundle\Model\Resolver\Products\Search;

use Shopsys\FrontendApiBundle\Model\Resolver\Products\Search\Exception\NoProductSearchResultsProviderEnabledOnDomainException;
use Shopsys\FrontendApiBundle\Model\Resolver\Products\Search\Exception\ProductSearchResultsProviderWithSamePriorityAlreadyExistsException;
use Webmozart\Assert\Assert;

class ProductSearchResultsProviderResolver
{
/**
* @var array<int, string>
*/
protected array $productSearchResultsProvidersServiceIdByPriority = [];

/**
* @param \Shopsys\FrontendApiBundle\Model\Resolver\Products\Search\ProductSearchResultsProviderInterface[] $productSearchResultsProviders
*/
public function __construct(
protected readonly iterable $productSearchResultsProviders,
) {
}

/**
* @param int $domainId
* @return \Shopsys\FrontendApiBundle\Model\Resolver\Products\Search\ProductSearchResultsProviderInterface
*/
public function getProductsSearchResultsProviderByDomainId(
int $domainId,
): ProductSearchResultsProviderInterface {
Assert::allIsInstanceOf($this->productSearchResultsProviders, ProductSearchResultsProviderInterface::class);

foreach ($this->getProductsSearchResultsProvidersOrderedByPriority() as $productSearchResultsProvider) {
if ($productSearchResultsProvider->isEnabledOnDomain($domainId)) {
return $productSearchResultsProvider;
}
}

throw new NoProductSearchResultsProviderEnabledOnDomainException($domainId);
}

/**
* @return \Shopsys\FrontendApiBundle\Model\Resolver\Products\Search\ProductSearchResultsProviderInterface[]
*/
protected function getProductsSearchResultsProvidersOrderedByPriority(): array
{
krsort($this->productSearchResultsProvidersServiceIdByPriority, SORT_NUMERIC);

$productSearchResultsProvidersOrderedByPriority = [];

foreach ($this->productSearchResultsProvidersServiceIdByPriority as $serviceId) {
foreach ($this->productSearchResultsProviders as $productSearchResultsProvider) {
if ($productSearchResultsProvider instanceof $serviceId) {
$productSearchResultsProvidersOrderedByPriority[] = $productSearchResultsProvider;
}
}
}

return $productSearchResultsProvidersOrderedByPriority;
}

/**
* @param string $serviceId
* @param int $priority
*/
public function registerProductSearchResultsProvider(string $serviceId, int $priority): void
{
if (array_key_exists($priority, $this->productSearchResultsProvidersServiceIdByPriority)) {
throw new ProductSearchResultsProviderWithSamePriorityAlreadyExistsException($serviceId, $priority);
}

$this->productSearchResultsProvidersServiceIdByPriority[$priority] = $serviceId;
}
}
8 changes: 8 additions & 0 deletions src/Resources/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,11 @@ services:

Shopsys\FrontendApiBundle\Component\ExpressionLanguage\DynamicPaginationComplexityExpressionFunction:
tags: [ 'overblog_graphql.expression_function' ]

Shopsys\FrontendApiBundle\Model\Resolver\Products\Search\ProductSearchResultsProviderResolver:
arguments:
$productSearchResultsProviders: !tagged 'shopsys.frontend_api.products_search_results_provider'

Shopsys\FrontendApiBundle\Model\Resolver\Products\Search\ProductSearchResultsProvider:
tags:
- { name: 'shopsys.frontend_api.products_search_results_provider', priority: 1 }
11 changes: 11 additions & 0 deletions src/ShopsysFrontendApiBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@

namespace Shopsys\FrontendApiBundle;

use Shopsys\FrontendApiBundle\DependencyInjection\RegisterProductsSearchResultsProvidersCompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class ShopsysFrontendApiBundle extends Bundle
{
/**
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
*/
public function build(ContainerBuilder $container)
{
parent::build($container);

$container->addCompilerPass(new RegisterProductsSearchResultsProvidersCompilerPass());
}
}

0 comments on commit e40970b

Please sign in to comment.