Skip to content

Commit

Permalink
OP-225: Facets autodiscovery
Browse files Browse the repository at this point in the history
  • Loading branch information
GracjanJozefczyk committed May 27, 2024
1 parent b03f674 commit 54e54af
Show file tree
Hide file tree
Showing 16 changed files with 209 additions and 122 deletions.
30 changes: 5 additions & 25 deletions src/Facet/AttributeFacet.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,17 @@
use Elastica\Aggregation\Terms;
use Elastica\Query\AbstractQuery;
use Elastica\Query\Terms as TermsQuery;
use RuntimeException;
use function sprintf;
use Sylius\Component\Attribute\Model\AttributeInterface;
use Sylius\Component\Locale\Context\LocaleContextInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;

final class AttributeFacet implements FacetInterface
{
private ConcatedNameResolverInterface $attributeNameResolver;

private RepositoryInterface $productAttributeRepository;

private string $attributeCode;

private LocaleContextInterface $localeContext;

public function __construct(
ConcatedNameResolverInterface $attributeNameResolver,
RepositoryInterface $productAttributeRepository,
string $attributeCode,
LocaleContextInterface $localeContext
private ConcatedNameResolverInterface $attributeNameResolver,
private AttributeInterface $attribute,
private LocaleContextInterface $localeContext
) {
$this->attributeNameResolver = $attributeNameResolver;
$this->productAttributeRepository = $productAttributeRepository;
$this->attributeCode = $attributeCode;
$this->localeContext = $localeContext;
}

public function getAggregation(): AbstractAggregation
Expand Down Expand Up @@ -74,18 +59,13 @@ private function getFieldName(): string
{
return sprintf(
'%s_%s.keyword',
$this->attributeNameResolver->resolvePropertyName($this->attributeCode),
$this->attributeNameResolver->resolvePropertyName($this->getProductAttribute()->getCode()),
$this->localeContext->getLocaleCode()
);
}

private function getProductAttribute(): AttributeInterface
{
$attribute = $this->productAttributeRepository->findOneBy(['code' => $this->attributeCode]);
if (!$attribute instanceof AttributeInterface) {
throw new RuntimeException(sprintf('Cannot find attribute with code "%s"', $this->attributeCode));
}

return $attribute;
return $this->attribute;
}
}
84 changes: 84 additions & 0 deletions src/Facet/AutoDiscoverRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace BitBag\SyliusElasticsearchPlugin\Facet;

use BitBag\SyliusElasticsearchPlugin\PropertyNameResolver\ConcatedNameResolverInterface;
use BitBag\SyliusElasticsearchPlugin\Repository\ProductAttributeRepositoryInterface;
use BitBag\SyliusElasticsearchPlugin\Repository\ProductOptionRepositoryInterface;
use Sylius\Component\Attribute\Model\AttributeInterface;
use Sylius\Component\Locale\Context\LocaleContextInterface;
use Sylius\Component\Product\Model\ProductOptionInterface;

final class AutoDiscoverRegistry implements AutoDiscoverRegistryInterface
{
/** @var FacetInterface[] */
private array $facets = [];

public function __construct(
private bool $autoRegister,
private ProductAttributeRepositoryInterface $productAttributeRepository,
private ProductOptionRepositoryInterface $productOptionRepository,
private ConcatedNameResolverInterface $attributeNameResolver,
private ConcatedNameResolverInterface $optionNameResolver,
private LocaleContextInterface $localeContext,
private RegistryInterface $registry,
private array $excludedAttributes = [],
private array $excludedOptions = [],
) {
}

public function autoRegister(): void
{
if (false === $this->autoRegister || [] !== $this->facets) {
return;
}

$this->discoverAttributes();
$this->discoverOptions();

foreach ($this->facets as $facetId => $facet) {
$this->registry->addFacet($facetId, $facet);
}
}

private function discoverAttributes(): void
{
$attributes = $this->productAttributeRepository->findAllWithTranslations($this->localeContext->getLocaleCode());

/** @var AttributeInterface $attribute */
foreach ($attributes as $attribute) {
$code = $attribute->getCode();

if (in_array($code, $this->excludedAttributes, true)) {
continue;
}

$this->facets[$code] = new AttributeFacet(
$this->attributeNameResolver,
$attribute,
$this->localeContext,
);
}
}

private function discoverOptions(): void
{
$options = $this->productOptionRepository->findAllWithTranslations($this->localeContext->getLocaleCode());

/** @var ProductOptionInterface $option */
foreach ($options as $option) {
$code = $option->getCode();

if (in_array($code, $this->excludedOptions, true)) {
continue;
}

$this->facets[$code] = new OptionFacet(
$this->optionNameResolver,
$option,
);
}
}
}
8 changes: 8 additions & 0 deletions src/Facet/AutoDiscoverRegistryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace BitBag\SyliusElasticsearchPlugin\Facet;

