Anda di halaman 1dari 33

v ol ue r v e r s une a r c hi t e c t ur e MVC e n

PHP
Table des matires
I. Prsentation du contexte d'exemple
o I-A. Base de donnes
o I-B. Page principale
o I-C. Affichage obtenu
o I-D. Critique de l'architecture actuelle
II. Mise en place d'une architecture MVC simple
o II-A. Amlioration de l'exemple
II-A-1. Isolation de l'affichage
II-A-2. Isolation de l'accs aux donnes
II-A-3. Bilan provisoire
o II-B. Le modle MVC
II-B-1. Prsentation
II-B-2. Rles des composants
II-B-3. Interactions entre les composants
II-B-4. Avantages et inconvnients
II-B-5. Diffrences avec un modle en couches
o II-C. Amliorations supplmentaires
II-C-1. Factorisation des lments d'affichage communs
II-C-2. Factorisation de la connexion la base
II-C-3. Gestion des erreurs
o II-D. Bilan : architecture obtenue
o II-E. Application : affichage des dtails d'un billet
II-E-1. Description du nouveau besoin
II-E-2. Prise en compte du nouveau besoin
III. Passage une architecture MVC oriente objet
o III-A. Amlioration de l'architecture MVC
III-A-1. Mise en uvre d'un contrleur frontal (front controller)
III-A-2. Rorganisation des fichiers sources
III-A-3. Bilan provisoire
o III-B. Aperu du modle objet de PHP
III-B-1. Exemple de hirarchie de classes
III-B-2. Caractristiques du modle objet de PHP
III-B-3. Spcificits du modle objet de PHP
o III-C. Mise en uvre du modle objet de PHP
III-C-1. Passage un Modle orient objet
III-C-2. Passage un Contrleur orient objet
o III-D. Bilan : architecture obtenue
o III-E. Application : ajout d'un commentaire
III-E-1. Description du nouveau besoin
III-E-2. Prise en compte du nouveau besoin
IV. Conclusion et perspectives
o IV-A. Amliorations possibles
o IV-B. Pour aller encore plus loin : les frameworks PHP
V. Remerciements
L'objectif de cet article est de dcouvrir comment amliorer l'architecture d'un site Web en passant d'une
organisation classique (monopage) une organisation respectant le modle MVC.
Il s'agit d'une adaptation d'un cours donn aux tudiants de seconde anne de BTS SIO (Services
Informatiques aux Organisations) au lyce La Martinire Duchre de Lyon.
Remarque : cet article s'inspire en partie de la page Web Symfony2 versus flat PHP.
20 commentaires
Article lu 12447 fois.
L'auteur
Baptiste Pesquet
L'article
Publi le 27 mars 2013 - Mis jour le 27 mars 2013
Version PDF Version hors-ligne
ePub, Azw et Mobi
Liens sociaux

I. Prsentation du contexte d'exemple
Nous mettrons en uvre les principes prsents dans cet article sur un exemple simple : une page Web PHP de type
blog interagissant avec une base de donnes relationnelle.
Vous trouverez les fichiers sources du contexte initial
l'adresse https://github.com/bpesquet/MonBlog/tree/sans-mvc.
I-A. Base de donnes
La base de donnes utilise est trs simple. Elle se compose de deux tables, l'une stockant les billets (articles) du
blog et l'autre les commentaires associs aux articles.

Cette base de donnes contient quelques donnes de test, insres par le script SQL ci-dessous.

Slectionnez
INSERT INTO T_BILLET(BIL_DATE, BIL_TITRE, BIL_CONTENU) VALUES
(NOW(), 'Premier billet', 'Bonjour monde ! Ceci est le premier billet sur mon blog.');
INSERT INTO T_BILLET(BIL_DATE, BIL_TITRE, BIL_CONTENU) VALUES
(NOW(), 'Au travail', 'Il faut enrichir ce blog ds maintenant.');

INSERT INTO T_COMMENTAIRE(COM_DATE, COM_AUTEUR, COM_CONTENU, BIL_ID) VALUES
(NOW(), 'A. Nonyme', 'Bravo pour ce dbut', 1);
INSERT INTO T_COMMENTAIRE(COM_DATE, COM_AUTEUR, COM_CONTENU, BIL_ID) VALUES
(NOW(), 'Moi', 'Merci ! Je vais continuer sur ma lance', 1);
I-B. Page principale
Voici le code source PHP de la page principale index.php de notre blog.

Slectionnez
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="style.css" />
<title>Mon Blog - Sans MVC</title>
</head>
<body>
<div id="global">
<header>
<h1 id="titreBlog"><a href="index.php">Mon Blog</a></h1>
<p>Je vous souhaite la bienvenue sur ce modeste blog.</p>
</header>
<nav>
<section>
<h1>Billets</h1>
<ul>
<li><a href="todo">Billets rcents</a></li>
<li><a href="todo">Tous les billets</a></li>
</ul>
</section>
<section>
<h1>Administration</h1>
<ul>
<li><a href="todo">crire un billet</a></li>
</ul>
</section>
</nav>
<div id="contenu">
<?php
$bdd = new PDO(
'mysql:host=localhost;dbname=monblog;charset=utf8',
'root', '');
$requeteBillets = "select * from T_BILLET order by BIL_ID desc";
$billets = $bdd->query($requeteBillets);
foreach ($billets as $billet):
?>
<article>
<header>
<h1 class="titreBillet">
<?= $billet['BIL_TITRE'] ?>
</h1>
<time><?= $billet['BIL_DATE'] ?></time>
</header>
<p><?= $billet['BIL_CONTENU'] ?></p>
</article>
<hr />
<?php endforeach; ?>
</div> <!-- #contenu -->
<footer id="piedBlog">
Blog ralis avec PHP, HTML5 et CSS.
</footer>
</div> <!-- #global -->
</body>
</html>
On peut faire les remarques suivantes.
Cette page est crite en HTML5 et utilise certaines nouvelles balises, comme <article>.
Elle prsente un menu de navigation (balise <nav>) prsent uniquement pour des raisons esthtiques (les liens ne
fonctionnent pas).
Elle emploie l'affichage abrg <?= ... ?> plutt que <?php echo ... ?>, ainsi que la syntaxe
alternative pour la boucle foreach.
Elle utilise l'extension PDO de PHP afin d'interagir avec la base de donnes.
Pour le reste, il s'agit d'un exemple assez classique d'utilisation de PHP pour construire une page dynamique
affiche par le navigateur client.
I-C. Affichage obtenu
Le rsultat obtenu depuis un navigateur client est le suivant.

Ce rsultat est d l'application d'une feuille de style CSS (fichier style.css) afin d'amliorer le rendu HTML.
Pour information, voici le code source associ.

Slectionnez
/* Pour pouvoir utiliser une hauteur (height) ou une hauteur minimale
(min-height) sur un bloc, il faut que son parent direct ait lui-mme une
hauteur dtermine (donc toute valeur de height sauf "auto": hauteur en
pixels, em, autres units...).
Si la hauteur du parent est en pourcentage, elle se rfre alors la
hauteur du grand-pre , et ainsi de suite.
Ainsi, pour pouvoir utiliser un "min-height: 100 %" sur div#global, il nous
faut:
- un parent (body) en "height: 100 %";
- le parent de body galement en "height: 100 %". */
html, body {
height: 100%;
}

