Réalisation d'un livre d'or avec PDO et MVC

Rechercher
Boutique en ligne, solution e-commerce, script PHP et PERL : RAYNETTE

Réalisation d'un livre d'or avec PDO et MVC

  • Par Emacs
  • 27 commentaires
  • 32724 lectures
  • RSS -  Atom

Les livres d'or font partie des applications dynamiques les plus populaires sur Internet. Ils permettent aux visiteurs d'un site de déposer une trace écrite de leur passage dans le but d'aider le webmaster à améliorer son site. Nous allons présenter dans ce tutoriel, une manière simple et efficace de développer un livre d'or fonctionnel et sécurisé. Bien entendu, celui-ci s'appuiera sur une base de données de type MySQL et son moteur de stockage MyISAM. Dans une optique de structuration de l'application, nous nous appuierons sur le modèle MVC.

Pré-requis

Avant de rentrer dans le vif du sujet, nous considérons que vous êtes déjà à l'aise avec

  • Les variables, constantes et tableaux
  • Les fonctions et procédures utilisateurs
  • Les bases de la programmation orientée objet
  • L'utilisation minimale (création d'une table, requête SQL SELECT, INSERT) des bases de données. Et notamment avec MySQL.

Vous devrez également vous assurer que votre serveur web fonctionne avec la version 5 de PHP et que l'extension PDO et le driver PDO MySQL sont installés.

Sachez également que ce tutoriel n'a pas l'unique but de vous présenter une solution technique fonctionnelle pour réaliser un livre d'or. Il a également l'ambition (voire même la prétention) de vous apporter des connaissances approfondies de développement PHP5 et de bonnes pratiques.

Présentation et structuration du projet

Un livre d'or est une application dynamique qui permet aux utilisateurs d'un site Internet de déposer des messages d'appréciation. C'est en quelque sorte le recueil des humeurs des visiteurs à l'encontre du site web. Vous l'aurez compris, l'intérêt pour le webmaster est double :

  • Faire remonter des idées et des suggestions pour améliorer son application
  • Promouvoir son site Internet avec des témoignages positifs d'utilisateurs satisfaits

L'application que nous allons développer devra répondre aux contraintes suivantes :

  • Poster de nouveaux messages
  • Contrôler les informations saisies dans le formulaire
  • Lister les messages
  • Paginer les résultats
  • Etre sécurisé contre les injections SQL et les failles XSS
  • S'appuyer sur le modèle MVC

Détaillons sommairement ce dernier point.

Qu'est-ce-que le modèle MVC ?

MVC est l'abbréviation de «Modèle, Vue, Contrôleur ». C'est une architecture et une méthode de conception (design pattern) pour le développement d'applications logicielles qui sépare le modèle de données, l'interface utilisateur et la logique de contrôle.

Dans une application web, la couche du modèle est représentée par la base de données, les librairies de fonctions, les classes, les fichiers, les structures de données... Ce sont en fait tous les composants qui permettent de stocker et de manipuler les données.

La vue est la couche logicielle qui assure l'affichage des données à l'utilisateur et l'interface Homme / Machine. Par exemple, le nombre 1 254,67 est ici issu de la vue. Il est en effet formaté pour un site français. Pour un site américain, nous l'aurions écrit 1 254.67. En revanche, dans les deux cas, cette valeur s'écrit 1254.67 dans le modèle (dans une variable de type flottant par exemple).

La dernière couche est le contrôleur. Il s'agit du moteur principal de l'application. Il fait la liaison entre le modèle et la vue. Le contrôleur a la tâche d'analyser la requête de l'utilisateur, d'appeller le modèle adéquat et de retourner la valeur de ce dernier à la vue qui prendra en charge son affichage.

Quels sont les avantages d'utiliser une telle architecture ? Le premier intérêt concerne avant tout la maintenance. En séparant le problème en 3 couches distinctes, l'application deviendra plus facile à maintenir où à faire évoluer. Le second avantage implique la vue. En effet, cet éclatement en 3 couches permet de remplacer la vue aisément sans avoir à toucher au modèle ou bien au contrôleur. Par exemple : changer le wedesign d'un site, proposer différents format d'affichage d'un contenu (XML, XHTML, PDF, image...).

Nous avons défini globalement à quoi correspond le modèle MVC. Arrêtons nous à présent sur la structuration générale de notre livre d'or. De quoi aurons-nous besoin ?

  • Une base de données
  • Un formulaire
  • Une page de listing des résultats

