Skip to content

Commit 25daee6

Browse files
ycerutonorkunas
authored andcommitted
Keeping BC
1 parent 1575153 commit 25daee6

9 files changed

+180
-44
lines changed

src/Autocomplete/src/DependencyInjection/AutocompleteExtension.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Symfony\UX\Autocomplete\Doctrine\EntitySearchUtil;
2828
use Symfony\UX\Autocomplete\Form\AsEntityAutocompleteField;
2929
use Symfony\UX\Autocomplete\Form\AutocompleteChoiceTypeExtension;
30+
use Symfony\UX\Autocomplete\Form\BaseEntityAutocompleteType;
3031
use Symfony\UX\Autocomplete\Form\ParentEntityAutocompleteType;
3132
use Symfony\UX\Autocomplete\Form\WrappedEntityTypeAutocompleter;
3233
use Symfony\UX\Autocomplete\Maker\MakeAutocompleteField;
@@ -130,6 +131,13 @@ private function registerBasicServices(ContainerBuilder $container): void
130131

131132
private function registerFormServices(ContainerBuilder $container): void
132133
{
134+
$container
135+
->register('ux.autocomplete.base_entity_type', BaseEntityAutocompleteType::class)
136+
->setArguments([
137+
new Reference('router'),
138+
])
139+
->addTag('form.type');
140+
133141
$container
134142
->register('ux.autocomplete.entity_type', ParentEntityAutocompleteType::class)
135143
->setArguments([

src/Autocomplete/src/Form/AutocompleteChoiceTypeExtension.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ public function finishView(FormView $view, FormInterface $form, array $options):
5353
$attr['data-controller'] = trim(($attr['data-controller'] ?? '').' '.$controllerName);
5454

5555
$values = [];
56-
if ($form->getConfig()->hasAttribute('autocomplete_url')) {
56+
if ($options['autocomplete_url']) {
57+
$values['url'] = $options['autocomplete_url'];
58+
} elseif ($form->getConfig()->hasAttribute('autocomplete_url')) {
5759
$values['url'] = $form->getConfig()->getAttribute('autocomplete_url');
5860
}
5961

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\Autocomplete\Form;
13+
14+
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
15+
use Symfony\Component\Form\AbstractType;
16+
use Symfony\Component\Form\Exception\RuntimeException;
17+
use Symfony\Component\Form\FormBuilderInterface;
18+
use Symfony\Component\OptionsResolver\Options;
19+
use Symfony\Component\OptionsResolver\OptionsResolver;
20+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
21+
use Symfony\UX\Autocomplete\Form\ChoiceList\Loader\ExtraLazyChoiceLoader;
22+
23+
/**
24+
* All form types that want to expose autocomplete functionality should use this for its getParent().
25+
*/
26+
final class BaseEntityAutocompleteType extends AbstractType
27+
{
28+
public function __construct(
29+
private UrlGeneratorInterface $urlGenerator,
30+
) {
31+
}
32+
33+
public function buildForm(FormBuilderInterface $builder, array $options): void
34+
{
35+
$builder->setAttribute('autocomplete_url', $this->getAutocompleteUrl($builder, $options));
36+
}
37+
38+
public function configureOptions(OptionsResolver $resolver): void
39+
{
40+
$choiceLoader = static function (Options $options, $loader) {
41+
return new ExtraLazyChoiceLoader($loader);
42+
};
43+
44+
$resolver->setDefaults([
45+
'autocomplete' => true,
46+
'choice_loader' => $choiceLoader,
47+
// set to the fields to search on or null to search on all fields
48+
'searchable_fields' => null,
49+
// override the search logic - set to a callable:
50+
// function(QueryBuilder $qb, string $query, EntityRepository $repository) {
51+
// $qb->andWhere('entity.name LIKE :filter OR entity.description LIKE :filter')
52+
// ->setParameter('filter', '%'.$query.'%');
53+
// }
54+
'filter_query' => null,
55+
// set to the string role that's required to view the autocomplete results
56+
// or a callable: function(Symfony\Component\Security\Core\Security $security): bool
57+
'security' => false,
58+
// set the max results number that a query on automatic endpoint return.
59+
'max_results' => 10,
60+
]);
61+
62+
$resolver->setAllowedTypes('security', ['boolean', 'string', 'callable']);
63+
$resolver->setAllowedTypes('max_results', ['int', 'null']);
64+
$resolver->setAllowedTypes('filter_query', ['callable', 'null']);
65+
$resolver->setNormalizer('searchable_fields', function (Options $options, ?array $searchableFields) {
66+
if (null !== $searchableFields && null !== $options['filter_query']) {
67+
throw new RuntimeException('Both the searchable_fields and filter_query options cannot be set.');
68+
}
69+
70+
return $searchableFields;
71+
});
72+
}
73+
74+
public function getParent(): string
75+
{
76+
return EntityType::class;
77+
}
78+
79+
/**
80+
* Uses the provided URL, or auto-generate from the provided alias.
81+
*/
82+
private function getAutocompleteUrl(FormBuilderInterface $builder, array $options): string
83+
{
84+
if ($options['autocomplete_url']) {
85+
return $options['autocomplete_url'];
86+
}
87+
88+
$formType = $builder->getType()->getInnerType();
89+
$attribute = AsEntityAutocompleteField::getInstance($formType::class);
90+
91+
if (!$attribute) {
92+
throw new \LogicException(sprintf('You must either provide your own autocomplete_url, or add #[AsEntityAutocompleteField] attribute to %s.', $formType::class));
93+
}
94+
95+
return $this->urlGenerator->generate($attribute->getRoute(), [
96+
'alias' => $attribute->getAlias() ?: AsEntityAutocompleteField::shortName($formType::class),
97+
]);
98+
}
99+
}

src/Autocomplete/src/Form/ParentEntityAutocompleteType.php

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,64 @@
1111

1212
namespace Symfony\UX\Autocomplete\Form;
1313

14-
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
1514
use Symfony\Component\Form\AbstractType;
15+
use Symfony\Component\Form\DataMapperInterface;
1616
use Symfony\Component\Form\Exception\RuntimeException;
1717
use Symfony\Component\Form\FormBuilderInterface;
18+
use Symfony\Component\Form\FormInterface;
19+
use Symfony\Component\Form\FormView;
1820
use Symfony\Component\OptionsResolver\Options;
1921
use Symfony\Component\OptionsResolver\OptionsResolver;
2022
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
21-
use Symfony\UX\Autocomplete\Form\ChoiceList\Loader\ExtraLazyChoiceLoader;
2223

2324
/**
2425
* All form types that want to expose autocomplete functionality should use this for its getParent().
2526
*/
26-
final class ParentEntityAutocompleteType extends AbstractType
27+
final class ParentEntityAutocompleteType extends AbstractType implements DataMapperInterface
2728
{
2829
public function __construct(
29-
private UrlGeneratorInterface $urlGenerator,
30+
private UrlGeneratorInterface $urlGenerator
3031
) {
3132
}
3233

33-
public function buildForm(FormBuilderInterface $builder, array $options): void
34+
public function buildForm(FormBuilderInterface $builder, array $options)
3435
{
35-
$builder->setAttribute('autocomplete_url', $this->getAutocompleteUrl($builder, $options));
36+
$formType = $builder->getType()->getInnerType();
37+
$attribute = AsEntityAutocompleteField::getInstance($formType::class);
38+
39+
if (!$attribute && empty($options['autocomplete_url'])) {
40+
throw new \LogicException(sprintf('You must either provide your own autocomplete_url, or add #[AsEntityAutocompleteField] attribute to %s.', $formType::class));
41+
}
42+
43+
// Use the provided URL, or auto-generate from the provided alias
44+
$autocompleteUrl = $options['autocomplete_url'] ?? $this->urlGenerator->generate($attribute->getRoute(), [
45+
'alias' => $attribute->getAlias() ?: AsEntityAutocompleteField::shortName($formType::class),
46+
]);
47+
48+
$builder
49+
->addEventSubscriber(new AutocompleteEntityTypeSubscriber($autocompleteUrl))
50+
->setDataMapper($this);
3651
}
3752

38-
public function configureOptions(OptionsResolver $resolver): void
53+
public function finishView(FormView $view, FormInterface $form, array $options)
3954
{
40-
$choiceLoader = static function (Options $options, $loader) {
41-
return new ExtraLazyChoiceLoader($loader);
42-
};
55+
// Add a custom block prefix to inner field to ease theming:
56+
array_splice($view['autocomplete']->vars['block_prefixes'], -1, 0, 'ux_entity_autocomplete_inner');
57+
// this IS A compound (i.e. has children) field
58+
// however, we only render the child "autocomplete" field. So for rendering, fake NOT compound
59+
// This is a hack and we should check into removing it in the future
60+
$view->vars['compound'] = false;
61+
// the above, unfortunately, can also trick other things that might use
62+
// "compound" for other reasons. This, at least, leaves a hint.
63+
$view->vars['compound_data'] = true;
64+
}
4365

66+
public function configureOptions(OptionsResolver $resolver)
67+
{
4468
$resolver->setDefaults([
45-
'autocomplete' => true,
46-
'choice_loader' => $choiceLoader,
69+
'multiple' => false,
70+
// force display errors on this form field
71+
'error_bubbling' => false,
4772
// set to the fields to search on or null to search on all fields
4873
'searchable_fields' => null,
4974
// override the search logic - set to a callable:
@@ -59,6 +84,7 @@ public function configureOptions(OptionsResolver $resolver): void
5984
'max_results' => 10,
6085
]);
6186

87+
$resolver->setRequired(['class']);
6288
$resolver->setAllowedTypes('security', ['boolean', 'string', 'callable']);
6389
$resolver->setAllowedTypes('max_results', ['int', 'null']);
6490
$resolver->setAllowedTypes('filter_query', ['callable', 'null']);
@@ -71,29 +97,20 @@ public function configureOptions(OptionsResolver $resolver): void
7197
});
7298
}
7399

74-
public function getParent(): string
100+
public function getBlockPrefix(): string
75101
{
76-
return EntityType::class;
102+
return 'ux_entity_autocomplete';
77103
}
78104

79-
/**
80-
* Uses the provided URL, or auto-generate from the provided alias
81-
*/
82-
private function getAutocompleteUrl(FormBuilderInterface $builder, array $options): string
105+
public function mapDataToForms($data, $forms)
83106
{
84-
if ($options['autocomplete_url']) {
85-
return $options['autocomplete_url'];
86-
}
87-
88-
$formType = $builder->getType()->getInnerType();
89-
$attribute = AsEntityAutocompleteField::getInstance($formType::class);
90-
91-
if (!$attribute) {
92-
throw new \LogicException(sprintf('You must either provide your own autocomplete_url, or add #[AsEntityAutocompleteField] attribute to %s.', $formType::class));
93-
}
107+
$form = current(iterator_to_array($forms, false));
108+
$form->setData($data);
109+
}
94110

95-
return $this->urlGenerator->generate($attribute->getRoute(), [
96-
'alias' => $attribute->getAlias() ?: AsEntityAutocompleteField::shortName($formType::class),
97-
]);
111+
public function mapFormsToData($forms, &$data)
112+
{
113+
$form = current(iterator_to_array($forms, false));
114+
$data = $form->getData();
98115
}
99116
}

src/Autocomplete/src/Form/WrappedEntityTypeAutocompleter.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ public function __construct(
3636

3737
public function getEntityClass(): string
3838
{
39-
return $this->getForm()->getConfig()->getOption('class');
39+
return $this->getFormOption('class');
4040
}
4141

4242
public function createFilteredQueryBuilder(EntityRepository $repository, string $query): QueryBuilder
4343
{
44-
$queryBuilder = $this->getForm()->getConfig()->getOption('query_builder');
44+
$queryBuilder = $this->getFormOption('query_builder');
4545
$queryBuilder = $queryBuilder ?: $repository->createQueryBuilder('entity');
4646

4747
if ($filterQuery = $this->getFilterQuery()) {
@@ -70,7 +70,7 @@ public function createFilteredQueryBuilder(EntityRepository $repository, string
7070

7171
public function getLabel(object $entity): string
7272
{
73-
$choiceLabel = $this->getForm()->getConfig()->getOption('choice_label');
73+
$choiceLabel = $this->getFormOption('choice_label');
7474

7575
if (null === $choiceLabel) {
7676
return (string) $entity;
@@ -114,7 +114,17 @@ public function isGranted(Security $security): bool
114114

115115
public function getGroupBy(): mixed
116116
{
117-
return $this->getForm()->getConfig()->getOption('group_by');
117+
return $this->getFormOption('group_by');
118+
}
119+
120+
private function getFormOption(string $name): mixed
121+
{
122+
$form = $this->getForm();
123+
// Remove when dropping support for ParentEntityAutocompleteType
124+
$form = $form->has('autocomplete') ? $form->get('autocomplete') : $form;
125+
$formOptions = $form->getConfig()->getOptions();
126+
127+
return $formOptions[$name] ?? null;
118128
}
119129

120130
private function getForm(): FormInterface

src/Autocomplete/tests/Fixtures/Form/AlternateRouteAutocompleteType.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use Symfony\Component\Form\AbstractType;
66
use Symfony\Component\OptionsResolver\OptionsResolver;
77
use Symfony\UX\Autocomplete\Form\AsEntityAutocompleteField;
8-
use Symfony\UX\Autocomplete\Form\ParentEntityAutocompleteType;
8+
use Symfony\UX\Autocomplete\Form\BaseEntityAutocompleteType;
99
use Symfony\UX\Autocomplete\Tests\Fixtures\Entity\Ingredient;
1010

1111
#[AsEntityAutocompleteField(route: 'ux_autocomplete_alternate')]
@@ -24,6 +24,6 @@ public function configureOptions(OptionsResolver $resolver)
2424

2525
public function getParent(): string
2626
{
27-
return ParentEntityAutocompleteType::class;
27+
return BaseEntityAutocompleteType::class;
2828
}
2929
}

src/Autocomplete/tests/Fixtures/Form/CategoryAutocompleteType.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace Symfony\UX\Autocomplete\Tests\Fixtures\Form;
44

55
use Doctrine\ORM\EntityRepository;
6-
use Symfony\UX\Autocomplete\Form\ParentEntityAutocompleteType;
6+
use Symfony\UX\Autocomplete\Form\BaseEntityAutocompleteType;
77
use Symfony\UX\Autocomplete\Tests\Fixtures\Entity\Category;
88
use Symfony\Component\Form\AbstractType;
99
use Symfony\Component\HttpFoundation\RequestStack;
@@ -48,6 +48,6 @@ public function configureOptions(OptionsResolver $resolver)
4848

4949
public function getParent(): string
5050
{
51-
return ParentEntityAutocompleteType::class;
51+
return BaseEntityAutocompleteType::class;
5252
}
5353
}

src/Autocomplete/tests/Fixtures/Form/CategoryNoChoiceLabelAutocompleteType.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Symfony\UX\Autocomplete\Tests\Fixtures\Form;
44

5-
use Symfony\UX\Autocomplete\Form\ParentEntityAutocompleteType;
5+
use Symfony\UX\Autocomplete\Form\BaseEntityAutocompleteType;
66
use Symfony\UX\Autocomplete\Tests\Fixtures\Entity\Category;
77
use Symfony\Component\Form\AbstractType;
88
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -21,6 +21,6 @@ public function configureOptions(OptionsResolver $resolver)
2121

2222
public function getParent(): string
2323
{
24-
return ParentEntityAutocompleteType::class;
24+
return BaseEntityAutocompleteType::class;
2525
}
2626
}

src/Autocomplete/tests/Fixtures/Form/IngredientAutocompleteType.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use Symfony\Component\Form\AbstractType;
66
use Symfony\Component\OptionsResolver\OptionsResolver;
77
use Symfony\UX\Autocomplete\Form\AsEntityAutocompleteField;
8-
use Symfony\UX\Autocomplete\Form\ParentEntityAutocompleteType;
8+
use Symfony\UX\Autocomplete\Form\BaseEntityAutocompleteType;
99
use Symfony\UX\Autocomplete\Tests\Fixtures\Entity\Ingredient;
1010

1111
#[AsEntityAutocompleteField]
@@ -24,6 +24,6 @@ public function configureOptions(OptionsResolver $resolver)
2424

2525
public function getParent(): string
2626
{
27-
return ParentEntityAutocompleteType::class;
27+
return BaseEntityAutocompleteType::class;
2828
}
2929
}

0 commit comments

Comments
 (0)