body {
color: #bfbfbf;
background: black;
font-family: 'Futura-Medium', 'Futura', 'Trebuchet MS', sans-serif;
}

a {
color: #bfbfbf;
}

a:hover, a:focus {
color: crimson;
}

nav {
float: right;
margin-left: 20px;
}

h1 {
color: white;
}

#global {
min-height: 100%; /* Voir commentaire sur html et body en haut de la feuille de style */
background: #333534;
width: 60%;
margin: auto; /* Permet de centrer la div */
text-align: justify;
padding: 5px 20px;
}

#contenu {
margin-bottom : 30px;
overflow: hidden; /* vite au bloc central de glisser sous le menu latral
(alternative la dfinition de marges pour ces blocs) */
}

#titreBlog, #piedBlog {
text-align: center;
}

.titreBillet {
margin-bottom : 0px;
}

.titreBillet a, #titreBlog a {
color: white;
text-decoration: none; /* Dsactive le soulignement des liens */
}


}
I-D. Critique de l'architecture actuelle
Les principaux dfauts de cette page Web sont les suivants :
elle mlange balises HTML et code PHP ;
sa structure est monobloc, ce qui rend sa rutilisation difficile.
On sait que tout logiciel doit grer plusieurs problmatiques :
interactions avec l'extrieur, en particulier l'utilisateur : saisie et contrle de donnes, affichage.
C'est la problmatique de prsentation ;
oprations sur les donnes (calculs) en rapport avec les rgles mtier ( business logic ). C'est
la problmatique des traitements ;
accs et stockage des informations qu'il manipule, notamment entre deux utilisations. C'est la
problmatique des donnes.
La page Web actuelle mlange code de prsentation (les balises HTML) et accs aux donnes (requte SQL).
Ceci est totalement contraire au principe deresponsabilit unique, le principal principe de conception
logicielle.
L'architecture actuelle montre ses limites ds que le contexte se complexifie. Le volume de code des pages
PHP explose et la maintenance devient dlicate. Il faut faire mieux.
II. Mise en place d'une architecture MVC simple
II-A. Amlioration de l'exemple
II-A-1. Isolation de l'affichage
Une premire amlioration consiste sparer le code d'accs aux donnes du code de prsentation au sein du
fichier index.php.

Slectionnez
<?php
$bdd = new PDO('mysql:host=localhost;dbname=monblog;charset=utf8',
'root', '');
$requeteBillets = "select * from T_BILLET order by BIL_ID desc";
$billets = $bdd->query($requeteBillets);
?>

<!doctype html>
<head>
...
<div id="contenu">
<?php
foreach ($billets as $billet):
?>
<article>
<header>
<h1 class="titreBillet">
<?= $billet['BIL_TITRE'] ?>
</h1>
<time><?= $billet['BIL_DATE'] ?></time>
</header>
<p><?= $billet['BIL_CONTENU'] ?></p>
</article>
<hr />
<?php endforeach; ?>
</div> <!-- #contenu -->
...
Le code est devenu plus lisible, mais les problmatiques de prsentation et d'accs aux donnes sont toujours
gres au sein d'un mme fichier PHP. En plus de limiter la modularit, ceci est contraire aux bonnes
pratiques de dveloppement PHP (norme PSR-1).
On peut aller plus loin dans le dcouplage en regroupant le code d'affichage prcdent dans un fichier ddi
nomm listeBillets.php.

Slectionnez
<!doctype html>
<html lang="fr">
<head>
...
</head>
<body>
...
<div id="contenu">
<?php
foreach ($billets as $billet):
?>
<article>
...
</article>
<hr />
<?php endforeach; ?>
</div> <!-- #contenu -->
...
</body>
</html>
La page principale index.php devient alors :

Slectionnez
<?php
// Accs aux donnes
$bdd = new PDO('mysql:host=localhost;dbname=monblog;charset=utf8',
'root', '');
$requeteBillets = "select * from T_BILLET order by BIL_ID desc";
$stmtBillets = $bdd->query($requeteBillets);

// Affichage
require 'listeBillets.php';
Rappel : la fonction PHP require fonctionne de manire similaire include : elle inclut et interprte le fichier
spcifi. En cas d'chec, include ne produit qu'un avertissement alors que require stoppe le script.
La balise de fin de code PHP ?> est volontairement omise la fin du fichier index.php.
C'est une bonne pratique pour les fichiers qui ne contiennent que du PHP. Elle
permet d'viter des problmes lors d'inclusions de fichiers.
II-A-2. Isolation de l'accs aux donnes
Nous avons amlior l'architecture de notre page, mais nous pourrions gagner en modularit en isolant le
code d'accs aux donnes dans un fichier PHP ddi. Appelons ce fichier modele.php.

Slectionnez
<?php

function getBillets()
{
$bdd = new PDO('mysql:host=localhost;dbname=monblog;charset=utf8',
'root', '');
$requeteBillets = "select * from T_BILLET order by BIL_ID desc";
$stmtBillets = $bdd->query($requeteBillets);
return $stmtBillets;
}
Dans ce fichier, nous avons dplac la rcupration des billets du blog l'intrieur
d'une fonction nomme getBillets.
Le code d'affichage (fichier listeBillets.php) ne change pas. Le lien entre accs aux donnes et prsentation
est effectu par le fichier principalindex.php. Ce fichier est maintenant trs simple.

Slectionnez
<?php

require 'modele.php';

$billets = getBillets();

require 'listeBillets.php';
II-A-3. Bilan provisoire
Outre la feuille de style CSS, notre page Web est maintenant constitue de trois fichiers :
modele.php (PHP uniquement) pour l'accs aux donnes ;
listeBillets.php (PHP et HTML) pour l'affichage des billets du blog ;
index.php (PHP uniquement) pour faire le lien entre les deux pages prcdentes.
Cette nouvelle structure est plus complexe, mais les responsabilits de chaque partie sont maintenant claires.
En faisant ce travail de refactoring, nous avons rendu notre exemple conforme un modle d'architecture
trs employ sur le Web : le modle MVC.
II-B. Le modle MVC
II-B-1. Prsentation
Le modle MVC dcrit une manire d'architecturer une application informatique en la dcomposant en trois
sous-parties :
la partie Modle ;
la partie Vue ;
la partie Contrleur.
Ce modle a t imagin la fin des annes 1970 pour le langage Smalltalk afin de bien sparer le code de
l'interface graphique de la logique applicative. Il est utilis dans de trs nombreux langages : bibliothques
Swing et Model 2 (JSP) de Java, frameworks PHP, ASP.NET MVC, etc.
II-B-2. Rles des composants
La partie Modle d'une architecture MVC encapsule la logique mtier ( business logic ) ainsi que l'accs aux
donnes. Il peut s'agir d'un ensemble de fonctions (Modle procdural) ou de classes (Modle orient objet).
La partie Vue s'occupe des interactions avec l'utilisateur : prsentation des informations, saisie et validation
des donnes.
La partie Contrleur gre la dynamique de l'application. Elle fait le lien entre l'utilisateur et le reste de
l'application.
II-B-3. Interactions entre les composants
Le diagramme ci-dessous rsume les relations entre les composants d'une architecture MVC.
Extrait de la documentation du
framework Symfony
La demande de l'utilisateur (par exemple une requte HTTP) est reue et interprte par
le Contrleur.
Celui-ci utilise les services du Modle afin de prparer les donnes afficher.
Ensuite, le Contrleur fournit ces donnes la Vue, qui les prsente l'utilisateur (par
exemple sous la forme d'une page HTML).
Une application construite sur le principe du MVC se compose toujours de trois parties distinctes. Cependant, il
est frquent que chaque partie soit elle-mme dcompose en plusieurs lments. On peut ainsi trouver
plusieurs modles, plusieurs vues ou plusieurs contrleurs l'intrieur d'une application MVC.
II-B-4. Avantages et inconvnients
Le modle MVC offre une sparation claire des responsabilits au sein d'une application, en conformit avec
les principes de conception dj tudis : responsabilit unique, couplage faible et cohsion forte. Le prix
payer est une augmentation de la complexit de l'architecture.
Dans le cas d'une application Web, l'application du modle MVC permet aux pages HTML (qui constituent
la Vue) de contenir le moins possible de code serveur, tant donn que le scripting est regroup dans les
deux autres parties de l'application.
II-B-5. Diffrences avec un modle en couches
Attention ne pas employer le terme de couche propos du modle MVC. Un modle en couches se
caractrise par l'interdiction pour une couche non transversale de communiquer au-del des couches
adjacentes. De plus, le nombre de couches n'est pas impos : il est fonction de la complexit du contexte.