Nous visualisons vaguement deux couches du modèles MVC . Le modèle représenté par la base de données ainsi que le formulaire et la page de listing qui feront partie de la vue. Nous construirons le contrôleur petit à petit. Passons désormais à la création de la base de données.

Mise en place de la base de données

Choix du moteur de stockage

Nous utiliserons ici une table MySQL avec un moteur de stockage de type MyISAM. C'est le moteur par défaut de MySQL. Il est rapide, performant et supporte la recherche en texte intégral (fulltext). Néanmoins, ce moteur a le principal défaut de ne pas être conforme au principe ACID (Atomicité, Cohérence, Isolement, Durabilité) des bases de données. Il ne supporte pas les transactions contrairement au moteur de stockage InnoDB. Malgré tout, nous décidons d'utiliser MyISAM car les enjeux d'intégrité et de cohérence des données ne justifient pas l'emploi d'un moteur de stockage transactionnel. Un livre d'or n'est pas une application critique, contrairement à une application manipulant des données bancaires ou des salaires par exemple.

Structure de la table MySQL

La table MySQL de notre livre d'or accueillera les messages des utilisateurs. Notre formulaire sera composé de 3 champs (pseudo, message et note). Il faut donc au minimum que ces informations soient stockées dans la base de données. Seulement, ces informations ne suffisent pas. Il nous manque tout d'abord la clé primaire de la table. Nous opterons naturellement pour un identifiant unique auto-incrémenté pour chaque nouvel enregistrement. Enfin, nous ajouterons un champ recevant la date d'enregistrement des message afin de pouvoir les ordonner du plus récent au plus ancien à l'affichage.

Code SQL de création de la table
CREATE TABLE IF NOT EXISTS guestbook
(
id INT(7) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
pseudo VARCHAR(20) NOT NULL,
message TEXT NOT NULL,
note TINYINT(2) NOT NULL DEFAULT 5,
creation DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00'
);

Commencez par exécuter ce script SQL dans votre outil d'administration de votre base de données (PHPMyAdmin, MySQL Query Browser...). Cela aura pour effet immédiat de créer la table "guestbook" dans votre base de données. Nous pouvons à présent nous tourner vers le développement PHP.

Choix du connecteur de base de données

PHP 5 propose plusieurs connecteurs capables de se connecter à une base de données MySQL. Le premier et le plus courant reste le driver MySQL de base. C'est le plus simple à utiliser et donc le plus populaire. Il existe également l'extension MySQLi (MySQL improved) qui n'est autre qu'un driver amélioré de l'extension MySQL de base et qui a la particularité d'être orienté objet. Avec MySQLi, le développeur manipule directement un objet de type MySQLi alors qu'avec le driver MySQL de base il doit fonctionner par appels de procédures et fonctions.

Enfin le dernier connecteur est l'extension PDO qui signifie PHP Data Objects. Cette extension permet de se connecter à de multiples bases de données à condition que les pilotes pour chaque système d'information soit installé sur le serveur Web. PDO a été intégré avec PHP 5 et a le principal avantage de faire bénéficier le développeur de certaines fonctionnalités de la base de données en fonction de son pilote. Au même titre que MySQLi, PDO est une interface orientée objet, ce qui est beaucoup plus pratique à utiliser lorsque l'on est à l'aise avec de la programmation orientée objet.

C'est donc le connecteur que nous choisirons pour nous connecter sur la base de données et la manipuler. Notez que nous aurions pu également sélectionner le driver de base ou bien le connecteur MySQLi pour notre projet. Nous avons volontairement choisi PDO comme connecteur pour les raisons évoquées ci-après :

  • Introduire une solution technique purement PHP 5
  • Profiter des requêtes préparées pour sécuriser plus efficacement les écritures en base de données
  • Utiliser une solution orientée objet
  • Permettre de déployer plus facilement le livre d'or sur un autre système d'informations (Oracle, SQLite, PostgreSQL...) sans avoir à modifier excessivement le code PHP

Nous sommes donc prêts à mettre la main à la pâte et commencer à produire nos premières lignes de code PHP.

Construction du livre d'or

Détermination des fichiers à créer

Notre application se composera de 5 fichiers PHP dont un sera le controller ou programme principal qui contiendra toute l'application. Nous appellerons ce fichier guestbook.php. D'un point de vue du code PHP, nous verrons qu'il est relativement limité car il ne contiendra que des appels aux autres fichiers.

Nous développerons également un fichier de configuration (guestbook-config.inc.php) qui contiendra uniquement les informations de configuration de l'application. Il s'agit en fait simplement de constantes définissant les paramètres de connexion sur la base de données ainsi que le nombre de messages à afficher par page.