interface AutoDiscoverRegistryInterface
{
public function autoRegister(): void;
}
25 changes: 4 additions & 21 deletions src/Facet/OptionFacet.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,14 @@
use Elastica\Aggregation\Terms;
use Elastica\Query\AbstractQuery;
use Elastica\Query\Terms as TermsQuery;
use RuntimeException;
use Sylius\Component\Product\Model\ProductOptionInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;

final class OptionFacet implements FacetInterface
{
private ConcatedNameResolverInterface $optionNameResolver;

private RepositoryInterface $productOptionRepository;

private string $productOptionCode;

public function __construct(
ConcatedNameResolverInterface $optionNameResolver,
RepositoryInterface $productOptionRepository,
string $optionCode
private ConcatedNameResolverInterface $optionNameResolver,
private ProductOptionInterface $productOption,
) {
$this->optionNameResolver = $optionNameResolver;
$this->productOptionRepository = $productOptionRepository;
$this->productOptionCode = $optionCode;
}

public function getAggregation(): AbstractAggregation
Expand All @@ -61,16 +49,11 @@ public function getBucketLabel(array $bucket): string

public function getLabel(): string
{
$productOption = $this->productOptionRepository->findOneBy(['code' => $this->productOptionCode]);
if (!$productOption instanceof ProductOptionInterface) {
throw new RuntimeException(sprintf('Cannot find product option with code "%s"', $this->productOptionCode));
}

return $productOption->getName();
return $this->productOption->getName();
}

private function getFieldName(): string
{
return $this->optionNameResolver->resolvePropertyName($this->productOptionCode) . '.keyword';
return $this->optionNameResolver->resolvePropertyName($this->productOption->getCode()) . '.keyword';
}
}
4 changes: 3 additions & 1 deletion src/Form/EventSubscriber/AddFacetsEventSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace BitBag\SyliusElasticsearchPlugin\Form\EventSubscriber;

