Mixons les formulaires avec Symfony

Lookin' through the archives

Depuis la version 1.1, Symfony propose un framework de génération de formulaire, respectant le modèle MVC, et bien entendu bati selon un modèle objet robuste.

Pour l’avoir un peu poussé dans ses retranchements, je dois avouer que je suis quelque peu impressionné par la qualité de l’ouvrage. Aujourd’hui, je vous propose un exemple de fonctionalité intéressante : les inclusions de formulaires.

Un formulaire peut en cacher un autre

Pour illustrer mon propos, prenons un modèle simple (dans schema.yml) :

propel:
  user:
    id:
    name: varchar(255)
    email: varchar(255)
    address_id:

  address:
    id:
    number: integer
    street: varchar(255)
    town: varchar(60)

Un utilisateur a une seule adresse, facile. Pour faire bonne mesure, j’ai quand même partitionné mes tables. Ajoutons quelques données de test dans le répertoire fixtures :

Address:
  a1:
    number: 1
    street: boulevard Belleville
    town: Paris

  a2:
    number: 666
    street: cours gambetta
    town: Montpellier

User:
  robert:
    name: Garcin Fony
    email: garcin.fony@example.com
    address_id: a1

Puis, achevons de créer notre environnement de démonstration, en créant le modèle, chargeant les données, et préparant un module :

symfony propel:build-all-load frontend
symfony generate:module frontend user

Édition d’un utilisateur

Je veux utiliser la puissance du framework de formulaires pour éditer un utilisateur. Facile, on va d’abord réaliser une action pour prendre en charge la création et la validation du formulaire :

public function executeEdit($request)
{
    $user = UserPeer::retrieveByPk($request->getParameter('id'));
    $this->forward404Unless($user);

    $this->userForm = new UserForm($user);

    if($request->isMethod('post') && $this->userForm->bindAndSave($request->getParameter('user')))
    {
        $this->redirect('@homepage');
    }
}

Ensuite, dans mon template editSuccess.php, il ne me reste qu’à afficher le formulaire :

<form action="" method="post">
<?php echo $userForm ?>
<input type="submit" value="modifier" />
</form>

Le résultat n’est pas trop mal :

Symfony propel form

Génial ! Symfony détecte automatiquement la clé étrangère, et adapte son formulaire en conséquence en me fournissant une liste déroulante des adresses (Au passage, remarquez que ce phénomène a nécessité l’ajout d’une méthode __toString dans la classe adresse, on n’a rien sans rien).

Bon, c’est bien beau, mais moi, j’espérais plutôt pouvoir éditer l’adresse en question dans le même formulaire. Pas de problèmes, nous allons modifier notre formulaire avec la fonction magique embedForm pour qu’il embarque un autre formulaire de modification de l’adresse.

La fonction embedForm permet, comme son nom l’indique, d’embarquer un formulaire dans un autre. Les validateurs sont également pris en compte. En alternative, selon les cas, on préférera la fonction mergeForm, qui permet de fusionner les formulaires.

Dans lib/form/UserForm.class.php :

class UserForm extends BaseUserForm
{
    public function setup()
    {
        parent::setup();

        $this->widgetSchema['address_id'] = new sfWidgetFormInputHidden();
        $this->embedForm('address', new AddressForm($this->getObject()->getAddress()));
    }
}

Et voilà ! Sans toucher le moins du monde au template, on se retrouve avec le résultat escompté :

symfony propel form with embed

Rhââ Lovely ! (comme dirait l’autre). Tout ça en 5 lignes de codes et 4 de templates. Bon, par contre, notre adresse n’est pas mise à jour à la soumission du formulaire. Embêtant… Il va nous falloir surcharger la méthode updateObject pour prendre en compte le formulaire embarqué. Dans la même classe :

public function updateObject()
{
    parent::updateObject();

    $address = $this->getObject()->getAddress();
    $values = $this->getValues();

    $address->fromArray($values['address'], BasePeer::TYPE_FIELDNAME);
    $address->save();

    return $this->object;
}

À ce moment, notre adresse sera sauvegardée en même temps que l’utilisateur. Pour la beauté de la démonstration, je n’ai pas rajouté de gestion des exceptions, mais l’idée est là.

Sur ce, je vous laisse vous amuser avec les formulaires. À la prochaine !