Puis nous créerons un fichier stockant des procédures / fonctions (ou helpers) utiles à l'application et potentiellement utilisables dans d'autres projets. Ce fichier se nommera guestbook-model.inc.php.

Ensuite nous développerons le coeur même de l'application, c'est-à-dire le contrôleur. Ce fichier PHP contiendra le code PHP qui vérifie et enregistre les données du formulaire en base de données; et récupère une liste de messages en fonction de la pagination. Nous appellerons ce fichier guestbook-controller.inc.php.

Enfin le dernier fichier contiendra le code générant la vue. Il s'agira majoritairement de code XHTML et de quelques appels à des fonctions élémentaires de PHP ainsi qu'à des fonctions utilisateurs du fichier guestbook-model.inc.php. Vous vous en doutez peut-être, ce fichier portera le nom guestbook-view.inc.php.

Fichier de configuration de l'application

Listing du fichier guestbook-config.inc.php
<?php
/**
* Constantes d'accès à la base de données
* et de configuration du livre d'or
**/
// Adresse du serveur de base de données
define('DB_SERVEUR', 'localhost');
// Login
define('DB_LOGIN','root');
// Mot de passe
define('DB_PASSWORD','');
// Nom de la base de données
define('DB_NOM','guestbook');
// Nom de la table du livre d'or
define('DB_GUESTBOOK_TABLE','guestbook');
// Driver correspondant à la BDD utilisée
define('DB_DSN','mysql:host='. DB_SERVEUR .';dbname='. DB_NOM);
// Nombre de messages à afficher par page
define('MAX_MESSAGES_PAR_PAGE', 15);
// URL du livre d'or
define('URL_GUESTBOOK', 'http://localhost/Guestbook/guestbook.php');

Librairie de fonctions

Listing du fichier guestbook-model.inc.php
<?php
/**
* Ce fichier contient toutes les fonctions
* utiles à l'application
**/
/**
* Fonction de connexion sur la BDD
*
* Cette fonction utilise l'extension PDO
* de PHP5
*
* @param string driver de connexion sur la BDD
* @param string login d'accès à la bdd
* @param string mot de passe d'accès à la base de données
* @param string encodage des données issues de la BDD
*
* @return PDO objet de connexion sur la BDD
**/
function PDOConnect($sDbDsn, $sDbLogin, $sDbPassword)
{
try
{
$oPDO = new PDO($sDbDsn, $sDbLogin, $sDbPassword);
}
catch (PDOException $e)
{
die('Une erreur interne est survenue');
}
$oPDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $oPDO;
}
/**
* Convertit la date au format américain en format français
*
* @param string la date au format US
* @return string la date au format FR
**/
function convertirDate($sDateUs)
{
return strftime('%d/%m/%Y à %H:%M', strtotime($sDateUs));
}
/**
* Fonction de pagination des résultats
*
* Retourne le code HTML des liens de pagination
*
* @param integer nombre de résultats total
* @param integer nombre de résultats par page
* @param integer numéro de la page courante
* @param integer nombre de pages avant la page courante
* @param integer nombre de pages après la page courante
* @param integer afficher le lien vers la première page (1=oui / 0=non)
* @param integer afficher le lien vers la dernière page (1=oui / 0=non)
* @return string code HTML des liens de pagination
**/
function paginer($nb_results, $nb_results_p_page, $numero_page_courante, $nb_avant, $nb_apres, $premiere, $derniere)
{
// Initialisation de la variable a retourner
$resultat = '';
// nombre total de pages
$nb_pages = ceil($nb_results / $nb_results_p_page);
// nombre de pages avant
$avant = $numero_page_courante > ($nb_avant + 1) ? $nb_avant : $numero_page_courante - 1;
// nombre de pages apres
$apres = $numero_page_courante <= $nb_pages - $nb_apres ? $nb_apres : $nb_pages - $numero_page_courante;
// premiere page
if ($premiere && $numero_page_courante - $avant > 1)
{
$resultat .= '<a href="'. htmlspecialchars($_SERVER['PHP_SELF']) .'?numeroPage=1" title="Première page">&laquo;&laquo;</a>&nbsp;';
}
// page precedente
if ($numero_page_courante > 1)
{
$resultat .= '<a href="'. htmlspecialchars($_SERVER['PHP_SELF']) .'?numeroPage='. ($numero_page_courante - 1) .'" title="Page précédente '. ($numero_page_courante - 1) . '">&laquo;</a>&nbsp;';
}
// affichage des numeros de page
for ($i = $numero_page_courante - $avant; $i <= $numero_page_courante + $apres; $i++)
{
// page courante
if ($i == $numero_page_courante)
{
$resultat .= '&nbsp;[<strong>' . $i . '</strong>]&nbsp;';
}
else
{
$resultat .= '&nbsp;[<a href="'. htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES) .'?numeroPage='. $i .'" title="Consulter la page '. $i . '">' . $i . '</a>]&nbsp;';
}
}
// page suivante
if($numero_page_courante < $nb_pages)
{
$resultat .= '<a href="'. htmlspecialchars($_SERVER['PHP_SELF']) .'?numeroPage='. ($numero_page_courante + 1) .'" title="Consulter la page '. ($numero_page_courante + 1) . ' !">&raquo;</a>&nbsp;';
}
// derniere page
if ($derniere && ($numero_page_courante + $apres) < $nb_pages)
{
$resultat .= '<a href="'. htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES) .'?numeroPage='. $nb_pages .'" title="Dernière page">&raquo;&raquo;</a>&nbsp;';
}
// On retourne le resultat
return $resultat;
}

Contrôleur de l'application

Listing du fichier guestbook-controller.inc.php
<?php
/**
* Contrôleur de l'application
*
* Ce fichier traite le formulaire
* Enregistre les informations en base de données
* Affiche une liste paginée de résultats
**/
/** ----
* Déclaration des variables globales
**/
// Objets de connexion et de manipulatin de la BDD
$oPDO = null;
$oPDOStatement = null;
// Tableau stockant les informations du livre d'or
$aInfosGuestbook = array();
// Tableau stockant les messages récupérés de la BDD
$aListeMessages = array();
// Tableau stockant les erreurs générées
$aListeErreurs = array();
// Nombre de messages enregistrés dans la BDD
$iNombreDeMessages = 0;
// Numéro de la page courante
$iNumeroDePageCourante = 1;
// Offset à partir duquel on récupère les messages dans la BDD
$iOffsetSelection = 0;
// Note moyenne du site
$fNoteMoyenne = 0;
/** ----
* Contrôle de la pagination
*/
if(!empty($_GET['numeroPage'])
&& is_numeric($_GET['numeroPage'])
&& ($_GET['numeroPage']>1))
{
$iNumeroDePageCourante = intval($_GET['numeroPage']);
$iOffsetSelection = ($iNumeroDePageCourante - 1) * MAX_MESSAGES_PAR_PAGE;
}
/** ----
* Initialisation de la connexion avec la base de données
**/
$oPDO = PDOConnect(DB_DSN, DB_LOGIN, DB_PASSWORD);
/** ----
* Contrôle du formulaire
*/
if (!empty($_POST))
{
// Nettoyage des chaines envoyées
$_POST['pseudo'] = isset($_POST['pseudo']) ? trim($_POST['pseudo']) : '';
$_POST['message'] = isset($_POST['message']) ? trim($_POST['message']) : '';
$_POST['note'] = isset($_POST['note']) ? intval($_POST['note']) : 5;
// Le pseudo est-il rempli ?
if (empty($_POST['pseudo']))
{
$aListeErreurs[] = 'Veuillez indiquer votre pseudo';
}
else
{
// Le pseudo est-il compris entre 2 et 20 caractères ?
if (strlen($_POST['pseudo']) < 2)
{
$aListeErreurs[] = 'Votre pseudo est trop court';
}
if (strlen($_POST['pseudo']) > 20)
{
$aListeErreurs[] = 'Votre peudo est trop long';
}
}
// Le message est-il rempli ?
if (empty($_POST['message']))
{
$aListeErreurs[] = 'Veuillez indiquer votre message';
}
// La note est-elle correcte ?
if (($_POST['note'] < 1) || ($_POST['note']>10))
{
$aListeErreurs[] = 'Veuillez choisir une note';
}
// Si aucune erreur n'a été générée
// On enregistre le message dans la BDD
if (0 === sizeof($aListeErreurs))
{
try
{
// Création d'une requête préparée
$oPDOStatement = $oPDO->prepare(
'INSERT INTO '. DB_GUESTBOOK_TABLE .' (pseudo, message, note, creation) VALUES(:pseudo, :message, :note, NOW())'
);
// Ajout de chaque paramètre à la requête
// Les paramètres sont automatiquement protégés par l'objet PDO
$oPDOStatement->bindParam(':pseudo', $_POST['pseudo'], PDO::PARAM_STR);
$oPDOStatement->bindParam(':message', $_POST['message'], PDO::PARAM_STR);
$oPDOStatement->bindParam(':note', $_POST['note'], PDO::PARAM_INT);
// Execution de la requête préparée
$oPDOStatement->execute();
// Redirection vers la même page pour vider le cache des données envoyées
header('Location: '. URL_GUESTBOOK);
}
catch (PDOException $oPdoException)
{
$aListeErreurs[] = 'Une erreur est survenue et a empêché l\'enregistrement de votre message';
}
}
}
/** ----
* Comptage du nombre de messages en base de données et calcule de la note moyenne
**/
$oPDOStatement = $oPDO->query('SELECT COUNT(1) AS nombreMessages, SUM(note) AS noteTotale FROM '. DB_GUESTBOOK_TABLE);
$oPDOStatement->setFetchMode(PDO::FETCH_ASSOC);
$aInfosGuestbook = $oPDOStatement->fetch();
$iNombreDeMessages = intval($aInfosGuestbook['nombreMessages']);
// Calcul de la note moyenne
if ($iNombreDeMessages > 0)
{
$fNoteMoyenne = round(intval($aInfosGuestbook['noteTotale']) / $iNombreDeMessages, 2);
}
$oPDOStatement = null;
/** ----
* Récupération des messages en fonction de la pagination
**/
if (sizeof($iNombreDeMessages)>0)
{
$oPDOStatement = $oPDO->prepare(
'SELECT pseudo, message, creation FROM '. DB_GUESTBOOK_TABLE .' ORDER BY creation DESC LIMIT :offset, '. MAX_MESSAGES_PAR_PAGE
);
$oPDOStatement->bindParam(':offset', $iOffsetSelection, PDO::PARAM_INT);
$oPDOStatement->execute();
// Récupération des résultats sélectionnés dans le tableau $aListeMessages
$aListeMessages = $oPDOStatement->fetchAll(PDO::FETCH_OBJ);
}
// Fermeture de la connexion SQL
$oPDOStatement = null;
$oPDO = null;

