diff --git a/src/Autocomplete/CHANGELOG.md b/src/Autocomplete/CHANGELOG.md index 1f8cea05633..8720236c2bb 100644 --- a/src/Autocomplete/CHANGELOG.md +++ b/src/Autocomplete/CHANGELOG.md @@ -4,6 +4,7 @@ - Deprecate `ExtraLazyChoiceLoader` in favor of `Symfony\Component\Form\ChoiceList\Loader\LazyChoiceLoader` - Reset TomSelect when updating url attribute #1505 +- Add `getAttributes()` method to define additional attributes for autocomplete results #2541 ## 2.22.0 diff --git a/src/Autocomplete/src/AutocompleteResultsExecutor.php b/src/Autocomplete/src/AutocompleteResultsExecutor.php index 0061ca8a55b..03d10e03466 100644 --- a/src/Autocomplete/src/AutocompleteResultsExecutor.php +++ b/src/Autocomplete/src/AutocompleteResultsExecutor.php @@ -73,10 +73,7 @@ public function fetchResults(EntityAutocompleterInterface $autocompleter, string if (!method_exists($autocompleter, 'getGroupBy') || null === $groupBy = $autocompleter->getGroupBy()) { foreach ($paginator as $entity) { - $results[] = [ - 'value' => $autocompleter->getValue($entity), - 'text' => $autocompleter->getLabel($entity), - ]; + $results[] = $this->formatResult($autocompleter, $entity); } return new AutocompleteResults($results, $hasNextPage); @@ -104,10 +101,7 @@ public function fetchResults(EntityAutocompleterInterface $autocompleter, string $optgroupLabels = []; foreach ($paginator as $entity) { - $result = [ - 'value' => $autocompleter->getValue($entity), - 'text' => $autocompleter->getLabel($entity), - ]; + $result = $this->formatResult($autocompleter, $entity); $groupLabels = $groupBy($entity, $result['value'], $result['text']); @@ -124,4 +118,21 @@ public function fetchResults(EntityAutocompleterInterface $autocompleter, string return new AutocompleteResults($results, $hasNextPage, $optgroups); } + + /** + * @return array + */ + private function formatResult(EntityAutocompleterInterface $autocompleter, object $entity): array + { + $attributes = []; + if (method_exists($autocompleter, 'getAttributes')) { + $attributes = $autocompleter->getAttributes($entity); + } + + return [ + ...$attributes, + 'value' => $autocompleter->getValue($entity), + 'text' => $autocompleter->getLabel($entity), + ]; + } } diff --git a/src/Autocomplete/src/EntityAutocompleterInterface.php b/src/Autocomplete/src/EntityAutocompleterInterface.php index df0874fd91c..4ad8a855f18 100644 --- a/src/Autocomplete/src/EntityAutocompleterInterface.php +++ b/src/Autocomplete/src/EntityAutocompleterInterface.php @@ -18,30 +18,50 @@ /** * Interface for classes that will have an "autocomplete" endpoint exposed. * - * @method mixed getGroupBy() Return group_by option. + * @template T of object + * + * TODO Remove next lines for Symfony UX 3 + * + * @method array getAttributes(object $entity) Returns extra attributes to add to the autocomplete result. + * @method mixed getGroupBy() Return group_by option. */ interface EntityAutocompleterInterface { /** * The fully-qualified entity class this will be autocompleting. + * + * @return class-string */ public function getEntityClass(): string; /** * Create a query builder that filters for the given "query". + * + * @param EntityRepository $repository */ public function createFilteredQueryBuilder(EntityRepository $repository, string $query): QueryBuilder; /** * Returns the "choice_label" used to display this entity. + * + * @param T $entity */ public function getLabel(object $entity): string; /** * Returns the "value" attribute for this entity, usually the id. + * + * @param T $entity */ public function getValue(object $entity): mixed; + /** + * Returns extra attributes to add to the autocomplete result. + * + * TODO Uncomment for Symfony UX 3 + */ + /* public function getAttributes(object $entity): array; */ + /** * Return true if access should be granted to the autocomplete results for the current user. * @@ -51,6 +71,8 @@ public function isGranted(Security $security): bool; /* * Return group_by option. + * + * TODO Uncomment for Symfony UX 3 */ /* public function getGroupBy(): mixed; */ } diff --git a/src/Autocomplete/tests/Fixtures/Autocompleter/CustomAttributesProductAutocompleter.php b/src/Autocomplete/tests/Fixtures/Autocompleter/CustomAttributesProductAutocompleter.php new file mode 100644 index 00000000000..7f86431ae8a --- /dev/null +++ b/src/Autocomplete/tests/Fixtures/Autocompleter/CustomAttributesProductAutocompleter.php @@ -0,0 +1,17 @@ + true, + 'value' => 'This value should be replaced with the result of getValue()', + 'text' => 'This value should be replaced with the result of getText()', + ]; + } +} diff --git a/src/Autocomplete/tests/Fixtures/Autocompleter/CustomProductAutocompleter.php b/src/Autocomplete/tests/Fixtures/Autocompleter/CustomProductAutocompleter.php index 912a864c0e9..09b5b96c993 100644 --- a/src/Autocomplete/tests/Fixtures/Autocompleter/CustomProductAutocompleter.php +++ b/src/Autocomplete/tests/Fixtures/Autocompleter/CustomProductAutocompleter.php @@ -19,6 +19,9 @@ use Symfony\UX\Autocomplete\EntityAutocompleterInterface; use Symfony\UX\Autocomplete\Tests\Fixtures\Entity\Product; +/** + * @implements EntityAutocompleterInterface + */ class CustomProductAutocompleter implements EntityAutocompleterInterface { public function __construct( @@ -58,6 +61,11 @@ public function getValue(object $entity): mixed return $entity->getId(); } + public function getAttributes(object $entity): array + { + return []; + } + public function isGranted(Security $security): bool { if ($this->requestStack->getCurrentRequest()?->query->get('enforce_test_security')) { diff --git a/src/Autocomplete/tests/Fixtures/Kernel.php b/src/Autocomplete/tests/Fixtures/Kernel.php index f1991a22c39..de2877b0a2a 100644 --- a/src/Autocomplete/tests/Fixtures/Kernel.php +++ b/src/Autocomplete/tests/Fixtures/Kernel.php @@ -34,6 +34,7 @@ use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\UX\Autocomplete\AutocompleteBundle; use Symfony\UX\Autocomplete\DependencyInjection\AutocompleteFormTypePass; +use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomAttributesProductAutocompleter; use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomGroupByProductAutocompleter; use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomProductAutocompleter; use Symfony\UX\Autocomplete\Tests\Fixtures\Form\ProductType; @@ -176,6 +177,13 @@ protected function configureContainer(ContainerConfigurator $c): void 'alias' => 'custom_group_by_product', ]); + $services->set(CustomAttributesProductAutocompleter::class) + ->public() + ->arg(1, new Reference('ux.autocomplete.entity_search_util')) + ->tag(AutocompleteFormTypePass::ENTITY_AUTOCOMPLETER_TAG, [ + 'alias' => 'custom_attributes_product', + ]); + $services->alias('public.results_executor', 'ux.autocomplete.results_executor') ->public(); diff --git a/src/Autocomplete/tests/Integration/AutocompleteResultsExecutorTest.php b/src/Autocomplete/tests/Integration/AutocompleteResultsExecutorTest.php new file mode 100644 index 00000000000..b804afd1123 --- /dev/null +++ b/src/Autocomplete/tests/Integration/AutocompleteResultsExecutorTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Autocomplete\Tests\Integration; + +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\UX\Autocomplete\AutocompleteResultsExecutor; +use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomAttributesProductAutocompleter; +use Symfony\UX\Autocomplete\Tests\Fixtures\Factory\ProductFactory; +use Symfony\UX\Autocomplete\Tests\Fixtures\Kernel; +use Zenstruck\Foundry\Test\Factories; +use Zenstruck\Foundry\Test\ResetDatabase; + +class AutocompleteResultsExecutorTest extends KernelTestCase +{ + use Factories; + use ResetDatabase; + + public function testItReturnsExtraAttributes(): void + { + $kernel = new Kernel('test', true); + $kernel->disableForms(); + $kernel->boot(); + + $product = ProductFactory::createOne(['name' => 'Foo']); + + /** @var AutocompleteResultsExecutor $executor */ + $executor = $kernel->getContainer()->get('public.results_executor'); + $autocompleter = $kernel->getContainer()->get(CustomAttributesProductAutocompleter::class); + $data = $executor->fetchResults($autocompleter, '', 1); + $this->assertCount(1, $data->results); + $this->assertSame(['disabled' => true, 'value' => $product->getId(), 'text' => 'Foo'], $data->results[0]); + } +} diff --git a/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php b/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php index 6bae36f9251..097e4d770f7 100644 --- a/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php +++ b/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php @@ -18,6 +18,7 @@ use Symfony\UX\LiveComponent\Test\InteractsWithLiveComponents; use Symfony\UX\LiveComponent\Tests\Fixtures\Component\Component2; use Symfony\UX\LiveComponent\Tests\Fixtures\Factory\CategoryFixtureEntityFactory; +use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; /** @@ -25,6 +26,7 @@ */ final class InteractsWithLiveComponentsTest extends KernelTestCase { + use Factories; use InteractsWithLiveComponents; use ResetDatabase; diff --git a/src/LiveComponent/tests/Unit/Form/ComponentWithFormTest.php b/src/LiveComponent/tests/Unit/Form/ComponentWithFormTest.php index a7062605e48..01941f10656 100644 --- a/src/LiveComponent/tests/Unit/Form/ComponentWithFormTest.php +++ b/src/LiveComponent/tests/Unit/Form/ComponentWithFormTest.php @@ -14,6 +14,7 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\UX\LiveComponent\Tests\Fixtures\Component\FormComponentWithManyDifferentFieldsType; use Symfony\UX\LiveComponent\Tests\Fixtures\Factory\CategoryFixtureEntityFactory; +use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; /** @@ -21,6 +22,7 @@ */ class ComponentWithFormTest extends KernelTestCase { + use Factories; use ResetDatabase; public function testFormValues(): void