formulaire-inscription-watchmydeskDans cet épisode, nous allons voir comment gérer les formulaires avec le Zend Framework pour créer la page d’inscription de WatchMyDesk.

Cette partie va être assez compliquée et un peu longue. Accrochez vous !

Plan de l’épisode #08

Création du formulaire d’inscription.

Comme vous avez pu le voir, cette partie va être assez longue. Nous allons tout de suite attaquer par la création de l’objet Zend_Form de notre formulaire.

Pour cela, nous avons besoin de savoir les champs à utiliser pour qu’un utilisateur puisse s’inscrire.
Quand un membre s’inscrira, il devra renseigner :

  • Son Login ( Entre 3 et 15 caractères alphanumériques uniquement.)
  • Son Email (Email valide)
  • Son Password et la confirmation (Entre 6 et 20 caractères.)
  • Le captcha (pour empêcher les robots de s’inscrire)

Pour créer l’objet du formulaire, rendez-vous dans applications/forms de notre application. Nous allons créer un fichier PHP nommé : Inscription.php .

Un formulaire construit avec Zend_Form à une structure précise :

class Form_Inscription extends Zend_Form
{
	public function __construct($options = null)
	{
		parent::__construct($options);
                // le formulaire
	}
}

Dans le Zend Framework, l’intégralité des composants sont des objets, il faudra donc utiliser des objets pour créer nos champs de texte ou liste déroulante de notre formulaire.

Commençons par le champs Login. Celui-ci doit être unique dans la base de donnée (c’est à dire que deux personnes ne peuvent pas s’inscrire avec le même login). Il est obligatoire et entre 3 et 20 caractères.

class Form_Inscription extends Zend_Form
{
	public function __construct($options = null)
	{
		parent::__construct($options);
                $login = new Zend_Form_Element_Text("login", array('size' => 25));
		$loginDoesntExist = new Zend_Validate_Db_NoRecordExists('membres', 'login');
		$login ->setLabel('Login')
		  ->addFilter('StripTags')
		  ->addFilter('StringTrim')
		  ->addValidator('NotEmpty')
		  ->addValidator($loginDoesntExist)
		  ->addValidator('StringLength', false, 3, 20)
		  ->setDescription("Login between 3 and 20 alphanumerics characters.");
	}
}

Dans ce simple morceau de code, plusieurs notions sont abordées.
La création de notre élément HTML passe par l’utilisation de Zend_Form_Element_Text qui prend 2 paramètres :

  • Son identifiant
  • et des attributs HTML (ici la taille du champ)

Nous lui ajoutons ensuite un label avec setLabel, ainsi que plusieurs filtres et validateurs. Les filtres utilisés ici sont très simple :

  • StripTags : Enlève les caractères HTML
  • StringTrim : Enlève les espaces dans la chaîne de caractère.

Concernant les validateurs (addValidator):

  • StringLength : Vérifie la longeur de la chaine de caractère.
  • NotEmpty : Vérifie si la chaine existe.

La différence entre les filtres et les validateurs est très simple. Les filtres sont s’appliquer avant la validation et les validateurs sont appliqués durant la validation et peuvent retourner des erreurs.

Dans notre cas, nous avons besoin de vérifier l’unicité du login dans la base de donnée, pour cela, le Zend Framework possède un validateur très simple d’utilisation mais très pratique : Zend_Validate_Db_NoRecordExists.

Il prend en paramètres :

  • Le nom de la table (ici membres)
  • Et le champ à regarder (ici login )

Si lors de l’inscription, un login identique à celui renseigné dans le formulaire est trouvé dans la base de données, le validateur va nous ressortir une erreur.

Pratique non? :)

Passons ensuite à l’adresse email qui ressemble tout particulièrement au login, sauf qu’ici nous validons un email et utilisons le validateur EmailAddress :


$emailDoesntExist = new Zend_Validate_Db_NoRecordExists('membres', 'email');
$email = new Zend_Form_Element_Text("email", array('size' => 25));
$email ->setLabel('Email address')
  ->addFilter('StripTags')
  ->addFilter('StringTrim')
  ->addValidator('NotEmpty')
  ->addValidator($emailDoesntExist)
  ->addValidator('EmailAddress')
  ->setDescription("Require a valid email address.");