Affichages des messages et du formulaire

Listing du fichier guestbook-view.inc.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
<head>
<title>Mon livre d'or</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
</head>
<body>
<h1>Mon livre d'or</h1>
<h2>Poster un message</h2>
<?php /** Affichage des erreurs générées **/ ?>
<?php if (sizeof($aListeErreurs) > 0) : ?>
<ul>
<?php foreach ($aListeErreurs as $sErreur) : ?>
<li><?php echo htmlspecialchars($sErreur); ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<form action="<?php echo htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES); ?>" method="post">
<div>
<label for="pseudo">Pseudo :</label>
<input type="text" name="pseudo" id="pseudo" value="<?php if(!empty($_POST['pseudo'])) : echo htmlspecialchars($_POST['pseudo'], ENT_QUOTES); endif; ?>" />
</div>
<div>
<label for="message">Message :</label>
<textarea name="message" id="message" rows="10" cols="45"><?php if (!empty($_POST['message'])) : echo htmlspecialchars($_POST['message']); endif; ?></textarea>
</div>
<div>
<label for="note">Note :</label>
<select name="note" id="note">
<?php for ($i=1; $i<11; $i++) : ?>
<option value="<?php echo $i; ?>"<?php if (!empty($_POST['note']) && ($_POST['note'] == $i)) : echo ' selected="selected"'; endif; ?>><?php echo $i; ?></option>
<?php endfor; ?>
</select>
</div>
<div>
<input type="submit" name="envoyer" value="Soumettre" />
</div>
</form>
<h2>Liste des messages</h2>
<?php /** Affichage des messages **/ ?>
<?php if ($iNombreDeMessages > 0) : ?>
<ul>
<li><?php echo $iNombreDeMessages; ?> message<?php if ($iNombreDeMessages > 1) : ?>s<?php endif; ?></li>
<li>Note moyenne : <?php echo $fNoteMoyenne; ?> / 10
</ul>
<?php foreach ($aListeMessages as $oMessage) : ?>
<div>
<p>
Par <?php echo htmlspecialchars($oMessage->pseudo); ?>, le <?php echo convertirDate($oMessage->creation); ?>
</p>
<blockquote>
<p>
<?php echo nl2br(htmlspecialchars($oMessage->message)); ?>
</p>
</blockquote>
</div>
<hr/>
<?php endforeach; ?>
<?php /** Affichage de la pagination si nécessaire **/ ?>
<?php if ($iNombreDeMessages > MAX_MESSAGES_PAR_PAGE) : ?>
<div class="pagination">
<?php echo paginer($iNombreDeMessages, MAX_MESSAGES_PAR_PAGE, $iNumeroDePageCourante, 4, 4, 1, 1); ?>
</div>
<?php endif; ?>
<?php else : ?>
<p>
Aucun message enregistré
</p>
<?php endif; ?>
</body>
</html>

