· 7 min read
Symfony UX Autocomplete
Et si je vous disez que mettre en place de l'autocompletion ne necessité qu'une seul ligne de code ? Avec Symfony UX AutoComplete, c'est possible !
Objectifs
Nous allons découvrir comment mettre en place super simplement une autocompletion avec Symfony UX AutoComplete.
Histoire de ce projetter dans une situation réelle, nous testerons dans un premier temps avec une liste de 10 000 choix, puis de 100 000 choix !
Mise en place d’un projet de démo
- Création d’un nouveau projet Symfony
symfony new SfAutocomplete --webapp
cd SfAutocomplete
docker compose up -d
symfony serve -d
- Ajoutons Bootstrap dans notre fichier
base.html.twig
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
<title>Bootstrap demo</title>
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</head>
<body>
<div class="container">
{% block body %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
</body>
</html>
- Créons une première entité
Choix
symfony console make:entity choix
created: src/Entity/Choix.php
created: src/Repository/ChoixRepository.php
New property name (press <return> to stop adding fields):
> nom
Field type (enter ? to see all types) [string]:
> string
Field length [255]:
> 255
Can this field be null in the database (nullable) (yes/no) [no]:
> no
updated: src/Entity/Choix.php
- Créons une seconde entité
Simple
symfony console make:entity Simple
created: src/Entity/Simple.php
created: src/Repository/SimpleRepository.php
New property name (press <return> to stop adding fields):
> nom
Field type (enter ? to see all types) [string]:
> string
Field length [255]:
> 255
Can this field be null in the database (nullable) (yes/no) [no]:
> no
updated: src/Entity/Simple.php
Add another property? Enter the property name (or press <return> to stop adding fields):
> choix
Field type (enter ? to see all types) [string]:
> relation
What class should this entity be related to?:
> Choix
Relation type? [ManyToOne, OneToMany, ManyToMany, OneToOne]:
> ManyToOne
Is the Simple.choix property allowed to be null (nullable)? (yes/no) [yes]:
> no
Do you want to add a new property to Choix so that you can access/update Simple objects from it - e.g. $choix->getSimples()? (yes/no) [yes]:
> yes
A new property will also be added to the Choix class so that you can access the related Simple objects from it.
New field name inside Choix [simples]:
> simples
Do you want to automatically delete orphaned App\Entity\Simple objects (orphanRemoval)? (yes/no) [no]:
> no
updated: src/Entity/Simple.php
updated: src/Entity/Choix.php
Success!
- Et enfin une troisième entité
Multiple
symfony console make:entity Multiple
created: src/Entity/Multiple.php
created: src/Repository/MultipleRepository.php
New property name (press <return> to stop adding fields):
> nom
Field type (enter ? to see all types) [string]:
> string
Field length [255]:
> 255
Can this field be null in the database (nullable) (yes/no) [no]:
> no
updated: src/Entity/Multiple.php
Add another property? Enter the property name (or press <return> to stop adding fields):
> Choix
Field type (enter ? to see all types) [string]:
> relation
What class should this entity be related to?:
> Choix
Relation type? [ManyToOne, OneToMany, ManyToMany, OneToOne]:
> ManyToMany
Do you want to add a new property to Choix so that you can access/update Multiple objects from it - e.g. $choix->getMultiples()? (yes/no) [yes]:
> yes
A new property will also be added to the Choix class so that you can access the related Multiple objects from it.
New field name inside Choix [multiples]:
> multiples
updated: src/Entity/Multiple.php
updated: src/Entity/Choix.php
Success!
- Générons les migrations et appliquons les
symfony console make:migration
symfony console d:m:m
Création “express” des CRUDs
- Créons un CRUD pour l’entité
Simple
symfony console make:crud
The class name of the entity to create CRUD (e.g. OrangeKangaroo):
> Simple
Choose a name for your controller class (e.g. SimpleController) [SimpleController]:
> SimpleController
Do you want to generate tests for the controller?. [Experimental] (yes/no) [no]:
> no
created: src/Controller/SimpleController.php
created: src/Form/SimpleType.php
created: templates/simple/_delete_form.html.twig
created: templates/simple/_form.html.twig
created: templates/simple/edit.html.twig
created: templates/simple/index.html.twig
created: templates/simple/new.html.twig
created: templates/simple/show.html.twig
Success!
- Créons un CRUD pour l’entité
Multiple
symfony console make:crud
The class name of the entity to create CRUD (e.g. OrangeGnome):
> Multiple
Choose a name for your controller class (e.g. MultipleController) [MultipleController]:
> MultipleController
Do you want to generate tests for the controller?. [Experimental] (yes/no) [no]:
> no
created: src/Controller/MultipleController.php
created: src/Form/MultipleType.php
created: templates/multiple/_delete_form.html.twig
created: templates/multiple/_form.html.twig
created: templates/multiple/edit.html.twig
created: templates/multiple/index.html.twig
created: templates/multiple/new.html.twig
created: templates/multiple/show.html.twig
Success!
Génération de fausse données
- Intallons quelques outils
composer require --dev orm-fixtures
composer require --dev fakerphp/faker
- Ecrivons les fixtures pour l’entité
Choix
// src/DataFixtures/AppFixtures.php
<?php
namespace App\DataFixtures;
use App\Entity\Choix;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
use Faker\Factory;
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
$faker = Factory::create('fr_FR');
for ($i=0; $i < 10000; $i++) {
$choix = new Choix();
$choix->setNom($faker->name);
$manager->persist($choix);
}
$manager->flush();
}
}
Adaptation de notre entité Choix
Pour que notre entité retourne une string, ajoutons lui la méthode suivante 🪄
public function __toString(): string
{
return $this->nom;
}
Testons nos formulaires
Sans surprise, les formulaires fonctionne bien, mais pas simple de naviguer dans cette longue liste de choix !
Symfony UX Autocomplete : Let’s go!
- Installons Webpack Encore et Symfony UI Autocomplete
composer require symfony/webpack-encore-bundle
composer require symfony/ux-autocomplete
npm install --force && npm run build
- Mettons en place l’autocomplete (sans Ajax) pour le formulaire
src/Form/SimpleType.php
<?php
namespace App\Form;
use App\Entity\Choix;
use App\Entity\Simple;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SimpleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('nom')
->add('choix', EntityType::class, [
'class' => Choix::class,
'choice_label' => 'nom',
'placeholder' => 'Choisissez un nom dans la liste',
'autocomplete' => true,
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Simple::class,
]);
}
}
- Mettons en place l’autocomplete (sans Ajax) pour le formulaire
src/Form/MultipleType.php
<?php
namespace App\Form;
use App\Entity\Choix;
use App\Entity\Multiple;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MultipleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('nom')
->add('choix', EntityType::class, [
'class' => Choix::class,
'choice_label' => 'nom',
'placeholder' => 'Choisissez un nom dans la liste',
'autocomplete' => true,
'multiple' => true,
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Multiple::class,
]);
}
}
- Et laissons la magie opérer
Créons nous un problème 🤯
- Modifions nos fixtures
src/DataFixtures/AppFixtures.php
pour générer non pas10 000
choix mais100 000
choix !
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
$faker = Factory::create('fr_FR');
for ($i=0; $i < 100000; $i++) {
$choix = new Choix();
$choix->setNom($faker->name);
$manager->persist($choix);
}
$manager->flush();
}
}
- Testons nos formulaires : BOOM… Fatal error !
Réglons le problème 😎
Pour le formulaire src/Form/SimpleType.php
- Générons un
autocomplete-field
php bin/console make:autocomplete-field
The class name of the entity you want to autocomplete:
> Choix
Choose a name for your entity field class (e.g. ChoixAutocompleteField) [ChoixAutocompleteField]:
> SimpleChoixAutocompleteField
created: src/Form/SimpleChoixAutocompleteField.php
Success!
- Modifions le fichier `src/Form/SimpleChoixAutocompleteField.php’
<?php
namespace App\Form;
use App\Entity\Choix;
use App\Repository\ChoixRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\UX\Autocomplete\Form\AsEntityAutocompleteField;
use Symfony\UX\Autocomplete\Form\ParentEntityAutocompleteType;
#[AsEntityAutocompleteField]
class SimpleChoixAutocompleteField extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => Choix::class,
'placeholder' => 'Choissisez un nom dans la liste',
'choice_label' => 'nom',
'query_builder' => function(ChoixRepository $choixRepository) {
return $choixRepository->createQueryBuilder('choix');
},
//'security' => 'ROLE_SOMETHING',
]);
}
public function getParent(): string
{
return ParentEntityAutocompleteType::class;
}
}
- Et adaptons notre formulaire
src/Form/SimpleType.php
<?php
namespace App\Form;
use App\Entity\Choix;
use App\Entity\Simple;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SimpleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('nom')
->add('choix', SimpleChoixAutocompleteField::class)
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Simple::class,
]);
}
}
Pour le formulaire src/Form/MultipleType.php
- Générons un
autocomplete-field
php bin/console make:autocomplete-field
The class name of the entity you want to autocomplete:
> Choix
Choose a name for your entity field class (e.g. ChoixAutocompleteField) [ChoixAutocompleteField]:
> MultipleChoixAutocompleteField
created: src/Form/MultipleChoixAutocompleteField.php
Success!
- Modifions le fichier `src/Form/MultipleChoixAutocompleteField.php’
<?php
namespace App\Form;
use App\Entity\Choix;
use App\Repository\ChoixRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\UX\Autocomplete\Form\AsEntityAutocompleteField;
use Symfony\UX\Autocomplete\Form\ParentEntityAutocompleteType;
#[AsEntityAutocompleteField]
class MultipleChoixAutocompleteField extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => Choix::class,
'placeholder' => 'Choissiez des noms dans la liste',
'choice_label' => 'nom',
'multiple' => true,
'query_builder' => function(ChoixRepository $choixRepository) {
return $choixRepository->createQueryBuilder('choix');
},
//'security' => 'ROLE_SOMETHING',
]);
}
public function getParent(): string
{
return ParentEntityAutocompleteType::class;
}
}
- Et adaptons notre formulaire
src/Form/MultipleType.php
<?php
namespace App\Form;
use App\Entity\Multiple;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MultipleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('nom')
->add('choix', MultipleChoixAutocompleteField::class)
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Multiple::class,
]);
}
}
Que le spectacle commence 🚀
Allons constater le résultat ! Magie !
Conclusion
Nous venons de le voir, avec Symgony UX Autocomplete, la mise en place d’un autocomplete pour nos formulaires et d’une simplicité déconcertante !
Le code source de la démo est dispo sur GitHub.