La méthode setDescription permet d’ajouter un message d’aide à l’utilisateur. Nous reviendrons sur son affichage dans la partie de personnalisation d’un formulaire.

Les champs de password sont eux aussi très simple :

$password = new Zend_Form_Element_Password("password", array('size' => 25));
$password ->setLabel('Password')
  ->addFilter('StringTrim')
  ->addValidator('NotEmpty');

$repassword = new Zend_Form_Element_Password("repassword", array('size' => 25));
$repassword ->setLabel('Retype password')
  ->addFilter('StringTrim')
  ->addValidator('NotEmpty');
$repassword->setDescription("Retype the previous password to prevent errors.");

Nous reviendrons tout à l’heure sur le fait que la confirmation du mot de passe doit être identique au mot de passe.

Ensuite la liste déroulante des pays.
Comme tout le monde le sait sûrement, quand on développe une application sans framework, il est souvent compliquer d’avoir une liste de pays complète et compatible avec plusieurs langues. Le Zend Framework embarque nativement ce genre de fonctionnalités que nous allons utilisé dans notre formulaire.

$pays = new  Zend_Form_Element_Select("pays");
$pays ->setLabel('Country')
 ->addMultiOptions(Zend_Locale::getCountryTranslationList(Zend_Registry::get('Zend_Locale')))
  ->addValidator('NotEmpty');

Le addMultiOptions prend un tableau en paramètre, ici la liste des pays généré par Zend_Local. De plus, nous allons récupérer la langue courante de notre application et changer la langue des pays.

Ce qui va nous donner :

Image 4

Et enfin, il nous faut le composant Captcha qui est directement intégré dans le Zend Framework. Pour pouvoir utiliser ce composant, il faut un compte sur le site de ReCaptcha.net. Une fois inscrit, vous aurez accès à votre clef publique et à une clef privée :

$pubKey = 'clef publique';
$privKey = 'clef privée';
$recaptcha = new Zend_Service_ReCaptcha($pubKey, $privKey);

$adapter = new Zend_Captcha_ReCaptcha();
$adapter->setService($recaptcha);

$captcha = new Zend_Form_Element_Captcha('recaptcha', array( 'label' => "Captcha", 'captcha' => $adapter));
$captcha->removeDecorator('label')->removeDecorator('errors');

Ici nous utilisons un concept de Zend_Form qui sont les décorateurs. Nous allons supprimer le label et les erreurs du captcha (ça rendra mieux au niveau du design).

Maintenant il suffit d’ajouter tout ces composants dans votre formulaire et le tour sera joué !

$this->addElements(array($login,$email, $password, $repassword, $pays, $captcha));

OUF ! Voila notre formulaire est enfin finit, enfin presque. Rappelons nous que il faut que le mot de passe correspondent à la confirmation.

Pour cela, je vais vous fournir un composant qui va vérifier si les Password correspondent entre eux ( à placer dans le repertoire library/App/Validate de votre application). Vous pouvez le télécharger ici.

Mais cela ne suffira pas, il faut maintenant utiliser ce composant.
Pour cela, nous allons re-écrire la méthode de validation du Zend_Form.

public function isValid($data)
{
	$password 	= $this->getElement('password');
	$password->addValidator(new App_Validate_PasswordMatch($data['repassword']));

	return parent::isValid($data);
}

Et voila, à la validation nous allons bien avoir une validation sur la confirmation du mot de passe.

Pour afficher le formulaire c’est très simple, rendez vous dans le contrôleur MembreController.php et ajouter cela:


class MembreController extends Zend_Controller_Action
{

	function newAction()
	{
		$this->view->form = $form = new Form_Inscription;
	}
}

Ensuite, il faut afficher le formulaire dans la vue correspondante à l’action de notre contrôleur dans membres/new.phtml

<h1><?php echo $this->translate('Register to Watch My Desk'); ?></h1>
<?php echo $this->form; ?>

Code source complet de la création du formulaire :

