V. Le Modèle (la base de données)▲
V-A. Introduction▲
Maintenant que nous avons séparé le contrôle de l'application de la Vue affichée, il est temps de passer à la partie Modèle de l'application. Rappelez-vous que le Modèle est la partie du MVC qui s'occupe de l'objectif central de l'application (la logique applicative) et ainsi, dans notre cas, dialogue avec la base de données. Nous utiliserons la classe Zend_Db_Table qui recherche, ajoute, modifie ou supprime des enregistrements de la base de données.
V-B. Configuration▲
Pour utiliser Zend_Db_Table, nous avons besoin de lui dire quelle base de données utiliser, ainsi que le nom d'utilisateur et le mot de passe. Puisque nous préférons ne pas inclure ces informations dans le code de l'application, nous allons utiliser un fichier de configuration pour les conserver.
Le Zend Framework propose une classe Zend_Config qui fournit un accès orienté objet aux fichiers de configuration. Ce fichier peut être soit un fichier INI soit XML. Nous utiliserons la méthode INI :
[general]
db.adapter = PDO_MYSQL
db.params.host = localhost
db.params.username = rob
db.params.password = 123456
db.params.dbname = zftest
Pensez évidemment à mettre vos propres informations, pas les miennes !
L'utilisation de Zend_Config est vraiment facile :
$config
=
new Zend_Config_Ini('
config.ini
'
,
'
section
'
);
Dans le cas ci-dessus, Zend_Config_Ini charge une section depuis le fichier INI, pas toutes les sections (bien que ce soit possible si on le souhaite). Zend_Config_Ini lit également le "point" dans le paramètre comme un séparateur de répertoires pour regrouper les paramètres en relation les uns avec les autres. Dans notre fichier config.ini, l'hôte, le nom d'utilisateur, le mot de passe et le nom de la base de données seront regroupés dans $config->db->params.
Nous allons charger notre fichier de configuration dans notre Contrôleur (index.php) :
...
include "
Zend/Loader.php
"
;
Zend_Loader::
registerAutoload();
// Chargement de la configuration
$config
=
new Zend_Config_Ini('
./application/config.ini
'
,
'
general
'
);
$registry
=
Zend_Registry::
getInstance();
$registry
->
set('
config
'
,
$config
);
// Mise en place du contrôleur
$frontController
=
Zend_Controller_Front::
getInstance();
...
Nous chargeons la section "general" de "application/config.ini" dans l'objet $config, puis nous assignons l'objet $config au registre afin de pouvoir le réutiliser ailleurs dans l'application.
Nous n'avons pas réellement besoin d'ajouter $config au registre dans ce tutoriel, mais c'est néanmoins une bonne pratique dans la mesure où, dans une application "réelle", vous aurez probablement davantage d'informations que les données d'accès à la base de données. De plus, soyez vigilant car le registre est un peu comme une globale et il cause des dépendances, si vous n'y prenez pas garde, entre des objets qui ne devraient pas dépendre les uns des autres.
V-C. Mise en place de Zend_Db_Table▲
Pour utiliser Zend_Db_Table, nous devons lui indiquer la configuration que nous venons juste de récupérer. Pour cela, nous devons créer une instance de Zend_Db et l'enregistrer avec la méthode statique Zend_Db_Table::setDefaultAdapter(). À nouveau, nous effectuons cette opération dans le bootstrapper (index.php) :
...
$registry
=
Zend_Registry::
getInstance();
$registry
->
set('
config
'
,
$config
);
// Mise en place de la BDD
$db
=
Zend_Db::
factory($config
->
db);
Zend_Db_Table::
setDefaultAdapter($db
);
// Mise en place du contrôleur
$frontController
=
Zend_Controller_Front::
getInstance();
...
Comme vous pouvez le voir, Zend_Db a une méthode statique factory() qui interprète les données de l'objet $config->db et qui instancie le bon adaptateur de bases de données à notre place.
V-D. Créer la table▲
J'utilise MySQL et la requête SQL est la suivante :
CREATE
TABLE
albums (
id int
(
11
)
NOT
NULL
auto_increment
,
artist varchar
(
100
)
NOT
NULL
,
title varchar
(
100
)
NOT
NULL
,
PRIMARY
KEY
(
id)
)
Exécutez cette requête avec un client comme MySQL ou le client standard en lignes de commandes.
V-E. Ajouter des enregistrements▲
Nous allons également ajouter quelques enregistrements de manière à pouvoir tester la fonctionnalité de récupération de la page d'accueil :
INSERT
INTO
albums (
artist, title)
VALUES
(
'Duffy'
, 'Rockferry'
)
,
(
'Van Morrison'
, 'Keep It Simple'
)
;;
V-F. Mise en place du Modèle▲
Zend_Db_Table est une classe abstraite, nous devons donc en hériter pour définir notre classe spécifique aux albums. Peu importe comment nous appelons notre classe mais il est logique de lui donner le nom de la table de la base de données. Ainsi, notre classe s'appelle "Albums" pour correspondre à notre table "album". Pour indiquer à Zend_Db_Table le nom de la table qu'il devra gérer, nous devons remplir la propriété protected $_name avec le nom de la table. De plus, Zend_Db_Table suppose que votre table a une clef primaire appelée "id" qui est auto incrementée par la base de données. Le nom de ce champ peut être précisé également, si besoin est.
Nous allons enregistrer la classe Albums dans le répertoire "application/models" :
<?php
class
Albums extends
Zend_Db_Table
{
protected
$_name
=
'albums'
;
}
Pas très compliqué, n'est-ce pas ? Heureusement pour nous, nos besoins sont très simples et Zend_Db_Table dispose déjà de toutes les fonctionnalités dont nous avons besoin. Cependant, si vous avez besoin de comportements spécifiques pour gérer votre Modèle, c'est dans cette classe que cela se passe. En général, les fonctions supplémentaires que vous pourrez vouloir implémenter sont des méthodes de type "recherche" (find) afin de permettre la sélection des informations dont vous avez besoin. Vous pouvez également indiquer à Zend_Db_Table les noms des tables liées, ainsi l'objet récupèrera également les données liées.
V-G. Afficher les albums▲
Maintenant que nous avons mis en place la configuration et les informations de la base de données, nous pouvons entrer dans le vif du sujet et afficher quelques albums. C'est dans l'IndexController que cela se passe.
...
function indexAction()
{
$this
->
view->
title =
"
Mes albums
"
;
$album
=
new Albums();
$this
->
view->
albums =
$album
->
fetchAll();
}
...
La méthode Zend_Db_Table::fetchAll() retourne un Zend_Db_Table_Rowset qui nous permet de parcourir les tuples retournés dans le script de gabarit :
<
p><a href
=
"
<?php
echo $this
->
url(array
('controller'
=>
'index'
,
'action'
=>
'ajouter'
));
?>
"
>
Ajouter un nouvel album<
/a
></p
>
<
table>
<
tr>
<
th>
Title<
/th
>
<
th>
Artist<
/th
>
<
th>
<
/th
>
<
/tr
>
<?php
foreach
($this
->
albums as
$album
) :
?>
<
tr>
<
td>
<?php
echo $this
->
escape($album
->
title);
?>
<
/td
>
<
td>
<?php
echo $this
->
escape($album
->
artist);
?>
<
/td
>
<
td>
<
a href
=
"
<?php
echo $this
->
url(array
('controller'
=>
'index'
,
'action'
=>
'modifier'
,
'id'
=>
$album
->
id));
?>
"
>
Modifier<
/a
>
<
a href
=
"
<?php
echo $this
->
url(array
('controller'
=>
'index'
,
'action'
=>
'supprimer'
,
'id'
=>
$album
->
id));
?>
"
>
Spprimer<
/a
>
<
/td
>
<
/tr
>
<?php
endforeach
;
?>
<
/table
>
La première chose que nous faisons est de créer un lien pour ajouter un nouvel album. L'assistant url() est fourni avec le framework et créé une adresse avec la bonne URL de base. Nous lui donnons simplement les valeurs en paramètre et il trouve le reste.
Nous créons ensuite un tableau HTML pour afficher le titre et l'artiste, et pour proposer des liens permettant de modifier ou de supprimer l'enregistrement. Une boucle standard foreach est utilisée pour parcourir les albums, et nous utilisons la syntaxe alternative car elle est plus simple que d'essayer de faire correspondre des parenthèses. L'assistant url() est là aussi utilisé pour créer les liens.
http://localhost/tutoriel-zf/ devrait maintenant afficher une jolie liste de (deux) albums, quelque chose comme :
V-H. Ajouter des albums▲
Nous allons maintenant programmer une fonctionnalité qui ajoute de nouveaux albums.
- Afficher un formulaire pour que l'utilisateur fournisse des détails ;
- Récupérer les informations et enregistrer en base.
Nous utiliserons Zend_Form pour y parvenir. Ce composant nous permet de créer un formulaire et d'en valider le contenu. Nous créons une nouvelle classe FormulaireAlbum qui étend Zend_Form pour définir notre formulaire :
<?php
class
FormulaireAlbum extends
Zend_Form
{
public
function
__construct
($options
=
null
)
{
parent
::
__construct
($options
);
$this
->
setName('album'
);
$id
=
new
Zend_Form_Element_Hidden('id'
);
$artist
=
new
Zend_Form_Element_Text('artist'
);
$artist
->
setLabel('Artist'
)
->
setRequired(true
)
->
addFilter('StripTags'
)
->
addFilter('StringTrim'
)
->
addValidator('NotEmpty'
);
$title
=
new
Zend_Form_Element_Text('title'
);
$title
->
setLabel('Title'
)
->
setRequired(true
)
->
addFilter('StripTags'
)
->
addFilter('StringTrim'
)
->
addValidator('NotEmpty'
);
$submit
=
new
Zend_Form_Element_Submit('submit'
);
$submit
->
setAttrib('id'
,
'submitbutton'
);
$this
->
addElements(array
($id
,
$artist
,
$title
,
$submit
));
}
}
Dans le constructeur, nous créons quatre éléments pour l'id, l'artiste, le titre et le bouton d'envoi. Chaque élément se voit affecté certains attributs, notamment le libellé à afficher. Pour les éléments textuels, nous ajoutons deux filtres StripTags et StringTrim pour supprimer le HTML et les espaces indésirables. Nous les mettons également comme "required" (nécessaires) et nous leur ajoutons un validateur NotEmpty (non vide) pour nous assurer que l'utilisateur entre véritablement l'information dont nous avons besoin.
Nous avons maintenant besoin d'afficher le formulaire et d'en récupérer les données. Cela se fait dans l'action ajouterAction() :
...
function ajouterAction()
{
$this
->
view->
title =
"
Ajouter un nouvel album
"
;
$form
=
new FormulaireAlbum();
$form
->
submit->
setLabel('
Ajouter
'
);
$this
->
view->
form =
$form
;
if ($this
->
_request->
isPost()) {
$formData
=
$this
->
_request->
getPost();
if ($form
->
isValid($formData
)) {
$albums
=
new Albums();
$row
=
$albums
->
createRow();
$row
->
artist =
$form
->
getValue('
artist
'
);
$row
->
title =
$form
->
getValue('
title
'
);
$row
->
save();
$this
->
_redirect('
/
'
);
}
else {
$form
->
populate($formData
);
}
}
}
}
...
Examinons ceci en plus de détails :
$form
=
new FormulaireAlbum();
$form
->
submit->
setLabel('
Ajouter
'
);
$this
->
view->
form =
$form
;
Nous créons notre FormulaireAlbum, nous donnons le libellé "Ajouter" au bouton d'envoi et ensuite nous assignons le formulaire à la Vue pour affichage.
if ($this
->
_request->
isPost()) {
$formData
=
$this
->
_request->
getPost();
if ($form
->
isValid($formData
)) {
Si la méthode isPost() de l'objet Request renvoie TRUE, alors le formulaire a été envoyé. Nous récupérons les données par la méthode getPost() et nous les vérifions avec isValid().
$albums
=
new Albums();
$row
=
$albums
->
createRow();
$row
->
artist =
$form
->
getValue('
artist
'
);
$row
->
title =
$form
->
getValue('
title
'
);
$row
->
save();
$this
->
_redirect('
/
'
);
Si le formulaire est valide, alors nous instancions la classe modèle Albums et nous utilisons createRow() pour récupérer un nouvel enregistrement et en remplir le titre et l'artiste avant d'enregistrer. Après avoir sauvegardé le nouvel enregistrement en base, nous revenons à la page d'accueil avec la méthode _redirect() du contrôleur.
}
else {
$form
->
populate($formData
);
}
Si les données du formulaire ne sont pas valides, alors nous le remplissons avec les données fournies et nous affichons à nouveau.
Nous avons maintenant besoin d'évaluer le formuaire dans le script ajouter.phtml :
<?php
echo $this
->
form ;
?>
Comme vous pouvez le voir, afficher un formulaire est très simple puisqu'il sait comment se prendre en charge.
V-I. Modifier un album▲
Modifier un album est presque identique à un ajout, le code se ressemble donc :
...
function modifierAction()
{
$this
->
view->
title =
"
Modifier un album
"
;
$form
=
new FormulaireAlbum();
$form
->
submit->
setLabel('
Enregistrer
'
);
$this
->
view->
form =
$form
;
if ($this
->
_request->
isPost()) {
$formData
=
$this
->
_request->
getPost();
if ($form
->
isValid($formData
)) {
$albums
=
new Albums();
$id
=
(int)
$form
->
getValue('
id
'
);
$row
=
$albums
->
fetchRow('
id=
'
.
$id
);
$row
->
artist =
$form
->
getValue('
artist
'
);
$row
->
title =
$form
->
getValue('
title
'
);
$row
->
save();
$this
->
_redirect('
/
'
);
}
else {
$form
->
populate($formData
);
}
}
else {
// L'id de l'album est attendu dans $params['id']
$id
=
(int)
$this
->
_request->
getParam('
id
'
,
0
);
if ($id
>
0
) {
$albums
=
new Albums();
$album
=
$albums
->
fetchRow('
id=
'
.
$id
);
$form
->
populate($album
->
toArray());
}
}
}
...
Voyons les différences avec un ajout. Premièrement, lorsque nous affichons le formulaire, nous avons besoin des données en base et de remplir le formulaire avec :
$id
=
(int)
$this
->
_request->
getParam('
id
'
,
0
);
if ($id
>
0
) {
$albums
=
new Albums();
$album
=
$albums
->
fetchRow('
id=
'
.
$id
);
$form
->
populate($album
->
toArray());
}
Ce n'est exécuté que si la requête est bien POST, et utilise le modèle pour récupérer l'enregistrement depuis la BDD. La classe Zend_Db_Table_Row a une méthode toArray() que nous pouvons utiliser directement pour remplir le formulaire.
Finalement, nous avons besoin de sauvegarder les données dans le bon enregistrement. On y parvient en récupérant l'enregistrement puis en le sauvegardant de nouveau :
$albums
=
new Albums();
$id
=
(int)
$form
->
getValue('
id
'
);
$row
=
$albums
->
fetchRow('
id=
'
.
$id
);
$row
->
artist =
$form
->
getValue('
artist
'
);
$row
->
title =
$form
->
getValue('
title
'
);
$row
->
save();
Le code de modifier.phtml est le même que pour ajouter.phtml :
<?php
echo $this
->
form ;
?>
Vous devriez désormais être en mesure d'ajouter et de modifier des enregistrements.
V-J. Supprimer un album▲
Pour terminer notre application, nous devons ajouter la suppression. Nous avons un lien "supprimer" à côté de chaque album de notre liste et l'approche naïve serait de supprimer un album lorsque le lien est utilisé. Ce serait une erreur. Souvenez-vous des spécifications : il ne faut pas faire d'action irréversible en utilisant GET mais plutôt POST.
Nous allons afficher un formulaire de confirmation à l'utilisateur et, s'il clique "oui", nous effectuerons la suppression. Puisque ce formulaire est trivial, nous allons simplement le faire en HTML dans le script de Vue.
Commençons par le code de l'action :
...
function supprimerAction()
{
$this
->
view->
title =
"
Supprimer un album
"
;
if ($this
->
_request->
isPost()) {
$id
=
(int)
$this
->
_request->
getPost('
id
'
);
$del
=
$this
->
_request->
getPost('
del
'
);
if ($del
==
'
Oui
'
&&
$id
>
0
) {
$albums
=
new Albums();
$where
=
'
id =
'
.
$id
;
$albums
->
delete($where
);
}
$this
->
_redirect('
/
'
);
}
else {
$id
=
(int)
$this
->
_request->
getParam('
id
'
);
if ($id
>
0
) {
$albums
=
new Albums();
$this
->
view->
album =
$albums
->
fetchRow('
id=
'
.
$id
);
}
}
}
...
De nouveau, nous utilisons cette même astuce (vérifier la méthode d'envoi) pour savoir si nous devons afficher le formulaire ou bien effectuer la suppression, à l'aide de la classe Albums. Tout comme l'ajout et la modification, la suppression est faite au moyen d'un appel à Zend_Db_Table::delete(). Si la requête n'est pas POST, alors nous recherchons un paramètre "id", nous récupérons le bon enregistrement depuis la BDD et nous assignons la Vue.
Le gabarit est un simple formulaire :
<?php
if
($this
->
album) :
?>
<
p>
Êtes-vous sûr de vouloir supprimer
'<?php
echo $this
->
escape($this
->
album->
title);
?>
' par
'<?php
echo $this
->
escape($this
->
album->
artist);
?>
' ?
<
/p
>
<
form action
=
"
<?php
echo $this
->
url(array
('action'
=>
'supprimer'
));
?>
"
method
=
"
post
"
>
<
div>
<
input type
=
"
hidden
"
name
=
"
id
"
value
=
"
<?php
echo $this
->
album->
id;
?>
"
/
>
<
input type
=
"
submit
"
name
=
"
del
"
value
=
"
Oui
"
/
>
<
input type
=
"
submit
"
name
=
"
del
"
value
=
"
Non
"
/
>
<
/div
>
<
/form
>
<?php
else
:
?>
<
p>
Impossible de trouver l'album.<
/p
>
<?php
endif
;
?>
Nous affichons dans le script un message de confirmation ainsi qu'un formulaire avec les boutons "Oui" et "Non". Dans l'action, nous avons comparé avec la valeur "Oui" au moment de la suppresion.
C'est tout. Vous avez maintenant une application pleinement fonctionnelle.