Skip to content

Commit acfd4b8

Browse files
loevgaardvvasiloi
andauthored
Adding taxon search feature (#45)
* Adding taxon search feature --------- Co-authored-by: Victor Vasiloi <[email protected]>
1 parent 5cc221d commit acfd4b8

File tree

18 files changed

+222
-15
lines changed

18 files changed

+222
-15
lines changed

.github/workflows/build.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ jobs:
298298
dependency-versions: "${{ matrix.dependencies }}"
299299

300300
- name: "Run infection"
301-
run: "vendor/bin/infection --test-framework-options=\"--testsuite Unit\""
301+
run: "vendor/bin/infection --test-framework-options=\"--testsuite=Unit\""
302302
env:
303303
STRYKER_DASHBOARD_API_KEY: "${{ secrets.STRYKER_DASHBOARD_API_KEY }}"
304304

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"symfony/options-resolver": "^5.4 || ^6.4 || ^7.0",
5252
"symfony/routing": "^5.4 || ^6.4 || ^7.0",
5353
"symfony/serializer": "^5.4 || ^6.4 || ^7.0",
54+
"symfony/service-contracts": "^1.1 || ^2.0 || ^3.3",
5455
"symfony/string": "^5.4 || ^6.4 || ^7.0",
5556
"symfony/translation-contracts": "^1.1 || ^2.5 || ^3.2",
5657
"symfony/validator": "^5.4 || ^6.4 || ^7.0",

src/Controller/Action/SearchAction.php

+15-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
namespace Setono\SyliusMeilisearchPlugin\Controller\Action;
66

77
use Doctrine\Persistence\ManagerRegistry;
8+
use Psr\EventDispatcher\EventDispatcherInterface;
89
use Setono\Doctrine\ORMTrait;
910
use Setono\SyliusMeilisearchPlugin\Engine\SearchEngineInterface;
1011
use Setono\SyliusMeilisearchPlugin\Engine\SearchRequest;
12+
use Setono\SyliusMeilisearchPlugin\Event\Search\SearchRequestCreated;
13+
use Setono\SyliusMeilisearchPlugin\Event\Search\SearchResponseParametersCreated;
14+
use Setono\SyliusMeilisearchPlugin\Event\Search\SearchResultReceived;
1115
use Setono\SyliusMeilisearchPlugin\Form\Builder\SearchFormBuilderInterface;
1216
use Setono\SyliusMeilisearchPlugin\Model\IndexableInterface;
1317
use Symfony\Component\HttpFoundation\Request;
@@ -23,13 +27,18 @@ public function __construct(
2327
private readonly Environment $twig,
2428
private readonly SearchFormBuilderInterface $searchFormBuilder,
2529
private readonly SearchEngineInterface $searchEngine,
30+
private readonly EventDispatcherInterface $eventDispatcher,
2631
) {
2732
$this->managerRegistry = $managerRegistry;
2833
}
2934

3035
public function __invoke(Request $request): Response
3136
{
32-
$searchResult = $this->searchEngine->execute(SearchRequest::fromRequest($request));
37+
$searchRequestCreatedEvent = new SearchRequestCreated($request, SearchRequest::fromRequest($request));
38+
$this->eventDispatcher->dispatch($searchRequestCreatedEvent);
39+
40+
$searchResult = $this->searchEngine->execute($searchRequestCreatedEvent->searchRequest);
41+
$this->eventDispatcher->dispatch(new SearchResultReceived($searchResult));
3342

3443
$searchForm = $this->searchFormBuilder->build($searchResult);
3544
$searchForm->handleRequest($request);
@@ -49,10 +58,13 @@ public function __invoke(Request $request): Response
4958
$items[] = $item;
5059
}
5160

52-
return new Response($this->twig->render('@SetonoSyliusMeilisearchPlugin/search/index.html.twig', [
61+
$searchResponseParametersCreatedEvent = new SearchResponseParametersCreated('@SetonoSyliusMeilisearchPlugin/search/index.html.twig', [
5362
'searchResult' => $searchResult,
5463
'searchForm' => $searchForm->createView(),
5564
'items' => $items,
56-
]));
65+
], $request);
66+
$this->eventDispatcher->dispatch($searchResponseParametersCreatedEvent);
67+
68+
return new Response($this->twig->render($searchResponseParametersCreatedEvent->template, $searchResponseParametersCreatedEvent->context));
5769
}
5870
}