class Form_Inscription extends Zend_Form
{
	public function __construct($options = null)
	{
		parent::__construct($options);

		$this->setName('inscription');

		$login = new Zend_Form_Element_Text("login", array('size' => 25));
		$loginDoesntExist = new Zend_Validate_Db_NoRecordExists('membres', 'login');
		$login ->setLabel('Login')
		  ->setRequired(true)
		  ->addFilter('StripTags')
		  ->addFilter('StringTrim')
		  ->addValidator('NotEmpty')
		 	->addValidator($loginDoesntExist)
		 	->addValidator('StringLength', false, 3, 20)
		  ->setDescription("Login between 3 and 20 alphanumerics characters.");

		$emailDoesntExist = new Zend_Validate_Db_NoRecordExists('membres', 'email');
		$email = new Zend_Form_Element_Text("email", array('size' => 25));
		$email ->setLabel('Email address')
		  ->setRequired(true)
		  ->addFilter('StripTags')
		  ->addFilter('StringTrim')
		  ->addValidator('NotEmpty')
		 	->addValidator($emailDoesntExist)
		  ->addValidator('EmailAddress')
		  ->setDescription("Require a valid email address.");

		$password = new Zend_Form_Element_Password("password", array('size' => 25));
		$password ->setLabel('Password')
		  ->setRequired(true)
		  ->addFilter('StringTrim')
		  ->addValidator('NotEmpty');

		$repassword = new Zend_Form_Element_Password("repassword", array('size' => 25));
		$repassword ->setLabel('Retype password')
		  ->setRequired(true)
		  ->addFilter('StringTrim')
		  ->addValidator('NotEmpty');
		$repassword->setDescription("Retype the previous password to prevent errors.");

		$pays = new  Zend_Form_Element_Select("pays");
		  $pays ->setLabel('Country')
		  ->setRequired(true)
			->addMultiOptions(Zend_Locale::getCountryTranslationList(Zend_Registry::get('Zend_Locale')))
		  ->addValidator('NotEmpty');

		$pubKey = '';
		$privKey = '';
		$recaptcha = new Zend_Service_ReCaptcha($pubKey, $privKey);

		$adapter = new Zend_Captcha_ReCaptcha();
		$adapter->setService($recaptcha);

		$captcha = new Zend_Form_Element_Captcha('recaptcha', array( 'label' => "Captcha", 'captcha' => $adapter));
		$captcha->removeDecorator('label')->removeDecorator('errors');

		$this->addElements(array($login,$email, $password, $repassword, $pays, $captcha));
	}

	public function isValid($data)
  {
		$password 	= $this->getElement('password');
		$password->addValidator(new App_Validate_PasswordMatch($data['repassword']));

		return parent::isValid($data);;
  }
}

Modification de l’apparence du formulaire

Pour le moment notre formulaire est, nous pouvons le dire, sacrement moche ! Néanmoins tout ce dont nous avons besoin est présent !

Image 1

Maintenant on va re-skinner le formulaire car pour le moment il utilise les éléments DD-DT et nous voulons utiliser des Li.

Pour faire cela, nous allons créer une vue dans /application/modules/frontend/views/scripts/membre nommé registerform.phtml. C’est dans cette vue que nous allons définir la manière de rendu de notre formulaire.

Nous allons appeler cette vue dans notre formulaire, avant la fin du constructeur :

$this->setDecorators( array( array('ViewScript', array('viewScript' => 'membre/registerform.phtml'))));

Ce morceau de code va donc permettre de spécifier quelle vue va décrire notre formulaire.

et ensuite il nous reste à implémenter ce fichier .phtml :

<form action="< ?= $this->escape($this->element->getAction()) ?>"
      method="< ?= $this->escape($this->element->getMethod()) ?>" id="myform">
