Symfony 1.4: embedForm et embedRelation ou imbriquer des formulaires

Benjamin Longearet 28 mars 2010 16
Symfony 1.4: embedForm et embedRelation ou imbriquer des formulaires

Formulaire imbriqué sous symfony

Dans votre administration, sous symfony, vous créez un module pour chaque modèle. Ne vous êtes vous jamais demandé si vous pouviez tous mettre à jour en un seul formulaire? Et bien c’est réalisable et pas si compliqué que cela.

Je vais donc vous montrer comment imbriqué différents formulaire pour mettre à jour plusieurs modèles en même temps!

Pour l’exemple, je vais prendre le schéma suivant : Création et un-ou-plusieurs LienCreation pour chaque Création.

Creation:
  actAs: {Timestampable: ~ }
  columns:
    titre:
      type: string(255)
    est_visible:
      type: boolean
      notnull: true
      default: 1
LienCreation:
  actAs: { Timestampable: ~ }
  columns:
    creation_id:
      type: integer
    url:
      type: string(255)
  relations:
    Creation:
      foreignType: many
      foreignAlias: LiensCreation
      local: creation_id

Jusque là pas de surprise, on à deux modèles très pauvres en données. Pour avoir un rendu immédiat, on va créer un petit /data/fixtures/fixture.yml pour avoir tous les cas possibles.

Creation:
  crea_1:
    titre: creation n°1
    est_visible: true
  crea_2:
    titre: creation n°2
    est_visible: true
  crea_3:
    titre: creation n°3
    est_visible: true
LienCreation:
  link1:
    Creation: crea_1
    url: test1.com
  link2:
    Creation: crea_1
    url: test2.com
  link3:
    Creation: crea_1
    url: test3.com
  link4:
    Creation: crea_2
    url: test4.com