II-C. Amliorations supplmentaires
Mme si notre architecture a dj t nettement amliore, il est possible d'aller encore plus loin.
II-C-1. Factorisation des lments d'affichage communs
Un site Web se rduit rarement une seule page. Il serait donc souhaitable de dfinir un seul endroit les
lments communs des pages HTML affiches l'utilisateur (les vues).
Une premire solution consiste inclure les lments communs avec des fonctions PHP include. Il existe une
autre technique, plus souple, que nous allons mettre en uvre : l'utilisation d'un modle de page (gabarit),
appel template en anglais. Ce modle contiendra tous les lments communs et permettra d'ajouter les
lments spcifiques chaque vue. On peut crire ce template de la manire suivante (fichier gabarit.php).

Slectionnez
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="style.css" />
<title><?= $titre ?></title> <!-- lment spcifique -->
</head>
<body>
<div id="global">
<header>
<h1 id="titreBlog">Mon Blog</h1>
<p>Je vous souhaite la bienvenue sur ce modeste blog.</p>
</header>
<nav>
<section>
<h1>Billets</h1>
<ul>
<li><a href="todo">Billets rcents</a></li>
<li><a href="todo">Tous les billets</a></li>
</ul>
</section>
<section>
<h1>Administration</h1>
<ul>
<li><a href="todo">crire un billet</a></li>
</ul>
</section>
</nav>
<div id="contenu">
<?= $contenu ?> <!-- lment spcifique -->
</div> <!-- #contenu -->
<footer id="piedBlog">
Blog ralis avec PHP, HTML5 et CSS.
</footer>
</div> <!-- #global -->
</body>
</html>
Au moment de l'affichage d'une vue HTML, il suffit de dfinir les valeurs des lments spcifiques, puis de
dclencher le rendu de notre gabarit. Pour cela, on utilise des fonctions PHP qui manipulent le flux de sortie de
la page. Voici notre page listeBillets.php rcrite :

Slectionnez
<?php $titre = 'Mon Blog ? MVC simple' ?>

<?php ob_start() ?>
<?php foreach ($billets as $billet):
?>
<article>
<header>
<h1 class="titreBillet"><?= $billet['BIL_TITRE'] ?></h1>
<time><?= $billet['BIL_DATE'] ?></time>
</header>
<p><?= $billet['BIL_CONTENU'] ?></p>
</article>
<hr />
<?php endforeach; ?>
<?php $contenu = ob_get_clean() ?>

<?php require 'gabarit.php' ?>
Ce code mrite quelques explications :
la premire ligne dfinit la valeur de l'lment spcifique $titre ;
la deuxime ligne utilise la fonction PHP ob_start. Son rle est de dclencher la mise en
tampon du flux HTML de sortie : au lieu d'tre envoy au navigateur, ce flux est stock en
mmoire ;
la suite du code (boucle foreach) gnre les balises HTML <article> associes aux billets
du blog. Le flux HTML cr est mis en tampon ;
une fois la boucle termine, la fonction PHP ob_get_clean permet de rcuprer dans une
variable le flux de sortie mis en tampon depuis l'appel ob_start. La variable se nomme
ici $contenu, ce qui permet de dfinir l'lment spcifique associ ;
enfin, on dclenche le rendu du gabarit. Lors du rendu, les valeurs des lments
spcifiques $titre et $contenu seront insres dans le rsultat HTML envoy au
navigateur.
L'affichage utilisateur est strictement le mme qu'avant l'utilisation d'un gabarit. Cependant, nous disposons
maintenant d'une solution souple pour crer plusieurs vues tout en centralisant la dfinition de leurs lments
communs.
II-C-2. Factorisation de la connexion la base
On peut amliorer l'architecture de la partie Modle en isolant le code qui tablit la connexion la base de
donnes sous la forme d'une fonctiongetBdd ajoute dans le fichier modele.php. Cela vitera de dupliquer le
code de connexion lorsque nous ajouterons d'autres fonctions au Modle.

Slectionnez
<?php

// Renvoie la liste de tous les billets, tris par identifiant dcroissant
function getBillets() {
$bdd = getBdd();
$requeteBillets = "select * from T_BILLET order by BIL_ID desc";
$stmtBillets = $bdd->query($requeteBillets);
return $stmtBillets;
}

// Effectue la connexion la BDD
// Instancie et renvoie l'objet PDO associ
function getBdd() {
$bdd = new PDO('mysql:host=localhost;dbname=monblog;charset=utf8',
'root', '');
return $bdd;
}
II-C-3. Gestion des erreurs
Par souci de simplification, nous avions mis de ct la problmatique de la gestion des erreurs. Il est temps de
s'y intresser.
Pour commencer, il faut dcider quelle partie de l'application aura la responsabilit de traiter les erreurs qui
pourraient apparatre lors de l'excution. Ce pourrait tre le Modle, mais il ne pourra pas les grer
correctement lui seul, ni informer l'utilisateur. La Vue, ddie la prsentation, n'a pas s'occuper de ce
genre de problmatique. Le meilleur choix est donc d'implmenter la gestion des erreurs au niveau
du Contrleur. Grer la dynamique de l'application, y compris dans les cas dgrads, fait partie de ses
responsabilits.
Nous allons tout d'abord modifier la connexion la base de donnes afin que les ventuelles erreurs soient
signales sous la forme d'exceptions.