<fieldset>
  <legend><?php echo $this->translate('Create your account'); ?>
	<ul class="formRegister">
	  <?php foreach($this->element as $element): ?>
		<li>
			<span>
				<?php echo $this->formLabel($element->getName(),$this->translate($element->getLabel())) ?>
       	<?php echo $element->renderDescription() ?>
      </span>
      <?php if($element->getName() == "recaptcha"): ?>
      <?php echo $element->render();?>
      <?php else:?>
      <?php echo $this->{$element->helper}(
                         $element->getName(),
                         $element->getValue(),
                         $element->getAttribs(),
                         (method_exists($element,'getMultiOptions')) ? $element->getMultiOptions() : ''
      ) ?>
      <?php endif;?>
	    <?php echo $this->formErrors($element->getMessages()) ?>
		</li>
	  <?php endforeach; ?>
	</ul>
  <p><input type="submit" id='submit' value="<?php echo $this->translate('Register'); ?>" /></p>
</fieldset>
</form>

Ce script va disséquer les éléments que nous avons créés dans la première partie et afficher dans des éléments HTML ce qu’il faut et où il faut !

Excellent non? Bon ok, vous allez surement me dire que c’est toujours aussi moche …

Image 2

Vous avez raison !

Mais essayer d’ajouter ces lignes de CSS dans le fichier public/css/main.css :


#myform {
    font-size: 1em;
    margin: 0 auto;
    width: 560px;
} 

#myform fieldset {
    background-color: #F2F9FE;
    border: 1px solid #AEDCF5;
    margin: 10px 0 20px;
    padding: 20px 15px 10px;
    position: relative;
} 

#myform fieldset:hover {
    background-color: #FFFCCD;
    border: 1px solid #FFDB60;
} 

#myform fieldset:hover input, #myform fieldset:hover textarea {
    background-color: #FFFFFF;
    border: 1px solid #FFDB60;
} 

#myform fieldset:hover select {
    background-color: #FFFFFF;
    border: 1px solid #FFDB60;
}
#myform legend {
    color: #FF5A00;
    font-family: Georgia, "Times New Roman", Times, serif;
    font-size: 16px;
    font-style: italic;
    left: 10px;
    position: absolute;
    top: -8px;
} 

#myform fieldset:hover legend, .designField:hover legend {
    color: #3F87E9;
} 

#myform label {
    color: #000000;
    display: block;
    float: left;
    font-weight: bold;
    margin: 0 10px 0px 0;
    padding: 0;
    text-align: right;
    width: 190px;
	font-size: 12px;
}
#myform input {
    border: 1px solid #AEDCF5;
    color: #3F87E9;
    font-size: 12px;
	line-height: 24px;
    height: 24px;
    margin: 0 0 13px;
    padding: 7px 3px 3px 3px;
    width: 210px;
}
#myform span {
	margin-right: 20px;
	display:block;
	float: left;
	width: 190px;
	text-align: right;
}
#myform input:hover {
    border: 1px solid #41A9D8;
}
#myform textarea {
    border: 1px solid #AEDCF5;
    color: #3F87E9;
    font-size: 1em;
    width: 215px;
}
#myform select {
    border: 1px solid #AEDCF5;
    color: #3F87E9;
    font-size: 12px;
    height: 29px;
    margin: 0 0 13px;
    padding: 4px 3px 2px;
    width: 220px;
}
#myform select option {
	padding-left: 10px;
}
#myform select:hover {
    border: 1px solid #41A9D8;
} 

#myform p {
	text-align: center;
}
#myform #submit {
    border: 1px solid #000;
    height: 35px;
	padding: 5px;
    width: 92px;
	font-weight: bold;
	color: #000;
	background: #c0c0c0;
	line-height: 35px;
	font-variant: small-caps;
	margin: 0;
}
#myform #submit:hover {
    border: 0 none;
    cursor: pointer;
}

et le changement est radical !

Image 3

Finitions du formulaire.

Maintenant passons à la traduction du formulaire.
Il y a plusieurs choses à voir, par exemple :

  • Traduction du Formulaire
  • Traduction des messages d’erreurs
  • Ajout des messages dans nos fichiers de traductions.

Pour résoudre les deux premiers points, rendez-vous dans notre plugin de traduction (application/plugins/Translate.php) et ajouter ces deux lignes de code à la fin de la méthode routeShutdown:

Zend_Form::setDefaultTranslator($translate);
Zend_Validate_Abstract::setDefaultTranslator($translate);

Il ne nous reste plus qu’à aller dans notre fichier de traduction fr_FR.php et le remplir comme suivant :

return array(
'hello' => 'Bonjour et bienvenue sur notre site',
'english' => 'Anglais',
'french' => 'Français',
'Watch My Desk - Show Off your Geekstation' => 'Watch My Desk - Show Off your Geekstation',
'Browse' => 'Explorer',
'Join Now' => "S'inscrire",
'Login' => 'Identification',
'Your Desk' => 'Ton Bureau',
'Show off' => 'Montre',
'your geekstation.' => 'ton bureau de geek',
'Watch My Desk is a website where you can share, browse and rate pictures of desks and computers of the world.' => 'Watch my desk est un site où vous pouvez partager, explorer et noter des photos du bureaux et ordinateurs du monde entier',
'Join the website to share your desk or post comments, just in 2 clics !' => 'Inscrivez vous pour partager vos bureaux, poster des coms... en 2 clics !',
'Join now and show off your desk with the community !' => 'Inscris-toi maintenant et partage ton bureau de geek avec la communauté !',
'Login between 3 and 20 alphanumerics characters.' => 'Pseudo compris entre 3 et 20 caractères alphanumériques.',
'Require a valid email address.' => 'Adresse email valide requise.',
'Retype the previous password to prevent errors.' => 'Saisissez de nouveau le mot de passe.',
'Create your account' => 'Créez votre compte',
'Country' => 'Pays',
'Retype password' => 'Password (encore)',
'Register' => 'S\'enregistrer'
);

Valider notre formulaire dans le controller

Rendez-vous dans notre contrôleur MembreController (dans application/modules/frontend/controllers).
Il va falloir procéder à la validation de notre formulaire :

class MembreController extends Zend_Controller_Action
{

	function newAction()
	{
		$this->view->form = $form = new Form_Inscription;
		if($post = $this->_request->isPost()){
			$formData = $this->getRequest()->getPost();
			if($form->isValid($formData)){
				// Traitement
			}else{
				$form->populate($formData);
			}
		}
	}
}

Vous pouvez tester ! Maintenant si une erreur est détecté, nous aurons les messages d’erreurs pour chaque éléments de notre formulaire.

Ajout du membre dans la base de donnée

Pour ajouter un membre une fois la validation terminer nous allons utiliser notre modèle Model_DbTable_Membres créé dans les premiers épisodes.

Mais avant nous allons ajouter un champs dans la base de donnée. Ce champs sera nommé token, sera un varchar de 32 caractère et servira à l’envoie d’un mail de confirmation à notre utilisateur !

Rendez vous dans le fichier Model_DbTable_Membres pour créer une nouvelle méthode d’ajout de membre.

public function addUser($data)
	{
		$otherData = array(
				'ip_inscription' 		=> $_SERVER['REMOTE_ADDR'],
				'pass' 							=> md5($data['password']),
				'date_inscription' 	=> date('Y-m-d H:i:s'),
				'lvl'								=> 1,
				'etat'							=> 0, // Doit valider son compte
				'token'							=> md5($data['email'].$_SERVER['REMOTE_ADDR'].$data['password'])
		);
		unset($data['recaptcha_challenge_field'],
		$data['recaptcha_response_field'],
		$data['repassword'],
		$data['password']);
		$userData = array_merge($data, $otherData);

		$this->insert($userData);

		return $otherData['token'];
	}

Ici le processus est très simple, on va récupérer les informations dont on a besoin et les traiter dans cette méthode. C’est à dire supprimer ceux qui ne nous servent pas, conserver celle qui nous servent et en créer de nouvelle.

On voit aussi que le champ token est utiliser ici, c’est un md5 de l’email, de l’ip et du mot de passe de l’utilisateur.

Une fois cela fait, placez-vous dans la partie traitement du code précédent et ajoutez :

$db = Zend_Db_Table::getDefaultAdapter();
$db->beginTransaction();

