Skip to content

Commit df6d50b

Browse files
committed
feature #2541 [Autocomplete] Configurable results (J-Ben87)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [Autocomplete] Configurable results | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Issues | | License | MIT TomSelect accepts by default an array of results, each item containing a "text" and a "value". Therefore this bundle exposes results formatted like so. However one may need to expose additional data such as a "disabled" state to customize TomSelect's rendering. This PR aims to offer this possibility by letting `EntityAutocompleter`s drive the creation of the results array. Commits ------- 44c0e1d [Autocomplete] Configurable results
2 parents 9b322da + 44c0e1d commit df6d50b

9 files changed

+124
-9
lines changed

src/Autocomplete/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- Deprecate `ExtraLazyChoiceLoader` in favor of `Symfony\Component\Form\ChoiceList\Loader\LazyChoiceLoader`
66
- Reset TomSelect when updating url attribute #1505
7+
- Add `getAttributes()` method to define additional attributes for autocomplete results #2541
78

89
## 2.22.0
910

src/Autocomplete/src/AutocompleteResultsExecutor.php

+19-8
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,7 @@ public function fetchResults(EntityAutocompleterInterface $autocompleter, string
7373

7474
if (!method_exists($autocompleter, 'getGroupBy') || null === $groupBy = $autocompleter->getGroupBy()) {
7575
foreach ($paginator as $entity) {
76-
$results[] = [
77-
'value' => $autocompleter->getValue($entity),
78-
'text' => $autocompleter->getLabel($entity),
79-
];
76+
$results[] = $this->formatResult($autocompleter, $entity);
8077
}
8178

8279
return new AutocompleteResults($results, $hasNextPage);
@@ -104,10 +101,7 @@ public function fetchResults(EntityAutocompleterInterface $autocompleter, string
104101
$optgroupLabels = [];
105102

106103
foreach ($paginator as $entity) {
107-
$result = [
108-
'value' => $autocompleter->getValue($entity),
109-
'text' => $autocompleter->getLabel($entity),
110-
];
104+
$result = $this->formatResult($autocompleter, $entity);
111105

112106
$groupLabels = $groupBy($entity, $result['value'], $result['text']);
113107

@@ -124,4 +118,21 @@ public function fetchResults(EntityAutocompleterInterface $autocompleter, string
124118

125119
return new AutocompleteResults($results, $hasNextPage, $optgroups);
126120
}
121+
122+
/**
123+
* @return array<string, mixed>
124+
*/
125+
private function formatResult(EntityAutocompleterInterface $autocompleter, object $entity): array
126+
{
127+
$attributes = [];
128+
if (method_exists($autocompleter, 'getAttributes')) {
129+
$attributes = $autocompleter->getAttributes($entity);
130+
}
131+
132+
return [
133+
...$attributes,
134+
'value' => $autocompleter->getValue($entity),
135+
'text' => $autocompleter->getLabel($entity),
136+
];
137+
}
127138
}

src/Autocomplete/src/EntityAutocompleterInterface.php

+23-1
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,50 @@
1818
/**
1919
* Interface for classes that will have an "autocomplete" endpoint exposed.
2020
*
21-
* @method mixed getGroupBy() Return group_by option.
21+
* @template T of object
22+
*
23+
* TODO Remove next lines for Symfony UX 3
24+
*
25+
* @method array getAttributes(object $entity) Returns extra attributes to add to the autocomplete result.
26+
* @method mixed getGroupBy() Return group_by option.
2227
*/
2328
interface EntityAutocompleterInterface
2429
{
2530
/**
2631
* The fully-qualified entity class this will be autocompleting.
32+
*
33+
* @return class-string<T>
2734
*/
2835
public function getEntityClass(): string;
2936

3037
/**
3138
* Create a query builder that filters for the given "query".
39+
*
40+
* @param EntityRepository<T> $repository
3241
*/
3342
public function createFilteredQueryBuilder(EntityRepository $repository, string $query): QueryBuilder;
3443

3544
/**
3645
* Returns the "choice_label" used to display this entity.
46+
*
47+
* @param T $entity
3748
*/
3849
public function getLabel(object $entity): string;
3950

4051
/**
4152
* Returns the "value" attribute for this entity, usually the id.
53+
*
54+
* @param T $entity
4255
*/
4356
public function getValue(object $entity): mixed;
4457

58+
/**
59+
* Returns extra attributes to add to the autocomplete result.
60+
*
61+
* TODO Uncomment for Symfony UX 3
62+
*/
63+
/* public function getAttributes(object $entity): array; */
64+
4565
/**
4666
* Return true if access should be granted to the autocomplete results for the current user.
4767
*
@@ -51,6 +71,8 @@ public function isGranted(Security $security): bool;
5171

5272
/*
5373
* Return group_by option.
74+
*
75+
* TODO Uncomment for Symfony UX 3
5476
*/
5577
/* public function getGroupBy(): mixed; */
5678
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter;
6+
7+
class CustomAttributesProductAutocompleter extends CustomProductAutocompleter
8+
{
9+
public function getAttributes(object $entity): array
10+
{
11+
return [
12+
'disabled' => true,
13+
'value' => 'This value should be replaced with the result of getValue()',
14+
'text' => 'This value should be replaced with the result of getText()',
15+
];
16+
}
17+
}

src/Autocomplete/tests/Fixtures/Autocompleter/CustomProductAutocompleter.php

+8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
use Symfony\UX\Autocomplete\EntityAutocompleterInterface;
2020
use Symfony\UX\Autocomplete\Tests\Fixtures\Entity\Product;
2121

22+
/**
23+
* @implements EntityAutocompleterInterface<Product>
24+
*/
2225
class CustomProductAutocompleter implements EntityAutocompleterInterface
2326
{
2427
public function __construct(
@@ -58,6 +61,11 @@ public function getValue(object $entity): mixed
5861
return $entity->getId();
5962
}
6063

64+
public function getAttributes(object $entity): array
65+
{
66+
return [];
67+
}
68+
6169
public function isGranted(Security $security): bool
6270
{
6371
if ($this->requestStack->getCurrentRequest()?->query->get('enforce_test_security')) {

src/Autocomplete/tests/Fixtures/Kernel.php

+8
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
3535
use Symfony\UX\Autocomplete\AutocompleteBundle;
3636
use Symfony\UX\Autocomplete\DependencyInjection\AutocompleteFormTypePass;
37+
use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomAttributesProductAutocompleter;
3738
use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomGroupByProductAutocompleter;
3839
use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomProductAutocompleter;
3940
use Symfony\UX\Autocomplete\Tests\Fixtures\Form\ProductType;
@@ -176,6 +177,13 @@ protected function configureContainer(ContainerConfigurator $c): void
176177
'alias' => 'custom_group_by_product',
177178
]);
178179

180+
$services->set(CustomAttributesProductAutocompleter::class)
181+
->public()
182+
->arg(1, new Reference('ux.autocomplete.entity_search_util'))
183+
->tag(AutocompleteFormTypePass::ENTITY_AUTOCOMPLETER_TAG, [
184+
'alias' => 'custom_attributes_product',
185+
]);
186+
179187
$services->alias('public.results_executor', 'ux.autocomplete.results_executor')
180188
->public();
181189

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Symfony package.
7+
*
8+
* (c) Fabien Potencier <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Symfony\UX\Autocomplete\Tests\Integration;
15+
16+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
17+
use Symfony\UX\Autocomplete\AutocompleteResultsExecutor;
18+
use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomAttributesProductAutocompleter;
19+
use Symfony\UX\Autocomplete\Tests\Fixtures\Factory\ProductFactory;
20+
use Symfony\UX\Autocomplete\Tests\Fixtures\Kernel;
21+
use Zenstruck\Foundry\Test\Factories;
22+
use Zenstruck\Foundry\Test\ResetDatabase;
23+
24+
class AutocompleteResultsExecutorTest extends KernelTestCase
25+
{
26+
use Factories;
27+
use ResetDatabase;
28+
29+
public function testItReturnsExtraAttributes(): void
30+
{
31+
$kernel = new Kernel('test', true);
32+
$kernel->disableForms();
33+
$kernel->boot();
34+
35+
$product = ProductFactory::createOne(['name' => 'Foo']);
36+
37+
/** @var AutocompleteResultsExecutor $executor */
38+
$executor = $kernel->getContainer()->get('public.results_executor');
39+
$autocompleter = $kernel->getContainer()->get(CustomAttributesProductAutocompleter::class);
40+
$data = $executor->fetchResults($autocompleter, '', 1);
41+
$this->assertCount(1, $data->results);
42+
$this->assertSame(['disabled' => true, 'value' => $product->getId(), 'text' => 'Foo'], $data->results[0]);
43+
}
44+
}

src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php

+2
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
use Symfony\UX\LiveComponent\Test\InteractsWithLiveComponents;
1919
use Symfony\UX\LiveComponent\Tests\Fixtures\Component\Component2;
2020
use Symfony\UX\LiveComponent\Tests\Fixtures\Factory\CategoryFixtureEntityFactory;
21+
use Zenstruck\Foundry\Test\Factories;
2122
use Zenstruck\Foundry\Test\ResetDatabase;
2223

2324
/**
2425
* @author Kevin Bond <[email protected]>
2526
*/
2627
final class InteractsWithLiveComponentsTest extends KernelTestCase
2728
{
29+
use Factories;
2830
use InteractsWithLiveComponents;
2931
use ResetDatabase;
3032

src/LiveComponent/tests/Unit/Form/ComponentWithFormTest.php

+2
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
1515
use Symfony\UX\LiveComponent\Tests\Fixtures\Component\FormComponentWithManyDifferentFieldsType;
1616
use Symfony\UX\LiveComponent\Tests\Fixtures\Factory\CategoryFixtureEntityFactory;
17+
use Zenstruck\Foundry\Test\Factories;
1718
use Zenstruck\Foundry\Test\ResetDatabase;
1819

1920
/**
2021
* @author Jakub Caban <[email protected]>
2122
*/
2223
class ComponentWithFormTest extends KernelTestCase
2324
{
25+
use Factories;
2426
use ResetDatabase;
2527

2628
public function testFormValues(): void

0 commit comments

Comments
 (0)