Programme principal

Listing du fichier guestbook.php
<?php
/**
* Programme principal
* Construit la page à partir de tous les fichiers
*/
require(dirname(__FILE__).'/guestbook-config.inc.php');
require(dirname(__FILE__).'/guestbook-model.inc.php');
require(dirname(__FILE__).'/guestbook-controller.inc.php');
require(dirname(__FILE__).'/guestbook-view.inc.php');

Utilisation du livre d'or

Pour tester le livre d'or, il vous suffit simplement de placer tous les fichiers dans le même répertoire et d'appeller le script principal dans votre navigateur.

Par exemple : http://monsite.com/guestbook/guestbook.php

Téléchargement et licence des fichiers sources

Les fichiers sources du programme sont disponibles en téléchargement libre. Vous pouvez les modifier et les commercialiser librement.

Améliorations possibles

Ce livre d'or reste malgré tout sommaire et il ne tient qu'à vous à présent de l'améliorer. Voici quelques idées pour agrémenter ce programme de nouvelles fonctionnalités :

  • Ajout d'un captcha visuel ou image pour empêcher le spamm par des robots
  • Ajout de nouveaux champs : email, site web...
  • Utilisation d'un éditeur WYSIWYG ou de tags BBCode pour personnaliser les messages
  • Développement du module d'administration des messages
  • Développement d'une classe GuestbookMessage qui permet de gérer de manière orientée objet chaque message du livre d'or
  • ...

Un petit mot sur le portage de l'application sur un autre SGBDR

Du fait de l'implémentation de l'objet PDO, cette application devient plus aisément portable sur un autre système de bases de données relationnelles. Si vous comptez utiliser ce livre d'or sur un système Oracle par exemple, il ne vous suffira qu'à changer la constante DB_DSN du fichier de configuration, et modifier les requêtes SQL du contrôleur en conséquence. Vous n'aurez nul besoin de toucher aux autres fichiers. C'est là tout l'avantage du modèle MVC comme nous l'avons expliqué plus haut.

Conclusion

Nous sommes arrivés au terme de ce tutoriel. Nous avons pu découvir progressivement comment réaliser une application Web PHP5 structurée et sécurisée s'appuyant sur le modèle MVC. Bien entendu ce n'est pas la seule et unique façon de procéder. Il en existe beaucoup d'autres mais cela vous donnera probablement de nouvelles idées pour vos prochains développements...



Les commentaires

1. Par Xireus le 25/12/2007 19:42

Excellent tutoriel qui se différencie des traditionnels "Créer un livre d'or". Bravo Emacs !

2. Par Emacs le 25/12/2007 19:46

Merci pour ton commentaire

3. Par gtraxx le 14/01/2008 21:22

Belle démonstration simple de PDO, autant dire que les tutos de pleuvent par pour celui-ci tellement il est jeune

4. Par Emacs le 14/01/2008 22:22

Merci Gtraxx

J'essaie effectivement de présenter des méthodes plus modernes et élégantes que les traditionnels mysql_* sachant que l'on est à PHP5.

5. Par x@v le 06/02/2008 01:01

Merci pour ce cour, enfin du mvc en php j'allait dire. Mais un contrôleur ne devrai pas s'occuper de persistence et seulement rediriger. Sa me donne approche original.
(ne pas tapez)

6. Par x@v le 06/02/2008 01:04

effectivement à regarder de plus prêt ce n'est pas du mvc, même pas 1

7. Par x@v le 06/02/2008 01:31

