Dans cet article, nous allons construire un formulaire dont la particularité est de comporter une liste de sélection avec des données personnalisées. Profitons de cette application pour voir comment créer un élément Select de formulaire avec une dépendance.
NOTE : le long du tutoriel, je vais vous faire rééditer des classes et des vues sur lesquels on sera déjà passé. Il va y avoir des parties qui auront disparu au second passage : typiquement les use, les instructions extends, implements etc... Cela ne veut pas dire qu'il faut les retirer. C'est juste pour alléger le code. S'il faut retirer du code, soit je vous remettrais le passage intégralement, pour voir ce qui a disparu, soit je vous signalerais qu'il faut effacer une ligne pour la remplacer par une autre.
Une note est constituée du numéro de la table (un texte libre), du nom du serveur (une valeur parmi la liste des serveurs du café) et, ça, c'est la valeur technique, l'id unique (guid) de la note.
L'élément Select
La liste des serveurs
La liste des serveurs du café est un objet CoffeeBar\Entity\Waiters
qui contient un tableau (array
) avec les noms et les identifiants de chaque serveur du café. On peut imaginer que cette objet CoffeeBar\Entity\Waiters
est relié à une base de données et extrait les informations de la base de données. Dans notre cas, la liste va être créée en statique.
// module/CoffeeBar/src/CoffeeBar/Entity/Waiters.php
<?php
namespace CoffeeBar\Entity ;
use ArrayObject ;
// l'objet Waiters hérite de l'objet ArrayObject
class Waiters extends ArrayObject
{
public function __construct(Array $array=null)
{
$array = array( ‘paul’ => 'Paul',
‘john’ => 'John',
‘melissa’ => 'Melissa',
‘julie’ => 'Julie',
‘michael’ => 'Michael'
) ;
parent::__construct($array) ;
}
}
Maintenant, on souhaite envoyer cette liste de serveur dans un élément Select
sans avoir à ressaisir la liste de nos serveurs.
Dans la documentation de l'élément Select, on remplit la liste des valeurs avec la méthode Select::setValueOptions($array)
.
$select = new Element\Select('mon_element_select') ;
$select->setValueOptions(array(
'0' => 'element1',
'1' => 'element2',
'2' => 'element3',
'clé' => 'valeur',
));
Ou en utilisant une notation en tableau :
$form->add(array(
'type' => 'Select',
'options' => array(
'value_options' => array(
'0' => 'element1',
'1' => 'element2',
'2' => 'element3',
'clé' => 'valeur',
),
),
)) ;</pre>
Notre besoin crucial est de remplacer notre tableau attendu pour value_options
par la liste de nos serveurs. Le premier point est de vérifier que notre tableau de serveurs est au bon format 'clé' => 'valeur'
... Pas de tableau à plusieurs entrées, et 'valeur</code>'
doit être une chaîne de caractère et non pas un objet.
L'élément Select personnalisé
Avec Zend Framework 2, on crée un élément Select
personnalisé, CoffeeBar\Form\WaiterSelect
, qui hérite de l'objet Zend\Form\Element\Select
du framework. On va lui injecter notre objet CoffeeBar\Entity\Waiters
et on va assigner les valeurs de l'objet CoffeeBar\Entity\Waiters
aux valeurs de l'élément CoffeeBar\Form\WaiterSelect
.
// module/CoffeeBar/src/CoffeeBar/Form/WaiterSelect
<?php
namespace CoffeeBar\Form ;
use CoffeeBar\Entity\Waiters;
use Zend\Form\Element\Select;
class WaiterSelect extends Select
{
protected $waiters ;
// dans le constructeur, on injecte la liste des serveurs (objet Waiters)
// ainsi, dans l'objet WaiterSelect, on peut l'utiliser comme on le souhaite
public function __construct(Waiters $waiters)
{
$this->waiters = $waiters ;
}
// dans la méthode init(), on récupère la liste des serveurs (de l'objet Waiters)
// on définit la liste des serveurs comme la liste des options de l'élément Select
// $this->setValueOptions() est une méthode qui fait partie de l'objet Select
// la méthode ArrayObject::getArrayCopy() prend l'objet ArrayObject tel quel et le retourne sous forme de tableau
public function init()
{
$this->setValueOptions($this->waiters->getArrayCopy()) ;
}
}
On charge les éléments dans le gestionnaire de services (Service Manager)
// module/CoffeeBar/Module.php
<?php
namespace CoffeeBar;
use CoffeeBar\Form\WaiterSelect;
use Zend\ModuleManager\Feature\FormElementProviderInterface;
// dès qu'il s'agit d'éléments de formulaire personnalisés
// il faut implémenter FormElementProviderInterface
class Module implements FormElementProviderInterface
{
public function getConfig() {...}
public function getAutoloaderConfig() {...}
// l'interface FormElementProviderInterface
// a la méthode getFormElementConfig
public function getFormElementConfig() {
return array(
'factories' => array(
// déclarer l'élément de formulaire dans le manager de formulaire
// dans mon exemple, la clé est 'WaiterSelect'
// mais n'importe quelle clé est possible
'WaiterSelect' => function($sm) {
$serviceLocator = $sm->getServiceLocator() ;
$waiters = $serviceLocator->get('CoffeeBarEntities\Waiters') ;
// ici par contre, c'est l'objet CoffeeBar\Form\WaiterSelect
// notez l’injection de l'objet Waiters dans le constructeur
$select = new WaiterSelect($waiters) ; //CoffeeBar\Form\WaiterSelect
return $select ;
},
),
);
}
// on charge le Service Manager
public function getServiceConfig()
{
return array(
'invokables' => array(
// clé dans le Service Manager => objet
'CoffeeBarEntities\Waiters' => 'CoffeeBar\Entity\Waiters',
),
) ;
}
}
On va maintenant créer notre formulaire, qui va contenir un élément caché, pour l'id unique, un élément texte pour le numéro de la table et notre élément select personnalisé, pour la liste des serveurs.
Le formulaire
// module/CoffeeBar/src/CoffeeBar/Form/OpenTabForm.php
<?php
namespace CoffeeBar\Form ;
use Zend\Form\Element\Csrf;
use Zend\Form\Form;
class OpenTabForm extends Form
{
// la fonction init va charger l'élément de formulaire customisé
// les autres éléments de formulaire "standards" peuvent être créé dans le constructeur
public function init()
{
$this->add(array(
'name' => 'waiter',
// utiliser la clé définie dans getFormElementConfig dans la classe Module
'type' => 'WaiterSelect',
'options' => array(
'label' => 'Serveur',
),
'attributes' => array(
// c'est l'une des classes CSS de Bootstrap Twitter
'class' => 'form-control',
),
)) ;
}
public function __construct()
{
parent::__construct('opentab') ;
$this->setAttribute('method', 'post') ;
// le champ id est un id unique (guid) caché
// il sera généré automatiquement dans la vue
$this->add(array(
'name' => 'id',
'type' => 'hidden',
)) ;
$this->add(array(
'name' => 'tableNumber',
'options' => array(
'label' => 'Numéro de la table',
),
'attributes' => array(
'required' => 'required',
'class' => 'form-control',
),
)) ;
$this->add(new Csrf('security')) ;
$this->add(array(
'name' => 'submit',
'type' => 'Submit',
'attributes' => array(
'value' => 'Open',
'class' => 'btn btn-default',
),
)) ;
}
}
Retournons dans le gestionnaire de services pour ajouter le formulaire
// module/CoffeeBar/Module.php
<?php
namespace CoffeeBar;
class Module implements FormElementProviderInterface
{
public function getConfig() {...}
public function getAutoloaderConfig() {...}
public function getFormElementConfig() {...}
// on charge le service manager
public function getServiceConfig()
{
return array(
'factories' => array(
'OpenTabForm' => function($sm) {
// parce que le formulaire OpenTabForm utilise un élément de formulaire personnalisé
// il faut utiliser $this->serviceLocator->get('FormElementManager') ;
// et utiliser le formulaire à partir du Form Element Manager
$formManager = $sm->get('FormElementManager') ;
$form = $formManager->get('CoffeeBar\Form\OpenTabForm') ;
return $form ;
},
),
) ;
}
}
Le rendu dans le navigateur
Le contrôleur
// module/CoffeeBar/src/CoffeeBar/Controller/TabController.php
<?php
namespace CoffeeBar\Controller ;
use Zend\Mvc\Controller\AbstractActionController;
class TabController extends AbstractActionController
{
public function openAction()
{
// récupérer le formulaire dans le ServiceManager
$form = $this->serviceLocator->get('OpenTabForm') ;
$request = $this->getRequest() ;
// si le formulaire a été posté
if($request->isPost()) {
// assigné les données du tableau $_POST aux éléments du formulaire
$form->setData($request->getPost()) ;
if($form->isValid()) {
// traiter les données du formulaire
}
}
$result['form'] = $form ;
return array('result' => $result) ;
}
}
La vue
Utilisons, pour le rendu du formulaire, les aides de vues mise à disposition par Zend Framework 2.
// module/CoffeeBar/view/coffee-bar/tab/open.phtml
<h1>Ouvrir une nouvelle note</h1>
<?php
$form = $this->result['form'] ;
$form->prepare() ;
// l’action va nous renvoyer sur la route ‘tab/open’
// soit TabController / OpenAction tel que défini dans la route dans module.config.php
$form->setAttribute('action', $this->url('tab/open')) ;
$form->setAttribute('method', 'post') ;
// on génère l'id unique dans la vue
$id = $form->get('id') ;
$id->setValue(uniqid()) ;
?>
<?php echo $this->form()->openTag($form) ; ?>
<div class='form-group'>
<?php echo $this->formRow($form->get('tableNumber')) ; ?>
</div>
<div class='form-group'>
<?php echo $this->formRow($form->get('waiter')) ; ?>
</div>
<?php
echo $this->formRow($form->get('security')) ;
echo $this->formHidden($id) ;
echo $this->formRow($form->get('submit')) ;
echo $this->form()->closeTag() ;
?>
Notre formulaire est fini.
Ainsi, pour personnaliser un élément select, il faut
- un objet métier avec la liste des valeurs
- s'assurer que cet objet retourne un tableau à une dimension :
'clé' => 'valeur'
(soit par défaut, soit avec une méthode dédiée) - créer un élément
select
personnalisé, qui hérite deZend\Form\Element\Select
- implémenter l'interface
FormElementProviderInterface
dans la classeModule
- injecter les dépendances dans la configuration des éléments de formulaire
Module::getFormElementConfig()
- utiliser le gestionnaire de formulaire 'FormElementManager' qui va se trouver dans le gestionnaire de service.
Dans le chapitre suivant, on va voir comment on récupère les données du formulaire (hydratées dans un objet) et comment on les exploite dans notre application.
Vous trouverez l'intégralité de l'application sur mon github