try{
	$user = new Model_DbTable_Membres();
	$token = $user->addUser($formData);

	$db->commit();
	Zend_Session::regenerateId();
}catch (Exception $e)
{
	$db->rollBack();
	throw $e;
}

$this->_redirect('/');

Alors, beaucoup de choses ici ! on récupère l’adapteur de notre base de donnée pour pouvoir gérer les transactions à la main. Simplement car si il y a un problème (on ne sait jamais) lors de l’enregistrement, on puisse revenir à l’état initial avec un rollback !

Dans le bloc try, on créer un objet Membres, on utilise la méthode addUser écrite précédemment, et on commit. Si une erreur se produit dans ce bloc, on rollback directement.

Je trouve cela très pratique de pouvoir gérer manuellement les transactions SQL personnellement !
Après le commit(), on génère un nouvel identifiant de session, uniquement pour la sécurité.

Email de validation

Et voila ! Nous pouvons nous inscrire sur WatchMyDesk! Mais le compte par défaut n’est pas actif… il va donc falloir envoyer un mail à l’utilisateur pour lui demander la confirmation de son compte.

Pour faire cela, rendez vous dans le fichier application/configs/application.ini et ajouter ces lignes de configurations juste avant la ligne [development : production] :

[mail]
mail.from.name    	= WatchMyDesk Support
mail.from.address 	= support@watchmydesk.com
mail.config.ssl 		= tls
mail.config.port 		= 587
mail.config.auth 		= login
mail.config.username     = votre email pour gmail
mail.config.password 		= votre mot de passe gmail

Pour envoyer un message je vais utiliser le service SMTP de Gmail, mais vous pouvez utiliser le serveur SMTP de votre propre serveur si vous le souhaitez !

Direction ensuite le fichier Bootstrap.php pour ajouter une méthode de configuration pour les emails.

protected function _initMails()
{
	$config = new Zend_Config_Ini(APPLICATION_PATH . '/configs/application.ini', 'mail');
	$mailConfig = $config->toArray();

	Zend_Registry::set('Mail_Config', $mailConfig['mail']['config']);
}

De cette façon, nous aurons accès dans toute notre application à la configuration de nos emails.

Revenons dans la méthode newAction de notre contrôleur membreController pour finir le code d’envois d’email juste après l’ajout d’un utilisateur:

$smtpConnection = new Zend_Mail_Transport_Smtp('smtp.gmail.com', Zend_Registry::get('Mail_Config'));
$mail = new Zend_Mail('utf-8');
$mail	->addTo($formData['email'])
->setFrom('support@watchmydesk.com', 'WatchMyDesk Support')
->setSubject('Bienvenue sur WatchMyDesk')
->setBodyHtml('

Dear '.$formData['email'].'

Welcome to WatchMyDesk !!

Please visit this url to activate your account:
'.ROOT_URL.'/membre/activate/id/'.$token.'

See you there,
The WatchMyDesk Team');
$mail->send($smtpConnection);

Notre email peut maintenant être envoyer tranquillement.
Vous avez du remarquer que nous demandons l’activation du compte par un lien dans l’email. Il va donc falloir créer l’action et la méthode du model Membre pour valider l’utilisateur.

Dans le contrôler membre:

public function activateAction()
{
	$token = $this->getRequest()->getParam('id');
	if($token){
		$user = new Model_DbTable_Membres();
		$user->validateUserByToken($token);
	}
	$this->_redirect('/');
}

Le token est passé dans le paramètre dans l’url est est récupéré dans le contrôleur pour valider l’utilisateur.

Dans le modèle Membres:

public function validateUserByToken($token)
{
	$where = array('token = ?' => (int)$token);
	$this->update(array('etat' => 1), $where);
	return $user;
}

Une simple mise à jour est effectué ici, en changeant l’état de 0 à 1 en fonction du token passé.

Conclusion

Ok, cet article est long mais il est très complet sur le processus d’inscription d’un utilisateur. Je vous conseille de prendre le temps d’analyser le code créer ici, de tester par vous même !

Retrouver la gestion des formulaire pour Symfony sur la FermeDuWeb:

N’hésitez pas à me poser votre question dans les commentaires! et Bonne journée !