en faite j'explique:
le mvc1 == modèle vue controleur
en J2EE(et avec toute techno récente)
persistence(stockage en bdd / affichage(xhtml et traitement php) / controle(redirection donc header() en php)
exclusivement ou sinon on n'est dans du mvc1 ni 1 mvc2

8. Par Emacs le 06/02/2008 09:09

Oui ce n'est pas complètement du MVC. C'est une première approche pour séparer le modèle (ici représenté par la BDD), la logique métier et la vue. Mais ce tutoriel n'a pour but que de présenter une programmation PHP plus structurée, plus maintenable et plus évolutive. Après si l'on souhaite vraiment entrer dans un motif pur MVC, on peut s'orienter du côté des frameworks MVC comme Symfony qui gère ça très bien.

++

9. Par rocawear le 21/02/2008 02:19

Très bon tutoriel, cela nous montre les débuts en utilisation de la poo

bravo !

10. Par gtraxx le 21/05/2008 00:25

Ahh quand d'autre exemple avec PDO emacs ?
Perso j'ai développer un système de newsletter ajax / PDO .
Par contre créer des sous categorie complexe pour album photos c hard avec PDO lol

11. Par Emacs le 21/05/2008 09:04

@gtraxx : il y'en aura d'autres quand j'aurais un peu plus de temps pour écrire. En ce moment, je suis occupé par pas mal de boulot professionnel, personnel et universitaire...

Par contre je ne comprends pas tes histoires de sous-catégories. En quoi est-ce lié à PDO ? Tu tas regardé du côté de la représentation intervallaire ?

++

12. Par Dam le 30/07/2008 15:58

Slt Emacs,

Ton tuto est vraiment sympa. Bien expliquer avec la clareté que l'on te connait . A +

13. Par Emacs le 30/07/2008 20:06

Merci Dam

14. Par herve le 19/08/2008 10:03

Bonjour,

Je reviens à la programmation (à près plusieurs années d'absence) avec les outils opensource. Dans une autre vie, j'étais développeur en asp.net. En tant que membre d'assos, j'utilise maintenant un CMS (joomla) avec son propre framework (pour l'instant je ne suis pas rentré dedans). J'ai donc besoin d'exemples simples, mais fonctionnels et avec les derniers orientations (MVC, classes, ...) pour ne pas réinventer la roue. Le tout s'intégrant simplement avec le CMS.

Tout cette intro pour te remercier pour cette bonne introduction à une prog en MVC (et au contenu du site en général). Voilà plusieurs jours que je cherchai sans succès sur l'Internet. Il y a beaucoup d'exemples mais la plupart sont obsolètes ou mal codés. C'est pour cela, que j'avais acheté un livre qui vient de sortir. Quelle déception, pour un livre sorti en 2008 de voir apparaitre les anciennes méthodes de cnx à mysql (( qui vont être progressivement déclassés le driver MySQL de base dans les prochaines versions de php ...


J'ai une question principale qui tourne autour de l'architecture et du contenu respectif des fichiers. J'essaie actuellement d'intégrer un framework de validation (http://formol.oeilduwake.com/) sans succès. Du coup je ne comprends pas l'utilité du
fichier guestbook.php.
- C'est bien dans le fichier controleur que l'on utilise les sessions ?
- Ne peut-on avoir une architecture comme celle-ci?

1°) Supprimer le fichier guestbook.php.
2°) Appellé guestbook-controller.inc.php renommé en guestbook-controller.php
3°) Contenu résumé de ce fichier :
r******************
...
require_once ('guestbook-model.inc.php';
// Interception des exceptions
try {
// Démarrage de la session
session_start();
// Chargement du formulaire en session
if ($form = Form::load($_SESSION, $_POST)) {
// Le formulaire est-il validé ?
if ( $form->validate() ) {
/* Traitements sur les données */

// Insertion des données du formulaire via classe

// Libération du formulaire
Form::free($_SESSION);
exit('Le formulaire a été validé.';
}
} else {
// **** Création du formulaire ****
require_once('guestbook-view.inc.php';
// Enregistrement du formulaire en session
Form:ave($_SESSION);
}


} catch(FormException $e) {
echo $e->display();
}
...
******************

... et quelques petites questions pour améliorer la gestion des données (via formulaire)
- quelques liens et pistes pour rendre plus robuste ce code
- a partir de quand, décides t-on de passer son code dans une classe ? (par ex. dans ce cas si je comprends bien, il faudrait insérer http://www.apprendre-php.com/tutoriels/tutoriel-45-singleton-instance-unique-d-une-classe.html pour la cnx

Merci et bon courage pour le livre.

15. Par Emacs le 19/08/2008 20:37

Bonsoir Hervé,

Tout d'abord merci pour vos compliments au sujet du tutoriel et du site en général. Je vais prendre le temps de répondre à vos quelques questions. Sachez tout d'abord que ce tutoriel est une approche au pattern MVC. Il s'agit en réalité du MVC 1. Une véritable implémentation du motif MVC devrait être réalisé avec des classes de modèles, de vues et de contrôleurs.

Comme vous le faites bien remarquer, le script guestbook.php n'est pas forcément utile et ce serait plutôt à la charge du script guestbook-controller.inc.php d'inclure le model, de manipuler les données du modèle et d'inclure enfin la vue pour lui transmettre les données du modèle.

Concernant les sessions, c'est effectivement dans le contrôleur qu'il faut les manipuler. En effet, une session peut être vu comme un modèle que le contrôleur va manipuler (lecture et écriture d'information).

Enfin, concernant la création de classe, la réponse est ça dépend. Les classes sont utiles pour structurer et factoriser le code. Le but d'une classe c'est d'être suffisamment générique et découplé du reste de l'application. Avec une classe, on crée ensuite des objets possédant des attributs et des méthodes à un instant T. Chaque objet est issu de la classe et est unique. Ainsi, si l'on peut percevoir une partie du code comme un objet, alors il peut être intéressant de l'encapsuler dans une classe. Ainsi un formulaire, une session, un contrôleur, une vue html... peuvent tous être perçus comme des objets et donc donner lieu à l'écriture d'une classe.

A bientôt,

Hugo.

16. Par aoeul le 03/12/2008 14:38

bravo pour le tuto!

17. Par x@v le 25/12/2008 22:57

@ gtraxx
Pour se logguer à une bdd avec pdo, cette classe fait tout pour tout le monde:
http://www.phpclasses.org/browse/file/23687.html

18. Par max8371 le 28/01/2009 22:25

Bonsoir Emacs,
Tout d'abord bravo, il est plaisant de naviguer sur votre site, élégant, un discours agréable, des tutos clairs pour le débutant que je suis.
Je viens de lire et d'appliquer, pour un collège, celui sur le livre d'or. Mais j'ai dû diriger mon lien vers guestbook-view.inc.php pour que la page s'affiche (et non guestbook.php comme vous l'indiquer). De plus deux erreurs sont relevées -- Notice: Undefined variable: aListeErreurs in C:\wamp\www\web-epontots\guestbook\guestbook-view.inc.php on line 13 -- et line 46 --. (j'utilise Wampserver).
Bien sûr aucun message n'est enregistré.
Pourriez-vous m'indiquer mes erreurs ?
Je vous remercie.
A bientôt
Max

19. Par Emacs le 31/01/2009 16:25

Bonjour Mac8371 et merci pour vos commentaires

Je vais regarder ce qui ne va pas avec le tutoriel et le mettre à jour.

Cordialement,

Hugo.

20. Par Emacs le 31/01/2009 16:59

Je viens de mettre à jour tout le tutoriel ainsi que les fichiers sources. Tout fonctionne parfaitement

21. Par manspace le 10/03/2009 12:09

Bravo pour la clarté de vos tutos, continuez à nous régaler.

22. Par Simo le 11/03/2009 01:27

Vraiment c'est un excelent tutoriel, bon boulot Emacs

23. Par Billy le 23/05/2009 17:11

Je reçois l'erreur suivante: Parse error: syntax error, unexpected '{' in /mnt/139/sdb/d/7/bezonnes/feria/guestbook-model.inc.php on line 24

Que faire ?

24. Par Gaël Bernard le 29/05/2009 14:42

Merci beaucoup pour cet excellent tutoriel. Je débute en POO et celui-ci m'a grandement aidé !

25. Par lerunner33 le 20/06/2009 16:16

bonjour,
J'essaie désespérément de faire un livre d'or.
J'ai téléchargé l'exemple et j'ai les mêmes erreurs que le post 18.
deux variables non définies ?
Je suis débutant et j'ai du mal à faire le lien. Idem si je lance guestbook.php j'ai"erreur interne".
J'ai du refermer les fichiers téléchargés (pas de ?>.
Pour le reste super site ou j'espère apprendre beaucoup!

merci de votre indulgence et de votre aide!

26. Par abdel le 18/08/2009 21:20

rarent des tutos de ce genre.Merci emacs

27. Par Alphonse le 31/08/2009 15:44

Salut !
Super intéressant comme tutoriel mais je ne comprends pas très bien pourquoi tu utilises des requêtes préparées dans ton exemple ?
Les requêtes sont très basiques et ne justifient pas l'utilisation des requêtes préparées...
J'imagine que tu l'as fait à cause de la documentation officielle de PHP qui recommande de passer par des requêtes préparées juste afin de ne pas se préoccuper des injections SQL ?
Toutefois, je ne pense pas que le temps d'exécution soit plus lent ou plus rapide dans un cas ou dans l'autre mais c'est juste l'utilisation systématique des requêtes préparées qui me surprend le plus...
Bonne continuation en tout cas car c'est un très chouette site !!