Slectionnez
...
$bdd = new PDO('mysql:host=localhost;dbname=monblog;charset=utf8',
'root', '', array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
...
On peut ensuite ajouter notre page une gestion minimaliste des erreurs de la manire suivante.

Slectionnez
<?php

require 'modele.php';

try {
$billets = getBillets();
require 'listeBillets.php';
}
catch (Exception $e) {
echo '<html><body>Erreur ! ' . $e->getMessage() . '</body></html>';
}
Le premier require inclut uniquement la dfinition d'une fonction et est plac en dehors du bloc try. Le reste
du code est plac l'intrieur de ce bloc. Si une exception est leve lors de son excution, une page HTML
minimale contenant le message d'erreur est affiche.
On peut souhaiter conserver l'affichage du gabarit des vues mme en cas d'erreur. Il suffit de dfinir une
vue erreur.php ddie leur affichage.

Slectionnez
<?php $titre = 'Mon Blog ? Erreur !' ?>

<?php ob_start() ?>
<p>Une erreur est survenue : <?= $msgErreur ?></p>
<?php $contenu = ob_get_clean() ?>

<?php require 'gabarit.php' ?>
On modifie ensuite le contrleur (fichier index.php) pour dclencher le rendu de cette vue en cas d'erreur.

Slectionnez
<?php

require 'modele.php';

try {
$billets = getBillets();
require 'listeBillets.php';
}
catch (Exception $e) {
$msgErreur = $e->getMessage();
require 'erreur.php';
}
Remarque : dans un contexte professionnel, une gestion d'erreurs efficace impliquerait une journalisation
dans un fichier et l'affichage d'un message d'erreur adapt l'utilisateur.
II-D. Bilan : architecture obtenue

Nous avons accompli sur notre page d'exemple un important travail de refactoring qui a modifi son
architecture en profondeur. Notre page respecte prsent un modle MVC simple.
L'ajout de nouvelles pages se fait prsent en trois tapes :
criture des fonctions d'accs aux donnes dans le modle ;
cration d'une nouvelle vue utilisant le gabarit pour afficher les donnes ;
ajout d'une page contrleur pour lier le modle et la vue.
II-E. Application : affichage des dtails d'un billet
II-E-1. Description du nouveau besoin
Afin de rendre notre contexte d'exemple plus raliste, nous allons ajouter un nouveau besoin : le clic sur le
titre d'un billet du blog doit afficher sur une nouvelle page le contenu et les commentaires associs ce billet.

II-E-2. Prise en compte du nouveau besoin
Commenons par ajouter dans notre modle (fichier modele.php) la fonction d'accs la base dont nous avons
besoin.

Slectionnez
// Renvoie les informations sur un billet
function getBillet($idBillet)
{
$bdd = getBdd();
$stmtBillet = $bdd->query(
'select * from T_BILLET where BIL_ID=' . $idBillet);
$billet = $stmtBillet->fetch(); // Accs au premier lment rsultat
return $billet;
}

// Renvoie la liste des commentaires associs un billet
function getCommentaires($idBillet)
{
$bdd = getBdd();
$stmtCommentaires = $bdd->query(
'select * from T_COMMENTAIRE where BIL_ID=' . $idBillet);
return $stmtCommentaires;
}
Nous crons ensuite une nouvelle vue detailsBillet.php dont le rle est d'afficher les informations
demandes.

Slectionnez
<?php $titre = "Mon Blog - Dtail d'un billet" ?>

<?php ob_start() ?>
<article>
<header>
<h1 class="titreBillet"><?= $billet['BIL_TITRE'] ?></h1>
<time><?= $billet['BIL_DATE'] ?></time>
</header>
<p><?= $billet['BIL_CONTENU'] ?></p>
</article>
<hr />
<header>
<h1 id="titreReponses">Rponses <?= $billet['BIL_TITRE'] ?></h1>
</header>
<?php foreach ($commentaires as $commentaire): ?>
<p><?= $commentaire['COM_AUTEUR'] ?> dit :</p>
<p><?= $commentaire['COM_CONTENU'] ?></p>
<?php endforeach; ?>
<?php $contenu = ob_get_clean() ?>

<?php include 'gabarit.php' ?>
Bien entendu, cette vue dfinit les lments dynamiques $titre et $contenu, puis inclut le gabarit commun.
Enfin, on cre un nouveau fichier contrleur, billet.php, qui fait le lien entre modle et vue pour rpondre au
nouveau besoin. Elle a besoin de recevoir en paramtre l'identifiant du billet. Elle s'utilise donc sous la
forme billet.php?id=<id du billet>.

Slectionnez
<?php

require 'modele.php';

try {
if (isset($_GET['id'])) {
// Renvoie la valeur numrique du paramtre ou 0 en cas d'chec
$id = intval($_GET['id']);
if ($id != 0) {
$billet = getBillet($id);
$commentaires = getCommentaires($id);
require 'detailsBillet.php';
}
else
throw new Exception("Identifiant de billet incorrect");
}
else
throw new Exception("Aucun identifiant de billet");
}
catch (Exception $e) {
$msgErreur = $e->getMessage();
require 'erreur.php';
}
Il faut galement modifier la vue listeBillets.php afin d'ajouter un lien vers la page billet.php sur le titre du
billet.

Slectionnez
...
<header>
<h1 class="titreBillet">
<a href="<?= $lienBillet . $billet['BIL_ID'] ?>">
<?= $billet['BIL_TITRE'] ?>
</a>
</h1>
<time><?= $billet['BIL_DATE'] ?></time>
</header>
...
La variable $lienBillet utilise dans cette vue est dfinie dans le fichier contrleur index.php.

Slectionnez
...
$billets = getBillets();
$lienBillet = "billet.php?id=";
require 'listeBillets.php';
...
Pour finir, on enrichit la feuille de style CSS afin de conserver une prsentation harmonieuse.

Slectionnez
#titreReponses {
font-size : 100%;
}
Vous trouverez les fichiers sources de cette tape
l'adresse https://github.com/bpesquet/MonBlog/tree/mvc-simple.
III. Passage une architecture MVC oriente objet
III-A. Amlioration de l'architecture MVC
III-A-1. Mise en uvre d'un contrleur frontal (front controller)
L'architecture actuelle, base sur un contrleur par page affiche, souffre de certaines limitations :
elle expose la structure interne du site (noms des fichiers PHP) ;
elle rend dlicate l'application de politiques communes tous les contrleurs
(authentification, scurit, etc.).
Pour remdier ces dfauts, il est frquent d'ajouter au site un contrleur frontal.
Le contrleur frontal constitue le point d'entre unique du site. Son rle est de centraliser la gestion des
requtes entrantes. Il utilise le service d'un autre contrleur pour raliser l'action demande et renvoyer son
rsultat sous la forme d'une vue.
Un choix frquent consiste transformer le fichier principal index.php en contrleur frontal. Nous allons
mettre en uvre cette solution.
Ce changement d'architecture implique un changement d'utilisation du site. Voici comment fonctionne
actuellement notre blog :
l'excution de index.php permet d'afficher la liste des billets ;
l'excution de billet.php?id=<id du billet> affiche les dtails du billet identifi dans
l'URL.
La mise en uvre d'un contrleur frontal implique que index.php recevra la fois les demandes d'affichage de
la liste des billets et les demandes d'affichage d'un billet prcis. Il faut donc lui fournir de quoi lui permettre
d'identifier l'action raliser. Une solution courante est d'ajouter l'URL un paramtre action. Dans notre
exemple, voici comment ce paramtre sera interprt :
si action vaut afficherBillet, le contrleur principal dclenchera l'affichage d'un billet ;
si action n'est pas valoris, le contrleur dclenchera l'affichage de la liste des billets
(action par dfaut).
Voici le fichier index.php de notre blog, rcrit sous la forme d'un contrleur frontal.

Slectionnez
<?php

require('actions.php');

try {
if (isset($_GET['action'])) {
if ($_GET['action'] == 'afficherBillet') {
if (isset($_GET['id'])) {
$idBillet = intval($_GET['id']);
if ($idBillet != 0)
afficherBillet($idBillet);
else
throw new Exception("Identifiant de billet non valide");
}
else
throw new Exception("Identifiant de billet non dfini");
}
else
throw new Exception("Action non valide");
}
else {
listerBillets(); // action par dfaut
}
}
catch (Exception $e) {
afficherErreur($e->getMessage());
}
On peut observer l'implmentation du comportement dcrit plus haut. Toutes les actions possibles sont
implmentes sous la forme de fonctions dans le fichier actions.php, inclus par le contrleur frontal.

