x-one / autocomplete-bundle
Integrates Symfony UX Autocomplete with additional enhancements
Requires
- php: >=8.1
- symfony/form: ^6.3|^7.0
- symfony/framework-bundle: ^6.3|^7.0
- symfony/ux-autocomplete: ^2.13
Requires (Dev)
- doctrine/doctrine-bundle: ^2.8
- doctrine/orm: ^2.14
- friendsofphp/php-cs-fixer: ^3.13
- mtdowling/jmespath.php: ^2.6
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^11.5
- symfony/security-bundle: ^6.3|^7.0
- symfony/twig-bundle: ^6.3|^7.0
- zenstruck/browser: ^1.8
- zenstruck/foundry: ^1.37
README
Paczka integrująca Symfony UX Autocomplete, oferująca pomocnicze klasy rozwijające funkcjonalność oraz ułatwiające definicję pól typu autocomplete.
Instalacja
Dodaj prywatne repozytorium do pliku composer.json
:
{
"repositories": [
{
"type": "vcs",
"url": "git@bitbucket.org:majchw/autocomplete-bundle.git"
}
]
}
Uwaga: wymagana będzie autoryzacja poprzez klucz SSH — instrukcja.
Następnie, zainstaluj paczkę:
composer require x-one/autocomplete-bundle
Symfony Flex powinien automatycznie dodać nowe wpisy do plików config/bundles.php
oraz assets/controllers.json
:
return [
// ...
XOne\Bundle\AutocompleteBundle\XOneAutocompleteBundle::class => ['all' => true],
];
{
"controllers": {
"@x-one/autocomplete-bundle": {
"autocomplete": {
"enabled": true,
"fetch": "eager"
}
}
}
}
Finalnie, przebuduj assety:
yarn install --force
yarn watch
Różnice w stosunku do Symfony UX Autocomplete
Ogólna definicja pól oraz możliwości konfiguracji znajdują się w oficjalnej dokumentacji Symfony UX Autocomplete.
Definicja pola autocomplete
Klasy formularzy definiujące pola typu autocomplete powinny rozszerzać klasę AbstractEntityAutocompleteType
:
// src/Form/Type/ProductAutocompleteFormType.php
use Symfony\UX\Autocomplete\Form\AsEntityAutocompleteField;
use Symfony\UX\Autocomplete\Form\ParentEntityAutocompleteType;
use XOne\Bundle\AutocompleteBundle\Form\Type\AbstractEntityAutocompleteType;
#[AsEntityAutocompleteField]
class ProductAutocompleteFormType extends AbstractEntityAutocompleteType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => Product::class,
'choice_label' => 'name',
]);
}
}
Dzięki temu nie trzeba podawać getParent()
, które w rozszerzanej klasie zwraca AutocompleteEntityType
zamiast ParentEntityAutocompleteType
przedstawianego w oficjalnej dokumentacji.
Obsługa filtracji w repozytoriach
Repozytoria mogą teraz implementować interfejs AutocompleteRepositoryInterface
,
dzięki czemu będą automatyczne wykorzystywane w procesie filtracji. Przykładowo:
// src/Repository/ProductRepository.php
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder;
use XOne\Bundle\AutocompleteBundle\Repository\AutocompleteRepositoryInterface;
class ProductRepository extends ServiceEntityRepository implements AutocompleteRepositoryInterface
{
public function addAutocompleteCriteria(QueryBuilder $queryBuilder, array $parameters): void
{
$rootAlias = current($queryBuilder->getRootAliases());
if ($query = $parameters['query']) {
$queryBuilder
->andWhere($queryBuilder->expr()->like("$rootAlias.name", ':query'))
->setParameter('query', "%$query%");
}
}
}
Tablica $parameters
zawiera parametry query z URL. Wyszukiwana wartość zawsze dostępna jest pod kluczem query
.
Uwaga: zwrócenie QueryBuilder
w opcji filter_query
pola autocomplete pomija repozytorium!
Przekazywanie parametrów
Największym problemem Symfony UX Autocomplete jest brak możliwości przekazania dodatkowych parametrów do podzapytania — ponieważ opcje, jakie przekażemy do formularza, są utracone w momencie wysłania żądania AJAX dla autocomplete.
W celu rozwiązania tego problemu bundle wprowadza nową opcję autocomplete_parameters
:
// src/Form/Type/CategoryFormType.php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class CategoryFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
/** @var null|Category $category */
$category = $options['data'];
$builder
->add('products', ProductAutocompleteFormType::class, [
'autocomplete_parameters' => [
'category_id' => $data?->getId(),
],
])
;
}
}
Parametry te zostają przekazywane w URL zapytań AJAX. W powyższym przykładzie (zakładając, że przekazane ID kategorii to "1") będzie to:
/autocomplete/product?query=&product_id=1
Dzięki temu parametr ten można wyciągnąć w repozytorium:
// src/Repository/ProductRepository.php
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder;
use XOne\Bundle\AutocompleteBundle\Repository\AutocompleteRepositoryInterface;
class ProductRepository extends ServiceEntityRepository implements AutocompleteRepositoryInterface
{
public function addAutocompleteCriteria(QueryBuilder $queryBuilder, array $parameters): void
{
$rootAlias = current($queryBuilder->getRootAliases());
if (isset($categoryId = $parameters['category_id'] ?? null)) {
$queryBuilder
->andWhere($queryBuilder->expr()->eq("$rootAlias.category", ':category'))
->setParameter('category', $categoryId);
}
}
}
Parametry o wartościach z innych pól formularza
W przypadku, gdy wartość parametr ma być pobrany z innego pola formularza,
do opcji autocomplete_parameters
można przekazać konkretne pole formularza.
Poniższy przykład zakłada, że mamy formularz dla produktu, w którym możemy wybrać jego klasę oraz grupę. Grupa może być wybrana tylko spośród tych, które należą do wybranej klasy.
// src/Form/Type/ProductFormType.php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ProductFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('productClass', ProductClassAutocompleteFormType::class)
->add('productGroup', ProductGroupAutocompleteFormType::class, [
'autocomplete_parameters' => [
'product_class_id' => $builder->get('productClass'),
],
])
;
}
}
Jeśli nie ma opcji na pobranie pola formularza (bo powstaje on później w procesie budowania),
można przekazać instancję FormReference
, w której podajemy jedynie nazwę pola:
// src/Form/Type/ProductFormType.php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use XOne\Bundle\AutocompleteBundle\Form\FormReference;
class ProductFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('productClass', ProductClassAutocompleteFormType::class)
->add('productGroup', ProductGroupAutocompleteFormType::class, [
'autocomplete_parameters' => [
'product_class_id' => new FormReference('productClass'),
],
])
;
}
}
Jeśli natomiast mamy pewność co do selektora CSS pola formularza, możemy przekazać go bezpośrednio:
// src/Form/Type/ProductFormType.php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use XOne\Bundle\AutocompleteBundle\Form\FormReference;
class ProductFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('productClass', ProductClassAutocompleteFormType::class)
->add('productGroup', ProductGroupAutocompleteFormType::class, [
'autocomplete_parameters' => [
'product_class_id' => '#product_form_productClass',
],
])
;
}
}
W powyższych przypadkach, do żądania AJAX pobierającego grupy produktów możliwe do wyboru,
zostanie doklejony parametr product_class_id
z wartością wybraną w polu klasy produktu.
Przetwarzanie parametrów
Jeśli zaistnieje potrzeba, aby przetworzyć tablicę parametrów przed przekazaniem jej do repozytorium,
w klasie typu pola autocomplete można zaimplementować interfejs AutocompleteParametersTransformerInterface
:
// src/Form/Type/ProductAutocompleteFormType.php
use Symfony\Component\Security\Core\Security;
use Symfony\UX\Autocomplete\Form\AsEntityAutocompleteField;
use Symfony\UX\Autocomplete\Form\ParentEntityAutocompleteType;
use XOne\Bundle\AutocompleteBundle\Form\AutocompleteParametersTransformerInterface;
use XOne\Bundle\AutocompleteBundle\Form\Type\AbstractEntityAutocompleteType;
#[AsEntityAutocompleteField]
class ProductAutocompleteFormType extends AbstractEntityAutocompleteType implements AutocompleteParametersTransformerInterface
{
public function __construct(
private Security $security,
) {
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => Product::class,
'choice_label' => 'name',
]);
}
public function transformAutocompleteParameters(array $parameters): array
{
// Add defaults...
$parameters += [
'category_id' => null,
];
// Cast to proper types if needed...
if ($parameters['category_id']) {
$parameters['category_id'] = (int) $parameters['category_id'];
}
// Add additional parameters...
$parameters['user_id'] = $this->security->getUser()?->getId();
return $parameters;
}
}
Metoda transformAutocompleteParameters()
otrzymuje tablicę parametrów (wraz z wartościami), które przyszły podczas żądania AJAX.
Rozwój bundle
Upewnij się, że testy nie zwracają żadnego błędu, oraz kod jest poprawnie sformatowany:
composer run-script pre-commit-checks
Wersjonowanie
Aby paczkę dało się zaktualizować przez composera, po zmergowaniu zmian do głównego brancha, należy utworzyć tag w formacie vX.Y.Z
, np.
git tag -a v1.1.0 -m "Version v1.1.0"
git push origin --tags