use BitBag\SyliusElasticsearchPlugin\Form\Resolver\FacetsResolverInterface;
use BitBag\SyliusElasticsearchPlugin\Facet\AutoDiscoverRegistryInterface;
use BitBag\SyliusElasticsearchPlugin\Form\Resolver\ProductsFilterFacetResolverInterface;
use BitBag\SyliusElasticsearchPlugin\Form\Type\SearchFacetsType;
use FOS\ElasticaBundle\Paginator\FantaPaginatorAdapter;
Expand All @@ -17,6 +17,7 @@
final class AddFacetsEventSubscriber implements EventSubscriberInterface
{
public function __construct(
private AutoDiscoverRegistryInterface $autoDiscoverRegistry,
private ProductsFilterFacetResolverInterface $facetsResolver,
private string $namePropertyPrefix = '',
) {
Expand All @@ -31,6 +32,7 @@ public static function getSubscribedEvents(): array

public function addFacets(FormEvent $event): void
{
$this->autoDiscoverRegistry->autoRegister();
$adapter = $this->facetsResolver->resolveFacets($event, $this->namePropertyPrefix)->getAdapter();

$this->modifyForm($event->getForm(), $adapter);
Expand Down
7 changes: 6 additions & 1 deletion src/Form/Type/SearchType.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace BitBag\SyliusElasticsearchPlugin\Form\Type;

use BitBag\SyliusElasticsearchPlugin\Facet\AutoDiscoverRegistryInterface;
use BitBag\SyliusElasticsearchPlugin\Form\EventSubscriber\AddFacetsEventSubscriber;
use BitBag\SyliusElasticsearchPlugin\Form\Resolver\ProductsFilterFacetResolverInterface;
use BitBag\SyliusElasticsearchPlugin\Model\Search;
Expand All @@ -22,6 +23,7 @@
final class SearchType extends AbstractType
{
public function __construct(
private AutoDiscoverRegistryInterface $autoDiscoverRegistry,
private ProductsFilterFacetResolverInterface $facetsResolver
) {
}
Expand All @@ -33,7 +35,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
->setMethod('GET')
;

$builder->addEventSubscriber(new AddFacetsEventSubscriber($this->facetsResolver));
$builder->addEventSubscriber(new AddFacetsEventSubscriber(
$this->autoDiscoverRegistry,
$this->facetsResolver
));
}

public function configureOptions(OptionsResolver $resolver): void
Expand Down
8 changes: 7 additions & 1 deletion src/Form/Type/ShopProductsFilterType.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@

namespace BitBag\SyliusElasticsearchPlugin\Form\Type;

use BitBag\SyliusElasticsearchPlugin\Facet\AutoDiscoverRegistryInterface;
use BitBag\SyliusElasticsearchPlugin\Form\EventSubscriber\AddFacetsEventSubscriber;
use BitBag\SyliusElasticsearchPlugin\Form\Resolver\ProductsFilterFacetResolverInterface;
use Symfony\Component\Form\FormBuilderInterface;

final class ShopProductsFilterType extends AbstractFilterType
{
public function __construct(
private AutoDiscoverRegistryInterface $autoDiscoverRegistry,
private string $namePropertyPrefix,
private ProductsFilterFacetResolverInterface $facetResolver
) {
Expand All @@ -31,6 +33,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
->add('price', PriceFilterType::class, ['required' => false, 'label' => false])
->setMethod('GET');

$builder->addEventSubscriber(new AddFacetsEventSubscriber($this->facetResolver, $this->namePropertyPrefix));
$builder->addEventSubscriber(new AddFacetsEventSubscriber(
$this->autoDiscoverRegistry,
$this->facetResolver,
$this->namePropertyPrefix
));
}
}
22 changes: 21 additions & 1 deletion src/Repository/ProductAttributeRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace BitBag\SyliusElasticsearchPlugin\Repository;

use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\ORM\QueryBuilder;
use Sylius\Component\Resource\Repository\RepositoryInterface;

class ProductAttributeRepository implements ProductAttributeRepositoryInterface
Expand All @@ -38,4 +38,24 @@ public function getAttributeTypeByName(string $attributeName): string

return $result['type'];
}

public function findAllWithTranslations(?string $locale): array
{
/** @var QueryBuilder $queryBuilder */
$queryBuilder = $this->productAttributeRepository->createQueryBuilder('o');

if (null !== $locale) {
$queryBuilder
->addSelect('translation')
->leftJoin('o.translations', 'translation', 'ot')
->andWhere('translation.locale = :locale')
->setParameter('locale', $locale)
;
}

return $queryBuilder
->getQuery()
->getResult()
;
}
}
2 changes: 2 additions & 0 deletions src/Repository/ProductAttributeRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@
interface ProductAttributeRepositoryInterface
{
public function getAttributeTypeByName(string $attributeName): string;

public function findAllWithTranslations(?string $locale): array;
}
36 changes: 36 additions & 0 deletions src/Repository/ProductOptionRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace BitBag\SyliusElasticsearchPlugin\Repository;

use Doctrine\ORM\QueryBuilder;
use Sylius\Component\Resource\Repository\RepositoryInterface;

class ProductOptionRepository implements ProductOptionRepositoryInterface
{
public function __construct(
private RepositoryInterface $productOptionRepository
) {
}

public function findAllWithTranslations(?string $locale): array
{
/** @var QueryBuilder $queryBuilder */
$queryBuilder = $this->productOptionRepository->createQueryBuilder('o');

if (null !== $locale) {
$queryBuilder
->addSelect('translation')
->leftJoin('o.translations', 'translation', 'ot')
->andWhere('translation.locale = :locale')
->setParameter('locale', $locale)
;
}

return $queryBuilder
->getQuery()
->getResult()
;
}
}
10 changes: 10 additions & 0 deletions src/Repository/ProductOptionRepositoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace BitBag\SyliusElasticsearchPlugin\Repository;

interface ProductOptionRepositoryInterface
{
public function findAllWithTranslations(?string $locale): array;
}
3 changes: 3 additions & 0 deletions src/Resources/config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ parameters:
bitbag_es_pagination_available_page_limits: [9, 18, 36]
bitbag_es_pagination_default_limit: 9
bitbag_es_fuzziness: AUTO
bitbag_es_facets_auto_discover: true
bitbag_es_excluded_facet_attributes: []
bitbag_es_excluded_facet_options: []

fos_elastica:
clients:
Expand Down
4 changes: 4 additions & 0 deletions src/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,9 @@
<service id="bitbag.sylius_elasticsearch_plugin.refresher.resource" class="BitBag\SyliusElasticsearchPlugin\Refresher\ResourceRefresher">
<argument type="service" id="service_container" />
</service>

<service id="bitbag.sylius_elasticsearch_plugin.repository.product_option_repository" class="BitBag\SyliusElasticsearchPlugin\Repository\ProductOptionRepository">
<argument type="service" id="sylius.repository.product_option" />
</service>
</services>
</container>
Loading

0 comments on commit 54e54af

Please sign in to comment.