Slectionnez
<?php

require 'modele.php';

// Affiche la liste de tous les billets du blog
function listerBillets() {
$billets = getBillets();
$lienBillet = "index.php?action=afficherBillet&id=";
require 'listeBillets.php';
}

// Affiche un billet et tous ses commentaires
function afficherBillet($id) {
$billet = getBillet($id);
$commentaires = getCommentaires($id);
require 'detailsBillet.php';
}

// Affiche une erreur
function afficherErreur($msgErreur)
{
require 'erreur.php';
}
On remarque que le lien (variable $lienBillet) permettant de naviguer vers un billet prcis a t modifi pour
renvoyer vers le contrleur frontal, avec l'action afficherBillet en paramtre.
La mise en uvre d'un contrleur frontal a permis de prciser les responsabilits et de clarifier la dynamique
de la partie Contrleur de notre site :
le contrleur frontal analyse la requte entrante et vrifie les paramtres fournis ;
si la requte est cohrente, il dlgue l'action raliser en lui passant les paramtres
ncessaires ;
sinon, il signale l'erreur l'utilisateur.
Autre bnfice : l'organisation interne du site est totalement masque l'utilisateur, puisque seul le
fichier index.php est visible. Cette encapsulationfacilite les rorganisations internes, comme celle que nous
allons entreprendre maintenant.
III-A-2. Rorganisation des fichiers sources
Par souci de simplicit, nous avons jusqu' prsent stock tous nos fichiers sources dans le mme rpertoire.
mesure que le site gagne en complexit, cette organisation montre ses limites. Il est maintenant difficile de
deviner le rle de certains fichiers sans les ouvrir pour examiner leur code.
Nous allons donc restructurer notre site. La solution la plus vidente consiste crer des sous-rpertoires en
suivant le dcoupage MVC :

le rpertoire Modele contiendra le fichier modele.php ;
le rpertoire Vue contiendra les
fichiers listeBillets.php, detailsBillets.php et erreur.php, ainsi que la page
commune gabarit.php ;
le rpertoire Controleur contiendra le fichier des actions actions.php ;
On peut galement prvoir un rpertoire Contenu pour les contenus statiques (fichier CSS, images, etc.) et un
rpertoire BD pour le script de cration de la base de donnes. On aboutit l'organisation ci-contre.
Il est videmment ncessaire de mettre jour les inclusions et les liens pour prendre en compte la nouvelle
organisation des fichiers sources. On remarque au passage que les mises jour sont localises et internes :
grce au contrleur frontal, les URL permettant d'utiliser notre site ne changent pas.
III-A-3. Bilan provisoire
Notre blog d'exemple est maintenant structur selon les principes du modle MVC, avec une sparation nette
des responsabilits entre composants qui se reflte dans l'organisation des sources.
Notre solution est avant tout procdurale : les actions du contrleur et les services du modle sont
implments sous la forme de fonctions.
Vous trouverez les fichiers sources de cette tape
l'adresse https://github.com/bpesquet/MonBlog/tree/mvc-procedural.
L'amlioration de l'architecture passe maintenant par la mise en uvre des concepts de la programmation
oriente objet, que PHP supporte pleinement depuis plusieurs annes.
III-B. Aperu du modle objet de PHP
partir de la version 5 (sortie en 2004), le langage PHP permet de programmer orient objet en crant des
classes munies d'attributs et de mthodes.
Nous allons tudier le modle objet de PHP, et notamment ses spcificits par rapport d'autres langages
comme Java ou C#, travers l'exemple classique des comptes bancaires.
III-B-1. Exemple de hirarchie de classes
Voici la dfinition PHP d'une classe CompteBancaire.
CompteBancaire.php
Slectionnez
<?php

class CompteBancaire
{
private $devise;
private $solde;
private $titulaire;

public function __construct($devise, $solde, $titulaire)
{
$this->devise = $devise;
$this->solde = $solde;
$this->titulaire = $titulaire;
}

public function getDevise()
{
return $this->devise;
}

public function getSolde()
{
return $this->solde;
}

protected function setSolde($solde)
{
$this->solde = $solde;
}

public function getTitulaire()
{
return $this->titulaire;
}

public function crediter($montant) {
$this->solde += $montant;
}

public function __toString()
{
return "Le solde du compte de $this->titulaire est de " .
$this->solde . " " . $this->devise;
}
}
En complment, voici la dfinition d'une classe CompteEpargne.
CompteEpargne.php
Slectionnez
<?php

require_once 'CompteBancaire.php';

class CompteEpargne extends CompteBancaire
{
private $tauxInteret;

public function __construct($devise, $solde, $titulaire, $tauxInteret)
{
parent::__construct($devise, $solde, $titulaire);
$this->tauxInteret = $tauxInteret;
}

public function getTauxInteret()
{
return $this->tauxInteret;
}

public function calculerInterets($ajouterAuSolde = false)
{
$interets = $this->getSolde() * $this->tauxInteret;
if ($ajouterAuSolde == true)
$this->setSolde($this->getSolde() + $interets);
return $interets;
}

public function __toString()
{
return parent::__toString() .
'. Son taux d\'intrt est de ' . $this->tauxInteret * 100 . '%.';
}
}
Voici un exemple d'utilisation de ces deux classes.

Slectionnez
<?php

require 'CompteBancaire.php';
require 'CompteEpargne.php';

$compteJean = new CompteBancaire("euros", 150, "Jean");
echo $compteJean . '<br />';
$compteJean->crediter(100);
echo $compteJean . '<br />';

$comptePaul = new CompteEpargne("dollars", 200, "Paul", 0.05);
echo $comptePaul . '<br />';
echo 'Intrts pour ce compte : ' . $comptePaul->calculerInterets() .
' ' . $comptePaul->getDevise() . '<br />';
$comptePaul->calculerInterets(true);
echo $comptePaul . '<br />';
Enfin, voici le rsultat de son excution.

