Adrien's log

Yet Another Note Pad

Symfony2 et Select avec Optgroups sur une entité Doctrine2

by adrien on 4 mars 2012

UPDATE: La solution présentée ci-dessous est maintenant obsolète. Il semble que Symfony gère les groupes dans une liste de manière native. Je n’ai malheureusement pas eu le temps de tester cela : Solution potentielle

Aujourd’hui, je décide d’avancer un peu sur le projet de fin d’année scolaire, à savoir : un système de prise de commande sur tablette (iPad, par exemple) pour restaurant. L’architecture que nous avons choisi : un serveur web (avec  Symfony2) afin de pouvoir changer de client sans redeveloper une application en cas de changement de marque de tablette, et enfin un backend d’administration. En gros, chaque restaurant est divisé en zones elles-même divisées en table. Donc le schéma de la base est simple, une table Restaurant, une table Zone et une table Table avec des relations en One to Many qui vont bien. Jusque là, tout va bien, mais j’ai juste rencontré un problème , très énervant, avec le système de formulaire de Symfony. Je voulais obtenir un select contenant la liste des zones classée par restaurant dans le formulaire d’ajout de table. Or aucune méthode simple n’est fournie de base avec Symfony : il faut donc se casser la tête ! Voilà ce que je souhaite obtenir : Select de zones classé par restaurantA la base, quand on génère le module CRUD avec la console Symfony, on obtient une liste de zones toute dégueulasse et même pas classée. Le type du champs dans le formulaire (ici, LunchtableType.php) est ‘entity’. Pour obtenir un tel classement, il faut ruser.

Le formulaire

Premièrement, il faut redéfinir le type de champ en ‘choice’ :

1
2
3
4
$builder
->add('numero', 'number')
->add('area', 'choice')
;

Mais comme fourni, le code ne va pas marcher, il faut fournir une option contenant la liste à ce champs :

1
2
3
4
$builder
->add('numero', 'number')
->add('area', 'choice', array('choices'=>$options['choices']))
;

Et aussi, sinon ça ne vas pas fonctionner non plus, fournir une option par défaut. Pour ça, il faut implémenter une méthode spéciale.

1
2
3
4
5
6
public function getDefaultOptions(array $options)
{
return array(
'choices' => array(),
);
}

Le ‘choices’ fourni est vide : ce n’est pas grave, c’est juste pour définir si le champ ‘Area’ doit être de type ‘entity’ ou de type ‘choice’. Ouais, il va être les deux : le type ‘choice’ pour l’affichage et le type entity pour le traitement par Doctrine. Comme ça, tout le monde s’y retrouve. Mais pour faire ceci, il nous faut retoucher notre builder de la sorte :

1
2
3
4
5
6
7
$builder
->add('numero', 'number');
 
if($options['choices'])
$builder->add('area', 'choice', array('choices'=>$options['choices']));
else
$builder->add('area');

Et voilà, notre formulaire est prêt à être utilisé.

Le controller

Pour cela, dans notre contrôleur à l’action ‘new’, nous voulons que le champs ce comporte en type ‘choice’. On initialise donc l’objet avec en paramètre la liste des valeurs voulues :

1
2
3
$form = $this->createForm(new LunchtableType(), $entity, array(
'choices' => $this->getDoctrine()->getEntityManager()->getRepository('MonBundle:Restaurant')->findAllWithAreasArrayRendered()
));

Pour l’action ‘create’, celle qui enregistre en base de données, on veut que le champ se comporte comme ‘entity’ donc on ne fournit pas d’option :

1
$form    = $this->createForm(new LunchtableType(), $entity);

Vous remarquerai ma méthode pour récupérer les zones. En faite, elle renvoie les résultats sous la forme d’un tableau à deux dimensions. Voici à quoi elle ressemble :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function findAllWithAreasArrayRendered()
{
$rests = $this->findAllWithAreas();
$ret = array();
foreach($rests as $rest)
{
$t = array();
foreach($rest->getAreas() as $area)
{
$t[$area->getId()] = $area;
}
$ret[$rest->getName()] = $t;
}
return $ret;
}

Le problème

Seul hic avec cette solution : si l’utilisateur se plante dans le remplissage le formulaire, il va donc se retrouver sur une page lui demandant de corriger son erreur, mais il va aussi se retrouver avec un champ Area non trié (de type ‘entity’ donc). Ce qui est logique puisque l’action en question est ‘create’ qui a un form avec un champ de type ‘entity’. Malheureusement, la seule solution que j’ai à ce problème est de rediriger l’utilisateur vers l’action ‘new’ ou bien de le laisser avec un select tout moche. Si quelqu’un a une solution à ça, je prends !

4 thoughts on “Symfony2 et Select avec Optgroups sur une entité Doctrine2

  1. Kzrdt says:

    Idée intéressante. As tu trouvé une meilleure solution depuis le temps?

  2. adrien says:

    Non, je ne me suis pas repenché dessus.
    Et c’est bien dommage, celle ci-dessus est bien crado.

    Toutefois, faudrait regarder par ici (j’ai pas trop regarder en détail) : https://github.com/symfony/symfony/issues/1735 Il semble qu’il y est une astuce beaucoup plus propre que la mienne.

  3. mehdi says:

    Ca a été implementé bien plus proprement depuis dans symfony. Il serait peut être bon de mettre une note en haut du post pour prévenir 😉 Et merci à toi d’avoir partagé tout ca

  4. adrien says:

    Yep, il me semblait bien avoir vu ça passer mais n’ayant pas eu de nouveau besoin de ce genre de listes, je ne m’y suis pas intéressé.

    Je n’ai pas essayé mais je pense que la solution serait quelque chose comme ça : http://symfony.com/doc/current/reference/forms/types/entity.html#group-by

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *