Pour pouvoir gérer une "to do list", nous allons avoir besoin d’un formulaire pour l’ajout et l’édition des éléments de la liste. Nous avons besoin d’un champ texte pour l’intitulé, d’un champ numérique pour la position, d’une checkbox pour savoir si la tâche est réalisée et d’un bouton de validation.
Rendez-vous dans le répertoire TodoModule\Views
et créez un fichier form-item-add.php
, éditez-le et ajoutez les lignes suivantes pour créer le formulaire :
<!-- modules/TodoModule/Views/form-todo-item-add.php -->
<div class="container">
<div class="row">
<div class="col-md-12">
<form action="?todo/item" method="POST">
<div class="form-group">
<label for="todo-item-title">Item<span class="form-required">*</span></label>
<input name="title" type="text" id="todo-item-title" class="form-control" maxlength="255" placeholder="Tâche à réaliser" required>
</div>
<div class="form-group">
<label for="todo-item-height">Position<span class="form-required">*</span></label>
<input name="height" type="number" id="todo-item-height" class="form-control" min="1" required value="1">
</div>
<div class="form-group">
<input name="submit" type="checkbox" id="item-todo-submit">
<label for="item-todo-submit">Réalisé</label>
</div>
<input name="submit" type="submit" class="btn btn-success" value="Enregistrer">
</form>
</div>
</div>
</div>
POST
à la fonction store()
via la route ?todo/item
,Une fois ce formulaire créé, rendez-vous dans le contrôleur TodoController
et ajoutez à la fonction create( $req )
les lignes suivantes :
# modules/TodoModule/Controller/TodoController.php
public function create( $req )
{
$tpl = new Template('html.php', VIEWS_TODO);
$block = new Template('form-todo-item-add.php', VIEWS_TODO);
/* Je retourne mon formulaire dans une sous template dans ma page principale. */
return $tpl->addVar('main_title', 'Ajout d’un élément à la liste')
->addBlock('content', $block)
->render();
}
Vérifions que notre fonction renvoie bien notre formulaire d’ajout d’items. Rendez-vous sur l’URL http://127.0.0.1/soosyze/?todo/item.
Dans les cas simples (petite application, page simple…), je vous recommande de créer votre formulaire en brut dans un template et de l’appeler directement, comme nous l’avons fait.
Dans le cas d’un CMS, les modules doivent pouvoir communiquer entre eux et notamment modifier dynamiquement un formulaire. C’est l’une des raisons pour lesquelles il existe le composant FormBuilder
. Celui-ci permet d’écrire un formulaire dynamique en PHP et de le générer en HTML.
Rendez-vous dans le contrôleur TodoController
, ajoutez le composant Soosyze\Components\Form\FormBuilder
, et dans la fonction itemAdd()
ajoutez les lignes suivantes :
<?php
# modules/TodoModule/Controller/TodoController.php
namespace TodoModule\Controller;
/* Déclaration du composant de génération de formulaire. */
use Soosyze\Components\Form\FormBuilder;
use Soosyze\Components\Http\Redirect;
use Soosyze\Components\Template\Template;
define("CONFIG_TODO", MODULES_CONTRIBUED . 'TodoModule' . DS . 'Config' . DS);
define("VIEWS_TODO", MODULES_CONTRIBUED . 'TodoModule' . DS . 'Views' . DS);
class TodoController extends \Soosyze\Controller
{
public function create( $req )
{
/* Déclaration du formulaire. */
$form = new FormBuilder([]);
/**
* Je crée mon champ texte avec le FormBuilder,
* Le premier paramètre est sa clé unique (le paramètre name) et le second son id.
*/
$form->text('title', 'todo-item-title');
$tpl = new Template('html.php', VIEWS_TODO);
$block = new Template('form-todo-item-add.php', VIEWS_TODO);
/* Nous ajoutons le formulaire à notre sous template form.php */
$block->addVar('form', $form);
return $tpl->addVar('main_title', 'Ajout d’un élément à la liste')
->addBlock('content', $block)
->render();
}
}
Puis, modifiez la vue form-todo-item-add.php
pour utiliser FormBuilder
:
<!-- modules/TodoModule/Views/form-todo-item-add.php -->
<div class="container">
<div class="row">
<div class="col-md-12">
<form action='?todo/item' method="POST">
<div class="form-group">
<label for="todo-item-title">Item<span class="form-required">*</span></label>
<?php echo $form->form_input('title', [
'class' => 'form-control',
'id' => 'todo-item-title'
'maxlength' => 255,
'placeholder' => 'Tâche à réaliser',
'required' => true
]); ?>
</div>
<div class="form-group">
<label for="todo-item-height">Position<span class="form-required">*</span></label>
<input name="height" type="number" id="todo-item-height" class="form-control" min="1" required value="1">
</div>
<div class="form-group">
<input name="achieve" type="checkbox" id="todo-item-achieve">
<label for="todo-item-achieve">Réalisé</label>
</div>
<input name="submit" type="submit" class="btn btn-success" value="Enregistrer">
</form>
</div>
</div>
</div>
Vérifions que notre fonction renvoie bien notre formulaire d’ajout d’item. Rendez-vous sur l’URL http://127.0.0.1/soosyze/?todo/item.
Comme vous pouvez le constater, nous utilisons la méthode form_input()
pour appeler le champ préalablement créé dans notre contrôleur. Le premier paramètre est la clé unique du champ, et le second paramètre est un tableau d’attributs acceptés dans une balise <input>
. Nous y avons mis les attributs class
, id
, placeholder
et required
.
Maintenant que vous savez comment créer un champ dans le contrôleur et l’appeler dans la vue, nous allons tranposer tous les autres éléments du formulaire, côté contrôleur, et les appeler dynamiquement dans la vue.
Rendez-vous dans le contrôleur TodoController
, et dans la fonction create()
ajoutez les lignes suivantes :
# modules/TodoModule/Controller/TodoController.php
public function create( $req )
{
$form = new FormBuilder([]);
/* Tous nos champs et labels sont créés dans le contrôleur. */
$form->label('todo-item-title-label', 'Item')
->text('title', 'todo-item-title')
->label('todo-item-height-label', 'Position')
->number('height', 'todo-item-height')
->checkbox('achieve', 'item-todo-achieve')
->label('todo-item-achieve-label', 'Réalisé')
->submit('submit', 'Enregistrer');
$tpl = new Template('html.php', VIEWS_TODO);
$block = new Template('form-todo-item-add.php', VIEWS_TODO);
$block->addVar('form', $form);
return $tpl->addVar('main_title', 'Ajout d’un élément à la liste')
->addBlock('content', $block)
->render();
}
Puis modifier la vue form-todo-item-add.php
pour appeler dynamiquement tous les champs :
<!-- modules/TodoModule/Views/form-todo-item-add.php -->
<div class="container">
<div class="row">
<div class="col-md-12">
<?php echo $form->form_open(['method' => 'post', 'action' => '?todo/item']); ?>
<div class="form-group">
<?php echo $form->form_label('todo-item-title-label', ['for'=>'todo-item-title']); ?>
<?php echo $form->form_input('title', [
'class' => 'form-control',
'id' => 'todo-item-title'
'maxlength' => 255,
'placeholder' => 'Tâche à réaliser',
'required' => true
]); ?>
</div>
<div class="form-group">
<?php echo $form->form_label('todo-item-height-label', ['for'=>'todo-item-height']); ?>
<?php echo $form->form_input('height', [
'class' => 'form-control',
'min' => 1,
'required' => true,
'value' => 1
]); ?>
</div>
<div class="form-group">
<?php echo $form->form_input('achieve'); ?>
<?php echo $form->form_label('todo-item-achieve-label', ['for'=>'todo-item-achieve']); ?>
</div>
<?php echo $form->form_input('submit', [
'class' => 'btn btn-success'
]); ?>
<?php echo $form->form_close(); ?>
</div>
</div>
</div>
Vérifions que notre fonction renvoie bien notre formulaire d’ajout d’item. Rendez-vous sur l’URL http://127.0.0.1/soosyze/?todo/item.
Cette façon de faire permet de générer un formulaire et de récupérer les champs avec plusieurs avantages :
L’utilisation des formulaires dynamiques apporte une très grande souplesse dans votre application. Rendre dynamique un formulaire signifie pouvoir changer le comportement des champs avant son affichage. Par exemple :
Pour rendre votre formulaire totalement dynamique, il doit être généré entièrement côté contrôleur, c’est-à-dire :
<div></div>
qui permettent de grouper les champs, sont également écrits dans le contrôleur,Rendez-vous dans le contrôleur TodoController
, et dans la fonction create()
ajoutez les lignes suivantes :
# modules/TodoModule/Controller/TodoController.php
public function create( $req )
{
/* Déclaration du formulaire et de ses attributs. */
$form = new FormBuilder(['method' => 'post', 'action' => '?todo/item']);
/* Création d’un groupe de champ. */
$form->group('todo-item-title-group', 'div', function ($form) {
$form->label('todo-item-title-label', 'Item', ['for'=>'todo-item-title'])
->text('title', 'todo-item-title', [
'class' => 'form-control',
'maxlength' => 255,
'placeholder' => 'Tâche à réaliser',
'required' => true
]);
}, ['class' => 'form-group']);
/* Vous pouvez également chaîner l’ensemble des méthodes. */
$form->group('todo-item-height-group', 'div', function ($form) {
$form->label('todo-item-height-label', 'Position', ['for'=>'todo-item-height'])
->number('height', 'todo-item-height', [
'class' => 'form-control',
'min' => 1,
'required' => true,
'value' => 1
]);
}, ['class' => 'form-group'])
->group('todo-item-achieve-group', 'div', function ($form) {
$form->checkbox('achieve', 'todo-item-achieve')
->label('todo-item-achieve-label', 'Réalisé', ['for'=>'todo-item-achieve']);
}, ['class' => 'form-group'])
->submit('submit', 'Enregistrer', [ 'class' => 'btn btn-success' ]);
$tpl = new Template('html.php', VIEWS_TODO);
$block = new Template('form-todo-item-add.php', VIEWS_TODO);
$block->addVar('form', $form);
return $tpl->addVar('main_title', 'Ajout d’un élément à la liste')
->addBlock('content', $block)
->render();
}
Vous pouvez appeler les groupes de champs directement :
<!-- modules/TodoModule/Views/form-todo-item-add.php -->
<div class="container">
<div class="row">
<div class="col-md-12">
<?php echo $form->form_open(); ?>
<?php echo $form->form_group('todo-item-title-group'); ?>
<?php echo $form->form_group('todo-item-height-group'); ?>
<?php echo $form->form_group('todo-item-achieve-group'); ?>
<?php echo $form->form_input('submit'); ?>
<?php echo $form->form_close(); ?>
</div>
</div>
</div>
Mais le but étant de générer entièrement le formulaire (si celui-ci est créé dans le contrôleur), le code suivant suffit :
<!-- modules/TodoModule/Views/form-todo-item-add.php -->
<div class="container">
<div class="row">
<div class="col-md-12">
<?php echo $form->renderForm(); ?>
</div>
</div>
</div>
Vérifions que notre fonction renvoie bien notre formulaire d’ajout d’item. Rendez-vous sur l’URL http://127.0.0.1/soosyze/?todo/item. Vous pouvez également remplir le formulaire et cliquer sur Enregistrer pour vérifier si le formulaire est bien envoyé dans la fonction de validation d’ajout d’item.
Pour tous les autres champs et le reste des fonctionnalités de FormBuilder, je vous invite à lire la PhpDoc du composant.
La faille CSRF (Cross-Site Request Forgery) est un type de vulnérabilité informatique qui vise à faciliter une requête HTTP.
Pour illustrer cela, imaginez que vous créiez un formulaire en local qui pointe sur l’URL de validation des données : vous pourriez alors envoyer des données sans passer par le formulaire du site.
Pour corriger cette faille, vous devez vous assurer que les données proviennent bien de votre formulaire et pas d’un autre. Un moyen très simple pour s’en assurer est de créer un token aléatoire de validité dans votre formulaire. À sa soumission, vérifiez si le token a bien été généré quelques minutes auparavant.
Le composant FormBuilder
propose une fonction token()
générant un champ caché avec une valeur aléatoire. Cette valeur sera stockée dans une variable de session $_SESSION['token']
, et la date de génération milliseconde sera stocké dans la variable de session $_SESSION['token_time']
.
Il suffit de contrôler si la valeur du token et la durée qui y est attribuée correspondent aux valeurs stockées en session dans les fonctions de validation de données.
Rendez-vous dans le contrôleur TodoController
, et dans la fonction create()
ajoutez la méthode token()
à notre génération de formulaire :
# modules/TodoModule/Controller/TodoController.php
public function create( $req )
{
$form = new FormBuilder(['method' => 'post', 'action' => '?todo/item']);
$form->/* […] */
/* Ajouter la fonction token en fin de formulaire pour une meilleure visibilité. */
->token()
->submit('submit', 'Enregistrer', [ 'class' => 'btn btn-success' ]);
/* […] */
}
Cette méthode générera un code HTML semblable à celui-ci :
<input name="token" type="hidden" value="261525b507996dcd958.39126826">
Maintenant, je vous propose cet exercice : créer le formulaire pour la page d’édition d’un item de la "to do list".
L’exercice est assez simple, car un formulaire d’édition est pratiquement identique à un formulaire de création. Les seuls points divergeants sont :
Comme vous n’utilisez pas de base de données pour le moment, je vous fournis une fonction à ajouter dans votre contrôleur pour simuler la récupération d’un élément de la liste via son identifiant :
# modules/TodoModule/Controller/TodoController.php
private function getItem( $id )
{
$data = $this->getList();
/* Retourne un item de la liste ou un tableau vide s’il n’existe pas. */
return isset($data[$id])
? $data[$id]
: [];
}
Voici la correction de notre fonction edit()
du contrôleur :
# modules/TodoModule/Controller/TodoController.php
public function edit( $id, $req )
{
if (!($data = $this->getItem( $id ))) {
/**
* Si vous avez regardé la PhpDoc de l’objet Controller,
* celui-ci fournit la méthode get404() qui retourne
* un objet Response avec le statut 404.
*/
return $this->get404($req);
/**
* La seconde solution peut-être l’utilisation directe d’un objet Response.
* return new Response(404, new Stream( 'Aucun item ne correspond à l’identifiant ' . $id ) );
*/
}
/* La déclaration du FormBuilder vient après la condition d’existence de l’item. */
/* Ça ne sert à rien d’exécuter du code s’il n’ai pas utilisé en cas d’erreur. */
$form = new FormBuilder(['method' => 'post', 'action' => '?todo/item/' . $id . '/edit']);
/**
* On injecte les données dans le formulaire
* avec l’utilisation de 'use' pour les fonctions anonymes.
*/
$form->group('todo-item-title-group', 'div', function ($form) use ($data){
$form->label('todo-item-title-label', 'Item', ['for'=>'todo-item-title'])
->text('title', 'todo-item-title', [
'class' => 'form-control',
'maxlength' => 255,
'placeholder' => 'Tâche à réaliser',
'required' => true,
'value' => $data['title']
]);
}, ['class' => 'form-group'])
->group('todo-item-height-group', 'div', function ($form) use ($data){
$form->label('todo-item-height-label', 'Position', ['for'=>'todo-item-height'])
->number('height', 'todo-item-height', [
'class' => 'form-control'
'min' => 1,
'required' => true,
'value' => $data['height']
]);
}, ['class' => 'form-group'])
->group('todo-item-achieve-group', 'div', function ($form) use ($data){
$form->checkbox('achieve', 'todo-item-achieve', ['checked' => $data['achieve']])
->label('todo-item-achieve-label', 'Réalisé', ['for'=>'todo-item-achieve']);
}, ['class' => 'form-group'])
->token()
->submit('submit', 'Enregistrer', [ 'class' => 'btn btn-success' ]);
$tpl = new Template('html.php', VIEWS_TODO);
/* On change de template. */
$block = new Template('form-todo-item-edit.php', VIEWS_TODO);
$block->addVar('form', $form);
return $tpl->addVar('main_title', 'Modification d’un élément à la liste')
->addBlock('content', $block)
->render();
}
Et le template form-todo-item-add.php
:
<!-- modules/TodoModule/Views/form-todo-item-add.php -->
<div class="container">
<div class="row">
<div class="col-md-12">
<?php echo $form->renderForm(); ?>
</div>
</div>
</div>
Vérifions que notre fonction renvoie bien notre formulaire d’édition d’item. Rendez-vous sur l’URL http://127.0.0.1/soosyze/?todo/item/1/edit.
Vous pouvez également vérifier si une erreur 404 est levée, dans le cas où l’URL pointe sur un item non existant. Rendez-vous sur l’URL http://127.0.0.1/soosyze/?todo/item/4/edit.
Comme votre module fonctionne sur le CMS, il renvoie par défaut sa page 404. Si on avait utilisé un framework, le message aurait été affiché dans l’objet Response
.
Vous pouvez retrouver les sources de ce chapitre en suivant ce lien.