III-B-2. Caractristiques du modle objet de PHP
L'observation des exemples prcdents nous permet de retrouver certains concepts bien connus de la POO,
repris par PHP :
une classe se compose d'attributs et de mthodes ;
le mot-cl class permet de dfinir une classe ;
les diffrents niveaux d'accessibilit sont public, protected et private ;
le mot-cl extends permet de dfinir une classe drive (comme en Java) ;
le mot-cl $this permet d'accder aux membres de l'objet courant ;
le mot-cl return permet de renvoyer la valeur de retour d'une mthode.
III-B-3. Spcificits du modle objet de PHP
Mme s'il est similaire ceux de C#, Java ou C++, le modle objet de PHP possde certaines particularits :
PHP tant un langage typage dynamique, on ne prcise pas les types des attributs et
des mthodes, mais seulement leur niveau d'accessibilit ;
le mot-cl function permet de dclarer une mthode, quelle que soit sa valeur de retour ;
le mot-cl parent permet d'accder au parent de l'objet courant. Il joue en PHP le mme
rle que base en C# et super en Java ;
le constructeur d'une classe s'crit __construct ;
la mthode __toString dtermine comment l'objet est affich en tant que chane de
caractres ;
on peut redfinir (override) une mthode, comme ici __toString, sans mot-cl
particulier ;
le mot-cl $this est obligatoire pour accder aux membres de l'objet courant. Son
utilisation est optionnelle en C# et en Java, et souvent limite la leve des ambiguts
entre attributs et paramtres ;
il est possible de dfinir une valeur par dfaut pour les paramtres d'une mthode. Elle
est utilise lorsque l'argument (paramtre effectif) n'est pas prcis au moment de
l'appel.
Remarques :
les mthodes __construct et __toString font partie de ce qu'on appelle les mthodes
magiques ;
l'instruction require_once est similaire require mais n'inclut le fichier demand qu'une
seule fois. Elle est utile pour viter, comme ici, les dfinitions multiples de classes.
III-C. Mise en uvre du modle objet de PHP
Munis de cette connaissance minimale du modle objet de PHP, nous pouvons prsent amliorer
l'architecture de notre site d'exemple en tirant parti des possibilits de la POO, en particulier l'encapsulation et
l'hritage.
III-C-1. Passage un Modle orient objet
Pour mmoire, voici la dfinition actuelle de notre partie Modle (fichier modele.php).

Slectionnez
<?php

// Renvoie la liste de tous les billets, tris par identifiant dcroissant
function getBillets() {
$bdd = getBDD();
$requeteBillets = "select * from T_BILLET order by BIL_ID desc";
$stmtBillets = $bdd->query($requeteBillets);
return $stmtBillets;
}

// Renvoie les informations sur un billet
function getBillet($idBillet)
{
$bdd = getBDD();
$stmtBillet = $bdd->query(
'select * from T_BILLET where BIL_ID=' . $idBillet);
$billet = $stmtBillet->fetch(); // Accs au premier lment rsultat
return $billet;
}

// Renvoie la liste des commentaires associs un billet
function getCommentaires($idBillet)
{
$bdd = getBDD();
$stmtCommentaires = $bdd->query(
'select * from T_COMMENTAIRE where BIL_ID=' . $idBillet);
return $stmtCommentaires;
}