src/DependencyInjection/Configuration.php

+16-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public function getConfigTreeBuilder(): TreeBuilder
109109
->end()
110110
->arrayNode('search')
111111
->canBeEnabled()
112-
->info('Configures your site search (and autocomplete) experience')
112+
->info('Configures your site search experience')
113113
->children()
114114
->scalarNode('path')
115115
->defaultValue('search')
@@ -125,6 +125,21 @@ public function getConfigTreeBuilder(): TreeBuilder
125125
->info('The index to search (must be configured in setono_sylius_meilisearch.indexes)')
126126
->cannotBeEmpty()
127127
->end()
128+
->arrayNode('taxon')
129+
->canBeDisabled()
130+
->info('Configures the taxon search feature')
131+
->children()
132+
->scalarNode('path')
133+
->defaultValue('taxons/{slug}')
134+
->info('This is the path where taxon searches are displayed')
135+
->cannotBeEmpty()
136+
->validate()
137+
->ifTrue(static fn (string $path): bool => !str_contains($path, '{slug}'))
138+
->thenInvalid('The path must contain the {slug} placeholder')
139+
->end()
140+
->end()
141+
->end()
142+
->end()
128143
->end()
129144
->end()
130145
->arrayNode('autocomplete')

src/DependencyInjection/SetonoSyliusMeilisearchExtension.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public function load(array $configs, ContainerBuilder $container): void
5151
* indexes: array<string, array{document: class-string<Document>, entities: list<class-string>, data_provider: class-string, indexer: class-string|null, prefix: string|null, default_filters: array<string, bool>}>,
5252
* server: array{ host: string, master_key: string, search_key: string },
5353
* metadata: array{ cache: bool },
54-
* search: array{ enabled: bool, path: string, index: string, hits_per_page: int },
54+
* search: array{ enabled: bool, path: string, index: string, hits_per_page: int, taxon: array{ path: string } },
5555
* autocomplete: array{ enabled: bool, indexes: list<string>, container: string, placeholder: string },
5656
* resources: array,
5757
* } $config
@@ -330,13 +330,14 @@ private static function registerDefaultIndexer(ContainerBuilder $container, stri
330330
/**
331331
* todo the search controller should only be available when search is enabled
332332
*
333-
* @param array{ enabled: bool, path: string, index: string, hits_per_page: int } $config the search configuration
333+
* @param array{ enabled: bool, path: string, index: string, hits_per_page: int, taxon: array{ path: string } } $config the search configuration
334334
* @param list<string> $indexes a list of index names
335335
*/
336336
private static function registerSearchConfiguration(array $config, array $indexes, ContainerBuilder $container, LoaderInterface $loader): void
337337
{
338338
$container->setParameter('setono_sylius_meilisearch.search.enabled', $config['enabled']);
339339
$container->setParameter('setono_sylius_meilisearch.search.path', $config['path']); // The route that uses this parameter is defined even if search is disabled
340+
$container->setParameter('setono_sylius_meilisearch.search.taxon.path', $config['taxon']['path']); // The route that uses this parameter is defined even if search is disabled
340341

341342
if (!$config['enabled']) {
342343
return;

src/Engine/SearchRequest.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ final class SearchRequest
1818

1919
// todo we need the hits per page here
2020
public function __construct(
21-
public readonly ?string $query,
21+
public ?string $query,
2222
/** @var array<string, mixed> $filters */
23-
public readonly array $filters = [],
24-
public readonly int $page = 1,
25-
public readonly ?string $sort = null,
23+
public array $filters = [],
24+
public int $page = 1,
25+
public ?string $sort = null,
2626
) {
2727
}
2828

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Setono\SyliusMeilisearchPlugin\Event\Search;
6+
7+
final class SearchFiltersBuilt
8+
{
9+
public function __construct(
10+
/** @var list<string> $filters */
11+
public array $filters,
12+
) {
13+
}
14+
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Setono\SyliusMeilisearchPlugin\Event\Search;
6+
7+
use Setono\SyliusMeilisearchPlugin\Engine\SearchRequest;
8+
use Symfony\Component\HttpFoundation\Request;
9+
10+
final class SearchRequestCreated
11+
{
12+
public function __construct(public readonly Request $request, public SearchRequest $searchRequest)
13+
{
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Setono\SyliusMeilisearchPlugin\Event\Search;
6+
7+
use Symfony\Component\HttpFoundation\Request;
8+
9+
final class SearchResponseParametersCreated
10+
{
11+
public function __construct(
12+
/**
13+
* @var non-empty-string
14+
*
15+
* The Twig template to render
16+
*/
17+
public string $template,
18+
19+
/**
20+
* @var array<string, mixed>
21+
*
22+
* The context to render the Twig template with
23+
*/
24+
public array $context,
25+
public readonly Request $request,
26+
) {
27+
}
28+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Setono\SyliusMeilisearchPlugin\Event\Search;
6+
7+
use Meilisearch\Search\SearchResult;
8+
9+
final class SearchResultReceived
10+
{
11+
public function __construct(public readonly SearchResult $searchResult)
12+
{
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Setono\SyliusMeilisearchPlugin\EventSubscriber\Search;
6+
7+
use Setono\SyliusMeilisearchPlugin\Event\Search\SearchFiltersBuilt;
8+
use Setono\SyliusMeilisearchPlugin\Event\Search\SearchRequestCreated;
9+
use Setono\SyliusMeilisearchPlugin\Event\Search\SearchResponseParametersCreated;
10+
use Sylius\Component\Locale\Context\LocaleContextInterface;
11+
use Sylius\Component\Taxonomy\Model\TaxonInterface;
12+
use Sylius\Component\Taxonomy\Repository\TaxonRepositoryInterface;
13+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
14+
use Symfony\Contracts\Service\ResetInterface;
15+
16+
final class TaxonSearchSubscriber implements EventSubscriberInterface, ResetInterface
17+
{
18+
private ?TaxonInterface $taxon = null;
19+
20+
public function __construct(
21+
private readonly TaxonRepositoryInterface $taxonRepository,
22+
private readonly LocaleContextInterface $localeContext,
23+
) {
24+
}
25+
26+
public static function getSubscribedEvents(): array
27+
{
28+
return [
29+
SearchRequestCreated::class => 'setTaxon',
30+
SearchResponseParametersCreated::class => 'updateResponseContext',
31+
SearchFiltersBuilt::class => 'updateFilters',
32+
];
33+
}
34+
35+
public function setTaxon(SearchRequestCreated $event): void
36+
{
37+
$route = $event->request->attributes->get('_route');
38+
if ('setono_sylius_meilisearch_shop_taxon' !== $route) {
39+
return;
40+
}
41+
42+
$slug = $event->request->attributes->get('slug');
43+
if (!is_string($slug) || '' === $slug) {
44+
return;
45+
}
46+
47+
$this->taxon = $this->taxonRepository->findOneBySlug($slug, $this->localeContext->getLocaleCode());
48+
}
49+
50+
public function updateFilters(SearchFiltersBuilt $event): void
51+
{
52+
if (null === $this->taxon) {
53+
return;
54+
}
55+
56+
$event->filters[] = sprintf('taxonCodes = "%s"', (string) $this->taxon->getCode());
57+
}
58+
59+
public function updateResponseContext(SearchResponseParametersCreated $event): void
60+
{
61+
if (null === $this->taxon) {
62+
return;
63+
}
64+
65+
$event->context['taxon'] = $this->taxon;
66+
}
67+
68+
public function reset(): void
69+
{
70+
$this->taxon = null;
71+
}
72+
}

src/Meilisearch/Filter/CompositeFilterBuilder.php

+10-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@
44

55
namespace Setono\SyliusMeilisearchPlugin\Meilisearch\Filter;
66

7+
use Psr\EventDispatcher\EventDispatcherInterface;
78
use Setono\CompositeCompilerPass\CompositeService;
9+
use Setono\SyliusMeilisearchPlugin\Event\Search\SearchFiltersBuilt;
810

911
/** @extends CompositeService<FilterBuilderInterface> */
1012
final class CompositeFilterBuilder extends CompositeService implements FilterBuilderInterface
1113
{
14+
public function __construct(private readonly EventDispatcherInterface $eventDispatcher)
15+
{
16+
}
17+
1218
public function build(array $facets, array $facetsValues): array
1319
{
1420
$filters = [];
@@ -17,6 +23,9 @@ public function build(array $facets, array $facetsValues): array
1723
$filters[] = $filterBuilder->build($facets, $facetsValues);
1824
}
1925

20-
return array_merge(...$filters);
26+
$searchFiltersBuiltEvent = new SearchFiltersBuilt(array_merge(...$filters));
27+
$this->eventDispatcher->dispatch($searchFiltersBuiltEvent);
28+
29+
return $searchFiltersBuiltEvent->filters;
2130
}
2231
}

src/Resources/config/routes/shop.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,10 @@ setono_sylius_meilisearch_shop_search_widget:
77
path: '%setono_sylius_meilisearch.search.path%/widget'
88
methods: [GET]
99
controller: Setono\SyliusMeilisearchPlugin\Controller\Action\SearchWidgetAction
10+
11+
setono_sylius_meilisearch_shop_taxon:
12+
path: '%setono_sylius_meilisearch.search.taxon.path%'
13+
methods: [GET]
14+
controller: Setono\SyliusMeilisearchPlugin\Controller\Action\SearchAction
15+
requirements:
16+
slug: .+(?<!/)

src/Resources/config/services/conditional/search.xml

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<argument type="service" id="twig"/>
1313
<argument type="service" id="Setono\SyliusMeilisearchPlugin\Form\Builder\SearchFormBuilderInterface"/>
1414
<argument type="service" id="Setono\SyliusMeilisearchPlugin\Engine\SearchEngineInterface"/>
15+
<argument type="service" id="event_dispatcher"/>
1516
</service>
1617

1718
<service id="Setono\SyliusMeilisearchPlugin\Engine\SearchEngine">

src/Resources/config/services/event_subscriber.xml

+7
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,12 @@
55
<service id="Setono\SyliusMeilisearchPlugin\EventSubscriber\AddMenuSubscriber">
66
<tag name="kernel.event_subscriber"/>
77
</service>
8+
9+
<service id="Setono\SyliusMeilisearchPlugin\EventSubscriber\Search\TaxonSearchSubscriber">
10+
<argument type="service" id="sylius.repository.taxon"/>
11+
<argument type="service" id="sylius.context.locale"/>
12+
13+
<tag name="kernel.event_subscriber"/>
14+
</service>
815
</services>
916
</container>

src/Resources/config/services/meilisearch.xml

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
alias="Setono\SyliusMeilisearchPlugin\Meilisearch\Filter\CompositeFilterBuilder"
99
/>
1010

11-
<service id="Setono\SyliusMeilisearchPlugin\Meilisearch\Filter\CompositeFilterBuilder"/>
11+
<service id="Setono\SyliusMeilisearchPlugin\Meilisearch\Filter\CompositeFilterBuilder">
12+
<argument type="service" id="event_dispatcher"/>
13+
</service>
1214

1315
<service
1416
id="Setono\SyliusMeilisearchPlugin\Meilisearch\Query\SearchQueryBuilderInterface"
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
<h1>{{ 'setono_sylius_meilisearch.ui.search_results_for'|trans }} "{{ app.request.query.get('q') }}"</h1>
1+
{% if taxon is defined%}
2+
{{ include("@SyliusShop/Taxon/_header.html.twig") }}
3+
{% else %}
4+
<h1>{{ 'setono_sylius_meilisearch.ui.search_results_for'|trans }} "{{ app.request.query.get('q') }}"</h1>
5+
{% endif %}

tests/Unit/Meilisearch/Builder/CompositeFilterBuilderTest.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
namespace Setono\SyliusMeilisearchPlugin\Tests\Unit\Meilisearch\Builder;
66

77
use PHPUnit\Framework\TestCase;
8+
use Prophecy\PhpUnit\ProphecyTrait;
9+
use Psr\EventDispatcher\EventDispatcherInterface;
810
use Setono\SyliusMeilisearchPlugin\Document\Metadata\Facet;
911
use Setono\SyliusMeilisearchPlugin\Meilisearch\Filter\ArrayFilterBuilder;
1012
use Setono\SyliusMeilisearchPlugin\Meilisearch\Filter\BooleanFilterBuilder;
@@ -13,9 +15,12 @@
1315

1416
final class CompositeFilterBuilderTest extends TestCase
1517
{
18+
use ProphecyTrait;
19+
1620
public function test_it_returns_filters(): void
1721
{
18-
$compositeFilterBuilder = new CompositeFilterBuilder();
22+
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
23+
$compositeFilterBuilder = new CompositeFilterBuilder($eventDispatcher->reveal());
1924
$compositeFilterBuilder->add(new ArrayFilterBuilder());
2025
$compositeFilterBuilder->add(new BooleanFilterBuilder());
2126
$compositeFilterBuilder->add(new FloatFilterBuilder());

0 commit comments

Comments
 (0)