On build tous ça, on charge les données dans la base de données et on crée notre module Création dans l’application backend (je suppose que vous avez déjà créé une application backend.

$ ./symfony doctrine:build --all --no-confirmation --and-load
$ ./symfony doctrine:generate-admin backend Creation
$ ./symfony plugin:publish-assets

Afficher les liens de la création dans le même formulaire

Alors pour commencer, on va éditer le fichier /lib/form/doctrine/CreationForm.php. C’est dans ce fichier que nous allons imbriquer le ou les formulaires supplémentaires. C’est également dans ce fichier que nous allons surcharger les règles de validation des formulaires imbriqués, nous verrons pourquoi en temps voulu.

Dans cette première partie, nous voulons donc afficher tous les liens de la création choisi. On se place donc dans le cadre de l’édition d’une création déjà existante.

Dans le fichier /lib/form/doctrine/CreationForm.php

public function configure() {
  // autre code
  $this->embedRelation('LiensCreation');
}

Toute la magie se trouve dans cette méthode embedRelation(). Cette méthode va générer au sein de votre formulaire de mise à jour de la Création, autant de formulaire que de relations présentes dans la base de données. Vous pourrez alors modifier TOUS les liens en même temps que la création correspondante!

 

Afficher également un formulaire de nouveau lien

Nous avons vu comment afficher les liens déjà reliés à la création, et bien nous allons voir comment rajouter un formulaire pour générer un nouveau lien en le liant automatiquement à la Création en cours d’édition ou en cours de création.

Dans le fichier /lib/form/doctrine/CreationForm.php on vient remodifier la méthode configure()!

public function configure() {
  // Formulaire vide pour ajouter un lien
  $newLienForm = new LienCreationForm();
  $this->embedForm('new', $newLienForm);
  // Formulaire pour les liens existants
  // On utilise la valeur donné à ForeignAlias dans le schema.yml
  $this->embedRelation('LiensCreation');
}

En effectuant les actions précédentes, même les formulaires vides seront pris en compte, il faut donc remédier à cela. Pour ce faire, nous allons créer un validateur personnalisé. Nous allons donc créer le fichier/lib/validator/LienCreationValidatorSchema.class.php et le remplir comme ci-dessous:

class LienCreationValidatorSchema extends sfValidatorSchema {
  protected function configure($options = array(), $messages = array()) {
    // Ici on viendra rajouter/editer ou supprimer les différents messages d'erreurs
    $this->addMessage('url', 'La description est requise.');
  }
  protected function doClean($values) {
    $errorSchema = new sfValidatorErrorSchema($this);
    foreach($values as $key => $value) {
      $errorSchemaLocal = new sfValidatorErrorSchema($this);
      // Si il n'y a pas l'url de remplit ou si elle est vide, on évite l'insertion d'un lien "vide"
      if (!$value['url'] || empty($value['url'])) {
        unset($values[$key]);
      }
      // Si il y a des erreurs ont les traitent ici
      if (count($errorSchemaLocal)) {
        $errorSchema->addError($errorSchemaLocal, (string) $key);
      }
    }
    // throws the error for the main form
    if (count($errorSchema)) {
      throw new sfValidatorErrorSchema($this, $errorSchema);
    }
    return $values;
  }
}

Une fois ce validateur personnalisé, on va l’appeler et s’en servir! On va pas s’amuser à écrire du code pour rien quand même !!! =D

Dans le fichier /lib/form/LienCreationForm.class.php on va venir ajouter ce validateur en rajoutant dans la méthode configure() la ligne suivante :

$this->mergePostValidator(new LienCreationValidatorSchema());

Cela évite de créer des enregistrements vides dans la base de donnée. Cela arrive si par exemple l’utilisateur veut seulement ajouter une Création sans liens, ou quand il édite une création avec déjà des liens existants.

 

Vous pouvez maintenant tester l’ajout de nouveau lien relié avec la création en cours d’édition ou de création directement depuis ce formulaire.

Bon dév’ :D

Geekos.fr vous recommande les articles suivants

16 Commentaires »

  1. Jacksay 3 novembre 2010 au 13 h 17 min - Reply

    Article très clair et intéressant, j’ai une petite question.

    Dans le sous formulaire il y a un select pour choisir la création, y’a un moyen d’automatiser la relation entre la création et ces liens (et donc de cacher le select) ?

  2. Jekif 11 décembre 2010 au 23 h 39 min - Reply

    Merci pour cet article qui m’a fait faire un grand pas en avant !

    Il subsiste pourtant un problème. Cette méthode renvoie le html suivant où la balise sensée contenir deux input text est vide :

    Entrees

    Il génère pourtant bien les champs cachés. Et si j’essaie d’afficher le formulaire imbriqué directement via son module (non imbriqué), j’ai bien tous les champs… J’avoue être un peu coincé là :(

    Mais merci quand même

  3. Pomme 18 février 2011 au 11 h 13 min - Reply

    Bonjour,

    Merci pour cet article, c’est exactement ce que je cherchais.

    J’ai une table Film, une table Realisateur, et une table de jointure Realisateur_has_film.
    L’administration de la table Film et de la table Realisateur est faite. Mais j’aimerais pouvoir lorsque je crée un film, lui attribuer un réalisateur (déjà existant ou que je crée).

    J’essaye donc en faisant :

    $this->embedRelation('RealisateurHasFilm');
    $newRealisateurHasFilm = new RealisateurHasFilmForm();
    $this->embedForm('new', $newRealisateurHasFilm);

    Néanmoins j’ai le même problème que Jekif.. Le formulaire ne s’affiche pas. Si je regarde le code source, les champs sont cachés. :(

  4. Pomme 28 février 2011 au 17 h 47 min - Reply

    Bonjour,

    Merci beaucoup pour votre aide.

    J’ai donc mis dans RealisateurHasFilmFormClass.php:

    $this->widgetSchema['realisateur_id_fk'] = new sfWidgetFormDoctrineChoice(array(
    ‘model’ => ‘Realisateur’,
    ‘add_empty’ => true
    ));
    $this->setDefault(‘id_realisateur_fk’, $this->getOption(‘id_realisateur_fk’));
    $form = new RealisateurForm();
    $this->embedForm(‘nom_realisateur’, $form);
    $this->widgetSchema->setLabel(‘id_realisateur_fk’, ‘Choisir…’);
    $this->widgetSchema->setLabel(‘nom_realisateur’, ‘ou créer.’);

    et dans FilmForm.class.php :

    $idProd = $this->getObject()->isNew() ? NULL : $this->getObject()->getIdProdFk();
    $form = new RealisateurHasFilmForm(array(), array(‘id_prod_fk’ => $idProd));
    $this->embedForm(‘Realisateur’, $form);
    $this->setValidator(‘id_realisateur_fk’, new sfValidatorNumber(array(‘required’ => true)));

    Mais lorsque je veux créer un nouveau film, il me dit “Unexpected extra form field named “auteur_id_fk”.” De plus, si j’essaye d’éditer un film, la liste déroulante ne récupère pas l’id du réalisateur.

    Je sais pas trop comment faire..

  5. lakhcim 11 mars 2011 au 23 h 54 min - Reply

    bonjour ,
    merci pour cet article , il ma aidé de faire un pas , je suis debutant en dev Web , je suis coincé sur un point de multi upload des photos du projet watchmydesk du site la fermeduweb, si vous avez une idée , ??

  6. chris 12 mai 2011 au 17 h 11 min - Reply

    Ouaaaaoouu!!! Trop bien, c’est vraiment puissant, merci pour cette astuce !!

  7. Lea 13 juillet 2011 au 12 h 02 min - Reply

    Bonjour,
    j’essaie également de faire un formulaire imbriqué.
    J’ai un classe Demande et une classe Message qui contient une relation vers une demande.

    Dans demandeForm.class methode configure j’ai ajouté:
    $message= new messageForm();
    $this->embedForm(‘message’, $message);
    $this->embedRelation(“message”);
    $this->mergePostValidator(new sfValidatorMessage());

    $this->widgetSchema->setLabels(array(
    ‘message’=>”Description”
    ));

    et dans messageForm.class methode configure j’ai ajouté:
    $this->useFields(array(
    ‘text’,
    ‘envoyePar_id’
    ));
    $this->widgetSchema['demande_id'] = new sfWidgetFormInputHidden();

    dans mon _form de demande j’ai ajouté:
    renderLabel() ?>:

    renderError() ?>

    mon problème est que quand j’affiche mon formulaire pour une nouvelle demande, j’ai bien le label “Description” mais a coté c’est un message d’erreur VIDE qui s’affiche au lieu des différents champs que j’attendais..

    est-ce que j’ai mal compris les explications si-dessus? est-ce que j’ai oublié quelque chose?
    merci pour votre aide

    léa

  8. Olivier Clémence 27 juillet 2011 au 17 h 46 min - Reply

    Merci pour cet article qui m’a bien aiguillé dans mon développement. J’ai réussi à résoudre tout mes problèmes grâce à lui et cet excellent plugin: http://www.symfony-project.org/plugins/ahDoctrineEasyEmbeddedRelationsPlugin/1_4_4

    Encore merci !

  9. irreniBissevy 28 juillet 2011 au 1 h 15 min - Reply

    ce que je cherchais, merci

  10. mbodj 9 février 2012 au 14 h 33 min - Reply

    au niveau du code validator y a une erreur au niveau du boucle foreach($values as $key =>; $value) pas de point virgule aprs$key=>

    Merci j’ai pu créer mes forms imbriqués

  11. Benjamin Longearet 14 novembre 2010 au 8 h 36 min - Reply

    Bonjour, désolé de répondre si tard, mais mon alternance me prend plus de temps que prévu!

    Pour ce qui est de votre question, il y a bien un moyen mais je pense qu’il faut le faire à la main.
    Je vais prendre comme exemple des Créations, qui imbriquent des Images

    * Il faut spécifier les champs à utiliser dans le form imbriqué, ici dans lib/form/doctrine/CreationForm.class.php.

    public function configure() {
    $this->useFields(array('description', 'image', 'est_visible'));
    // Ton code
    }

    Pour ma part, il met automatiquement à jour le champs et ne l’affiche pas!

    En espérant que cela t’aide!
    Bon dév’

  12. Benjamin Longearet 20 février 2011 au 22 h 04 min - Reply

    Salut à toi!

    Désolé de répondre si tard.

    Pour effectuer soit la sélection soit l’ajout d’un réalisateur, il ne suffit pas de ces trois lignes!

    Dans le FilmForm.class.php:

    public function configure() {
    $realisateurId = $this->getObject()->isNew() ? NULL : $this->getObject()->getRealisateurId();
    $form = new FilmRealisateurForm(array(), array('realisateur_id' => $realisateurId));
    $this->embedForm('Realisateur', $form);
    }

    Cela a pour effet de créer le formulaire de la table de jointure avec l’id d’un réalisateur ou non si celui ci est spécifié.
    Ensuite, il faut modifier l’action de FilmRealisateurForm.class.php:

    public function configure() {
    $this->widgetSchema['realisateur_id'] = new sfWidgetFormDoctrineChoice(array(
    'model' => 'Realisateur',
    'add_empty' => true
    ));

    $this->setDefault('realisateur_id', $this->getOption('realisateur_id'));

    $form = new RealisateurForm();
    $this->embedForm('new_realisateur', $form);

    $this->widgetSchema->setLabel('realisateur_id', 'Choisir…');
    $this->widgetSchema->setLabel('new_realisateur', 'ou créer.');
    }

    L’affichage du formulaire affichera quelques chose comme le screen ci-dessous!

    Screenshot demo

  13. Benjamin Longearet 13 mars 2011 au 10 h 06 min - Reply

    @lakhcim: Bonjour, est-ce que tu pourrais être un peu plus précis sur ton problème? Merci.

  14. Benjamin Longearet 13 mars 2011 au 10 h 07 min - Reply

    @Pomme: Bonjour,
    Et non je n’ai pas oublié de répondre, mais il faut que je prenne un peu de temps, je réponds dès que je peux.
    Bonne journée!

  15. Benjamin Longearet 27 juillet 2011 au 19 h 20 min - Reply

    Content de l’aide que ça t’as apporté!
    A l’occasion, je fouinerai un peu ce plugin.
    Bon dév’

Laissez un message »