// Effectue la connexion la BDD
// Instancie et renvoie l'objet PDO associ
function getBDD() {
$bdd = new PDO(
'mysql:host=localhost;dbname=monblog;charset=utf8',
'root', '', array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
return $bdd;
}
Rappel : le rle de la partie Modle d'une application MVC est d'encapsuler la logique mtier et l'accs aux
donnes.
Dans le cadre d'un passage la POO, il serait envisageable de crer des classes mtier modlisant les entits
du domaine, en l'occurrence Billet etCommentaire.

Plus modestement, nous allons nous contenter de dfinir les services d'accs aux donnes en tant que
mthodes et non comme simples fonctions. Voici une premire version possible de la classe Modele.

Slectionnez
<?php

class Modele {

// Renvoie la liste de tous les billets, tris par identifiant dcroissant
public function getBillets() {
$bdd = $this->getBDD();
$requeteBillets = "select * from T_BILLET order by BIL_ID desc";
$stmtBillets = $bdd->query($requeteBillets);
return $stmtBillets;
}

// Renvoie les informations sur un billet
public function getBillet($idBillet) {
$bdd = $this->getBDD();
$stmtBillet = $bdd->query(
'select * from T_BILLET where BIL_ID=' . $idBillet);
$billet = $stmtBillet->fetch(); // Accs au premier lment rsultat
return $billet;
}

// Renvoie la liste des commentaires associs un billet
public function getCommentaires($idBillet) {
$bdd = $this->getBDD();
$stmtCommentaires = $bdd->query(
'select * from T_COMMENTAIRE where BIL_ID=' . $idBillet);
return $stmtCommentaires;
}

// Effectue la connexion la BDD
// Instancie et renvoie l'objet PDO associ
private function getBDD() {
$bdd = new PDO(
'mysql:host=localhost;dbname=monblog;charset=utf8',
'root', '', array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
return $bdd;
}
}
Par rapport notre ancien modle procdural, la seule relle avance offerte par cette classe est
l'encapsulation de la mthode de connexion la base. De plus, elle regroupe des services lis des entits
distinctes (billets et commentaires), ce qui est contraire au principe de cohsion forte.
Une meilleure solution consiste crer un modle par entit du domaine, tout en regroupant les services
communs dans une superclasse commune.
On peut crire la classe Billet, en charge de l'accs aux donnes des billets, comme ceci.
Billet.php
Slectionnez
<?php

require_once 'Modele/Modele.php';

class Billet extends Modele
{
// Renvoie la liste des billets
public function lireTout()
{
return $this->executerLecture(
'select * from T_BILLET order by BIL_ID desc');
}

// Renvoie un billet identifi
public function lire($id)
{
return $this->executerLecture(
'select * from T_BILLET where BIL_ID=' . $id, true);
}
}
La classe Modele est dsormais abstraite (mot-cl abstract) et fournit ses classes drives des services de
connexion la base et d'excution d'une requte de slection.
Modele.php
Slectionnez
<?php

abstract class Modele {

private $bdd;

// Excute une requte SQL de lecture dans la base
protected function executerLecture($sql, $accederPremierResultat = false) {
$stmtResultats = $this->getBdd()->query($sql);
if ($ accederPremierResultat == true)
return $stmtResultats->fetch(); // Accs au premier rsultat
else
return $stmtResultats;
}

// Renvoie un objet de connexion la BDD
private function getBdd() {
if ($this->bdd === null) {
$this->bdd = new PDO(
'mysql:host=localhost;dbname=monblog;charset=utf8',
'root', '', array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
}
return $this->bdd;
}

}
On remarque au passage que la technologie d'accs la base est totalement masque aux modles concrets,
et que Modele utilise la technique du chargement tardif (lazy loading) pour retarder l'instanciation de
l'objet $bdd sa premire utilisation.
Sur le mme modle, on peut crire la classe Commentaire.
Commentaire.php
Slectionnez
<?php

require_once 'Modele/Modele.php';

class Commentaire extends Modele
{
// Renvoie la liste des commentaires associs un billet
public function lireListe($idBillet)
{
return $this->executerLecture(
'select * from T_COMMENTAIRE where BIL_ID=' . $idBillet);
}
}
prsent, l'architecture de la partie Modele tire parti des avantages de la POO que sont l'encapsulation et
l'hritage. Cette architecture facilite les volutions : si le contexte mtier s'enrichit (exemple : gestion des
auteurs de billets), il suffit de crer une nouvelle classe modle drive de Modele(exemple : Auteur) qui
s'appuiera sur les services communs fournis par sa superclasse.
III-C-2. Passage un Contrleur orient objet
Notre partie Contrleur actuelle se compose du contrleur frontal et d'une srie d'actions crites sous la
forme de fonctions.

Slectionnez
<?php

require 'Modele/modele.php';

// Affiche la liste de tous les billets du blog
function listerBillets() {
$billets = getBillets();
$lienBillet = "index.php?action=afficherBillet&id=";
require 'Vue/listeBillets.php';
}

// Affiche un billet et tous ses commentaires
function afficherBillet($id) {
$billet = getBillet($id);
$commentaires = getCommentaires($id);
require 'Vue/detailsBillet.php';
}

// Affiche une erreur
function afficherErreur($msgErreur)
{
require 'Vue/erreur.php';
}
On rappelle que le rle de la partie Contrleur d'une application est de grer la dynamique de l'application.
Commenons par regrouper les actions sous la forme de mthodes d'une classe Controleur.

Slectionnez
<?php

require_once 'Modele/Billet.php';
require_once 'Modele/Commentaire.php';

class Controleur
{
private $billet;
private $commentaire;

public function __construct()
{
$this->billet = new Billet();
$this->commentaire = new Commentaire();
}

public function listerBillets()
{
$billets = $this->billet->lireTout();
$lienBillet = "index.php?action=afficherBillet&id=";
require 'Vue/listeBillets.php';
}

public function afficherBillet($id)
{
$billet = $this->billet->lire($id);
$commentaires = $this->commentaire->lireListe($id);
require 'Vue/detailsBillet.php';
}

public function afficherErreur($msgErreur)
{
require 'Vue/erreur.php';
}
}
Pour l'instant, les possibilits de la POO sont peu exploites. Pour faire mieux, on va dcoupler ce qui est
spcifique ce contrleur (les mthodes d'action lies aux billets) et ce qui serait commun aux autres
contrleurs ajoutables par la suite : les services de gnration d'une vue et d'affichage d'une erreur. On
regroupe ces services dans une classe abstraite Controleur.
Controleur.php
Slectionnez
<?php

abstract class Controleur
{
protected function genererVue($vue, $donnees = array())
{
$fichierVue = 'Vue/' . $vue . '.php';
if (file_exists($fichierVue)) {
extract($donnees);
require $fichierVue;
}
else
throw new Exception("Fichier $fichierVue non trouv");
}

protected function afficherErreur($msgErreur)
{
require 'Vue/erreur.php';
}
}
La mthode genererVue encapsule l'utilisation de require et permet en outre de vrifier l'existence du fichier
vue afficher. Elle utilise la fonction PHPextract pour que la vue puisse accder aux variables PHP qui lui sont
ncessaires. Comme nous allons le voir ci-dessous, ces variables sont passes en paramtre sous la forme
d'un tableau associatif (paramtre $donnees).
On peut prsent crer une classe ControleurBillet qui utilise les fonctionnalits de Controleur.
ControleurBillet.php
Slectionnez
<?php

require_once 'Modele/ModeleBillet.php';
require_once 'Modele/ModeleCommentaire.php';

require_once 'Controleur/Controleur.php';

class ControleurBillet extends Controleur
{
private $billet;
private $commentaire;

public function __construct()
{
$this->billet = new Billet();
$this->commentaire = new Commentaire();
}

public function listerBillets()
{
$billets = $this->billet->lireTout();
$lienBillet = "index.php?action=afficherBillet&id=";
$this->genererVue('listeBillets',
array('billets' => $billets, 'lienBillet' => $lienBillet));
}

public function afficherBillet($id)
{
$billet = $this->billet->lire($id);
$commentaires = $this->commentaire->lireListe($id);
$this->genererVue('detailsBillet',
array('billet' => $billet, 'commentaires' => $commentaires));
}
}
Ce contrleur utilise la mthode genererVue de sa superclasse avec deux arguments :
le nom de la vue (le rpertoire et l'extension .php sont ajouts automatiquement) ;
un tableau associatif contenant l'ensemble des donnes ncessaires la gnration de la
vue. Chaque lment de ce tableau est constitu d'une cl (entre apostrophes) et de la
valeur associe cette cl. Cette technique permet d'utiliser la mthode
gnrique genererVue en lui passant des paramtres spcifiques.
Il nous reste refactoriser le contrleur frontal. Il s'agit d'un contrleur particulier, dont le rle est d'analyser
la requte entrante pour dterminer l'action entreprendre. On parle souvent de routage de la requte.
On pourrait crire ce contrleur sous la forme d'une classe ControleurFrontal drive de Controleur.

Slectionnez
<?php

require 'Controleur/ControleurBillet.php';
require_once 'Controleur/Controleur.php';

class ControleurFrontal extends Controleur
{
private $ctrlBillet;

public function __construct()
{
$this->ctrlBillet = new ControleurBillet();
}

public function routerRequete()
{
try {
if (isset($_GET['action'])) {
if ($_GET['action'] == 'afficherBillet') {
if (isset($_GET['id'])) {
$idBillet = intval($_GET['id']);
if ($idBillet != 0)
$this->ctrlBillet->afficherBillet($idBillet);
else
throw new Exception(
"Identifiant de billet non valide");
}
else
throw new Exception("Identifiant de billet non dfini");
}
else
throw new Exception("Action non valide");
}
else {
$this->ctrlBillet->listerBillets(); // action par dfaut
}
}
catch (Exception $e) {
$this->afficherErreur($e->getMessage());
}
}
}
Le fichier principal index.php est maintenant simplifi l'extrme. Il se contente d'instancier le contrleur
frontal puis de lui faire router la requte.

Slectionnez
<?php

require 'Controleur/ControleurFrontal.php';

$ctrl = new ControleurFrontal();
$ctrl->routerRequete();
On peut remarquer que la mthode routerRequete du contrleur frontal est dj relativement complexe, alors
qu'elle ne gre que deux actions. Afin d'anticiper l'ajout de fonctionnalits au blog, nous pouvons d'ores et
dj la scinder en plusieurs sous-mthodes.
De plus, le code du contrleur frontal effectue beaucoup de contrles sur les paramtres reus. Nous allons
ajouter une mthode ddie dans la classe abstraite Controleur.
Controleur.php
Slectionnez
<?php

abstract class Controleur
{
...

protected function getParametreUrl($nomParametre) {
if (isset($_GET[$nomParametre])) {
$param = htmlentities($_GET[$nomParametre], ENT_QUOTES);
return $param;
}
else
throw new Exception("Paramtre '$nomParametre' absent de l'URL");
}
}
La mthode getParametreUrl vrifie si un paramtre est dfini dans l'URL demande :
si le paramtre est prsent, elle le renvoie aprs l'avoir nettoy avec la fonction
PHP htmlentities. Ceci a pour but d'viter l'excution de code indsirable ;
si le paramtre n'est pas trouv, elle lve une exception pour signaler le problme.
Voici prsent le contrleur frontal mis en place.
ControleurFrontal.php
Slectionnez
<?php

require 'Controleur/ControleurBillet.php';
require_once 'Controleur/Controleur.php';

class ControleurFrontal extends Controleur {

private $ctrlBillet;

public function __construct() {
$this->ctrlBillet = new ControleurBillet();
}

public function routerRequete() {
try {
if (!empty($_GET)) {
$this->executerAction();
}
else {
$this->ctrlBillet->listerBillets(); // action par dfaut
}
}
catch (Exception $e) {
$this->afficherErreur($e->getMessage());
}
}

private function executerAction() {
$action = $this->getParametreUrl('action');
switch ($action) {
case 'afficherBillet' :
$param = $this->getParametreUrl('id');
$idBillet = intval($param);
if ($idBillet != 0)
$this->ctrlBillet->afficherBillet($idBillet);
else {
throw new Exception(
"Identifiant de billet '$param' non valide");
}
break;
default :
throw new Exception("Action '$action' non valide");
break;
}
}

}
Depuis la mise en place du contrleur frontal, toutes les URL du blog sont de la
forme index.php?action=<action raliser>. La mthodeexecuterAction dtecte l'action excuter partir
du paramtre action prsent dans l'URL. Elle appelle ensuite le service du contrleur adquat.
III-D. Bilan : architecture obtenue

La structure actuelle du site est prsente ci-dessus. Elle est videmment beaucoup plus complexe qu'au
dpart. Cette complexit est le prix payer pour disposer de fondations robustes qui faciliteront la
maintenance et les volutions futures.
L'ajout de nouvelles fonctionnalits se fait prsent en trois tapes :
ajout ou enrichissement de la classe associe dans le modle ;
ajout ou enrichissement d'une vue utilisant le gabarit pour afficher les donnes ;
ajout ou enrichissement d'une classe contrleur pour lier le modle et la vue.
III-E. Application : ajout d'un commentaire
III-E-1. Description du nouveau besoin
On souhaite maintenant que l'affichage des dtails sur un billet permette d'ajouter un nouveau commentaire.
Le remplissage des champs Auteur et Commentaire est obligatoire. Le clic sur le bouton Commenter dclenche
l'insertion du commentaire dans la base de donnes et la ractualisation de la page Web.

III-E-2. Prise en compte du nouveau besoin
On commence par intervenir dans la partie Modle. Nous avons besoin d'un service d'ajout de commentaire
dans la base de donnes. Pour cela, on ajoute la classe abstraite Modele une mthode
gnrique executerModification permettant d'excuter une requte SQL de modification
(INSERT,UPDATE ou DELETE).

Slectionnez
abstract class Modele {
...
protected function executerModification($sql, $valeurs)
{
$requete = $this->getBdd()->prepare($sql);
$requete->execute($valeurs);
}
...
}
Ensuite, on ajoute la classe Commentaire une mthode dont le rle est d'ajouter un nouveau commentaire
dans la base.

Slectionnez
class Commentaire extends Modele
{
...
public function ajouter($auteur, $commentaire, $idBillet)
{
$date = date(DATE_W3C);
$this->executerModification(
'insert into T_COMMENTAIRE(COM_DATE, COM_AUTEUR, COM_CONTENU, BIL_ID)
values (?, ?, ?, ?)',
array($date, $auteur, $commentaire, $idBillet));
}
}
Passons maintenant la partie Vue. Afin de pouvoir saisir le nouveau commentaire, on ajoute un formulaire
la fin de la vue detailsBillet.php.

Slectionnez
<?php $titre = "Mon Blog - Dtail d'un billet" ?>

<?php ob_start() ?>
...
<?php endforeach; ?>
<hr />
<form method="post" action="index.php">
<input id="auteur" name="auteur" type="text" placeholder="Votre pseudo"
required /><br />
<textarea id="txt_commentaire" name="commentaire" rows="4"
placeholder="Votre commentaire" required></textarea><br />
<input name="idBillet" type="hidden" value="<?= $billet['BIL_ID'] ?>" />
<input type="submit" value="Commenter" />
</form>
<?php $contenu = ob_get_clean() ?>

<?php include 'gabarit.php' ?>
La feuille de style CSS est lgrement modifie pour intgrer le stylage de la zone de saisie du commentaire.

Slectionnez
#txt_commentaire {
width: 100%;
}
Il faut ensuite enrichir notre contrleur frontal afin de grer la validation du formulaire.

Slectionnez
class ControleurFrontal extends Controleur {
...
public function routerRequete() {
try {
if (!empty($_GET)) {
$this->executerAction();
}
elseif (!empty($_POST)) {
$this->validerFormulaire();
}
else {
$this->ctrlBillet->listerBillets(); // action par dfaut
}
}
catch (Exception $e) {
$this->afficherErreur($e->getMessage());
}
}
...
private function validerFormulaire() {
$auteur = $this->getParametreRequete('auteur');
$commentaire = $this->getParametreRequete('commentaire');
$param = $this->getParametreRequete('idBillet');
$idBillet = intval($param);
if ($idBillet != 0)
$this->ctrlBillet->ajouterCommentaire($auteur, $commentaire,
$idBillet);
else
throw new Exception("Identifiant de billet '$param' non valide");
}
}
La mthode validerFormulaire du contrleur frontal utilise une nouvelle mthode ajoute la superclasse
abstraite Controleur. Elle permet de rcuprer un paramtre inclus dans la requte reue (dans le
tableau $_POST).

Slectionnez
abstract class Controleur {
...
protected function getParametreRequete($nomParametre) {
if (isset($_POST[$nomParametre])) {
$param = htmlentities($_POST[$nomParametre], ENT_QUOTES);
return $param;
}
else
throw new Exception(
"Paramtre '$nomParametre' absent de la requte");
}
}
Il ne reste plus qu' ajouter la classe ControleurBillet l'action associe l'ajout du nouveau commentaire.

Slectionnez
class ControleurBillet extends Controleur
{
...

public function ajouterCommentaire($auteur, $commentaire, $idBillet) {
$this->commentaire->ajouter($auteur, $commentaire, $idBillet);
$this->afficherBillet($idBillet);
}
}
Dans cette action, on fait appel au nouveau service du modle pour ajouter le commentaire dans la base, puis
on dclenche l'opration d'affichage du billet actuel.
Vous trouverez les fichiers sources de la version finale du blog
l'adresse https://github.com/bpesquet/MonBlog.
IV. Conclusion et perspectives
Nous arrivons au terme de notre chantier de refactoring. D'une simple page PHP, notre blog d'exemple s'est
transform en un site Web architectur selon les principes du modle MVC. Il dispose d'un contrleur frontal
orient objet, ainsi que de classes abstraites rutilisables fournissant des services communs.
IV-A. Amliorations possibles
Mme si notre blog a normment gagn en qualit de conception, il subsiste quelques limites qui sont autant
de pistes d'amlioration futures :
chaque modle instancie une connexion distincte la base de donnes, ce qui est sous-optimal
du point de vue des performances ;
dans la classe abstraite Modele, il faudrait paramtrer le nom de la base de donnes utilise ainsi
que les identifiants de connexion ;
les requtes sans rsultat (exemple : identifiant de billet non trouv dans la base) ne
provoquent pas de traitement spcifique ;
les noms des champs dans la base de donnes sont utiliss dans toute l'application, jusqu'aux
vues. Cela rend risqu un changement du schma BD ;
on pourrait mettre en place un systme d'URL gnriques. L'utilisation du site se ferait avec des
URL de la forme index.php/afficherBillet/2 plutt que des
URL index.php?action=afficherBillet&id=2. On retrouve ce format d'URL dans la plupart des
plates-formes PHP modernes.
IV-B. Pour aller encore plus loin : les frameworks PHP
Mme s'il reste possible d'enrichir encore l'architecture de notre blog, nous arriverons tt ou tard aux limites
de ce qu'on peut obtenir en codant manuellement un site de A Z. Pour gagner en qualit, l'tape suivante
serait de s'appuyer sur un framework PHP.
Un framework fournit un ensemble de services de base, gnralement sous la forme de classes en interaction.
condition de respecter l'architecture qu'il prconise (pratiquement toujours une dclinaison du modle MVC),
un framework PHP libre le dveloppeur de nombreuses tches techniques comme le routage des requtes, la
scurit, la gestion du cache, etc. Cela lui permet de se concentrer sur l'essentiel, c'est--dire ses tches
mtier. Il existe une grande quantit de frameworks PHP. Parmi les plus connus, citons Symfony, Zend
Framework ou encore CodeIgniter.

Anda mungkin juga menyukai