Вы находитесь на странице: 1из 50

voluer vers une architecture MVC en PHP

Par Baptiste Pesquet

Date de publication : 27 mars 2013

Dernire mise jour : 2 juillet 2015

Dcouvrez comment amliorer l'architecture d'un site Web, depuis une organisation
classique vers une architecture MVC utilisant un framework cr de toutes pices.

Cet article est 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.

Tous les fichiers source sont disponibles sur le dpt GitHub associ l'article.

IMPORTANT : cet article est vise uniquement pdagogique. Le framework construit ici
illustre certains principes de conception logicielle mais n'est pas destin une utilisation
professionnelle. Si vous souhaitez dcouvrir comment intgrer un vrai framework PHP
vos dveloppements, consultez ce cours.

Commentez
voluer vers une architecture MVC en PHP par Baptiste Pesquet

I - Prsentation du contexte d'exemple.......................................................................................................................4


I-A - Base de donnes...........................................................................................................................................4
I-B - Page principale.............................................................................................................................................. 4
I-C - Affichage obtenu............................................................................................................................................ 5
I-D - Critique de l'exemple..................................................................................................................................... 7
II - Mise en place d'une architecture MVC simple...................................................................................................... 7
II-A - Amlioration de l'exemple............................................................................................................................. 7
II-A-1 - Isolation de l'affichage.......................................................................................................................... 7
II-A-2 - Isolation de l'accs aux donnes......................................................................................................... 8
II-A-3 - Bilan provisoire..................................................................................................................................... 9
II-B - Le modle MVC............................................................................................................................................ 9
II-B-1 - Prsentation..........................................................................................................................................9
II-B-2 - Rles des composants......................................................................................................................... 9
II-B-3 - Interactions entre les composants..................................................................................................... 10
II-B-4 - Avantages et inconvnients................................................................................................................10
II-B-5 - Diffrences avec un modle en couches........................................................................................... 10
II-C - Amliorations supplmentaires................................................................................................................... 11
II-C-1 - Factorisation des lments d'affichage communs............................................................................. 11
II-C-2 - Factorisation de la connexion la base............................................................................................ 12
II-C-3 - Gestion des erreurs............................................................................................................................13
II-D - Bilan provisoire........................................................................................................................................... 14
II-E - Application : affichage des dtails d'un billet.............................................................................................. 14
II-E-1 - Prise en compte du nouveau besoin................................................................................................. 14
II-E-2 - Affichage obtenu.................................................................................................................................16
III - Amlioration de l'architecture MVC.................................................................................................................... 17
III-A - Rappels sur l'architecture actuelle............................................................................................................. 17
III-B - Mise en uvre d'un contrleur frontal (front controller)............................................................................ 17
III-C - Rorganisation des fichiers source........................................................................................................... 19
III-D - Bilan provisoire.......................................................................................................................................... 20
IV - Passage une architecture MVC oriente objet............................................................................................... 20
IV-A - Aperu du modle objet de PHP.............................................................................................................. 20
IV-A-1 - Caractristiques du modle objet de PHP........................................................................................22
IV-A-2 - Spcificits du modle objet de PHP............................................................................................... 22
IV-B - Mise en uvre du modle objet de PHP................................................................................................. 22
IV-B-1 - Rappels sur l'architecture actuelle.................................................................................................... 22
IV-B-2 - Passage un Modle orient objet..................................................................................................23
IV-B-3 - Passage une Vue oriente objet................................................................................................... 26
IV-B-4 - Passage un Contrleur orient objet.............................................................................................28
IV-C - Bilan provisoire.......................................................................................................................................... 31
IV-D - Application : ajout d'un commentaire........................................................................................................ 32
IV-D-1 - Description du nouveau besoin........................................................................................................ 32
IV-D-2 - Prise en compte du nouveau besoin................................................................................................33
V - Construction d'un framework MVC......................................................................................................................35
V-A - O aller maintenant ?................................................................................................................................. 35
V-A-1 - Intrt d'un framework....................................................................................................................... 35
V-A-2 - Limites de l'architecture actuelle........................................................................................................35
V-B - tapes de construction du framework........................................................................................................36
V-B-1 - Accs gnrique aux donnes.......................................................................................................... 36
V-B-2 - Automatisation du routage de la requte.......................................................................................... 39
V-B-3 - Mise en place d'URL gnriques.......................................................................................................43
V-B-4 - Scurisation des donnes reues et affiches.................................................................................. 44
V-B-5 - Contraintes sur l'architecture du site................................................................................................. 45
V-C - Application : utilisation du framework sur le contexte d'exemple............................................................... 46
VI - Conclusion et perspectives.................................................................................................................................47
VI-A - Bilan final................................................................................................................................................... 47
VI-B - Complments............................................................................................................................................. 48
VI-C - Pour aller encore plus loin........................................................................................................................ 49
VI-D - Liens utiles.................................................................................................................................................50

-2-
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

VII - Remerciements.................................................................................................................................................. 50

-3-
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

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 puisant ses informations dans une base de donnes relationnelle.

Vous trouverez les fichiers source 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.

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.

index.php
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="style.css" />
<title>Mon Blog</title>
</head>
<body>
<div id="global">
<header>
<a href="index.php"><h1 id="titreBlog">Mon Blog</h1></a>
<p>Je vous souhaite la bienvenue sur ce modeste blog.</p>
</header>
<div id="contenu">

-4-
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

index.php
<?php
$bdd = new PDO('mysql:host=localhost;dbname=monblog;charset=utf8',
'root', '');
$billets = $bdd->query('select BIL_ID as id, BIL_DATE as date,'
. ' BIL_TITRE as titre, BIL_CONTENU as contenu from T_BILLET'
. ' order by BIL_ID desc');
foreach ($billets as $billet): ?>
<article>
<header>
<h1 class="titreBillet"><?= $billet['titre'] ?></h1>
<time><?= $billet['date'] ?></time>
</header>
<p><?= $billet['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 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

Une feuille de style CSS est utilise afin d'amliorer le rendu HTML. Voici le code source associ.

style.css
/* 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.
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;
}

h1 {
color: white;
}

.titreBillet {
margin-bottom : 0px;
}

-5-
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

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

#contenu {
margin-bottom : 30px;
}

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

Le rsultat obtenu depuis un navigateur client est le suivant.

-6-
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

I-D - Critique de l'exemple

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.

De manire gnrale, 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 (requtes SQL). Ceci
est contraire au principe de responsabilit unique. Ce principe de conception logicielle est le suivant : afin de
clarifier l'architecture et de faciliter les volutions, une application bien conue doit tre dcompose en sous-parties,
chacune ayant un rle et une responsabilit particuliers. L'architecture actuelle montre ses limites ds que le contexte
se complexifie. Le volume de code des pages PHP explose et la maintenabilit 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.

index.php
<?php
// Accs aux donnes
$bdd = new PDO('mysql:host=localhost;dbname=monblog;charset=utf8', 'root', '');
$billets = $bdd->query('select BIL_ID as id, BIL_DATE as date,'
. ' BIL_TITRE as titre, BIL_CONTENU as contenu from T_BILLET'
. ' order by BIL_ID desc');
?>

<!-- Affichage -->


<!doctype html>
<html lang="fr">
<head>
...
<div id="contenu">
<?php foreach ($billets as $billet): ?>
<article>
<header>
<h1 class="titreBillet"><?= $billet['titre'] ?></h1>
<time><?= $billet['date'] ?></time>
</header>
<p><?= $billet['contenu'] ?></p>
</article>
<hr />
<?php endforeach; ?>
</div> <!-- #contenu -->
...

-7-
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

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
vueAccueil.php.

vueAccueil.php
<!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 :

index.php
<?php
// Accs aux donnes
$bdd = new PDO('mysql:host=localhost;dbname=monblog;charset=utf8', 'root', '');
$billets = $bdd->query('select BIL_ID as id, BIL_DATE as date,'
. ' BIL_TITRE as titre, BIL_CONTENU as contenu from T_BILLET'
. ' order by BIL_ID desc');

// Affichage
require 'vueAccueil.php';

Rappel : la fonction PHP require fonctionne de manire similaire include : elle inclut et excute 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.

Modele.php
<?php

// Renvoie la liste de tous les billets, tris par identifiant dcroissant


function getBillets() {
$bdd = new PDO('mysql:host=localhost;dbname=monblog;charset=utf8', 'root', '');
$billets = $bdd->query('select BIL_ID as id, BIL_DATE as date,'
. ' BIL_TITRE as titre, BIL_CONTENU as contenu from T_BILLET'
. ' order by BIL_ID desc');

-8-
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Modele.php
return $billets;
}

Dans ce fichier, nous avons dplac la rcupration des billets du blog l'intrieur d'une fonction nomme getBillets.

Le code d'affichage (fichier vueAccueil.php) ne change pas. Le lien entre accs aux donnes et prsentation est
effectu par le fichier principal index.php. Ce fichier est maintenant trs simple.

index.php
<?php

require 'Modele.php';

$billets = getBillets();

require 'vueAccueil.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 ;


vueAccueil.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 de conception ( design pattern ) 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, 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.

-9-
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

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

1 La demande de l'utilisateur (exemple : requte HTTP) est reue et interprte par le Contrleur.
2 Celui-ci utilise les services du Modle afin de prparer les donnes afficher.
3 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 partie
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.

- 10 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

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).

gabarit.php
<!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>
<a href="index.php"><h1 id="titreBlog">Mon Blog</h1></a>
<p>Je vous souhaite la bienvenue sur ce modeste blog.</p>
</header>
<div id="contenu">
<?= $contenu ?> <!-- lment spcifique -->
</div>
<footer id="piedBlog">
Blog ralis avec PHP, HTML5 et CSS.
</footer>
</div> <!-- #global -->
</body>
</html>

- 11 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

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 vueAccueil.php rcrite :

vueAccueil.php
<?php $titre = 'Mon Blog'; ?>

<?php ob_start(); ?>


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

<?php require 'gabarit.php'; ?>

Ce code mrite quelques explications :

1 La premire ligne dfinit la valeur de l'lment spcifique $titre ;


2 Le 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 ;
3 La suite du code (boucle foreach) gnre les balises HTML article associes aux billets du blog. Le flux
HTML cr est mis en tampon ;
4 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 ;
5 Enfin, on dclenche le rendu du gabarit. Lors du rendu, les valeurs des lments spcifiques $titre et
$contenu seront insrs 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 fonction getBdd ajoute dans le fichier Modele.php. Cela vitera de dupliquer le code de
connexion lorsque nous ajouterons d'autres fonctions au Modle.

Modele.php
<?php

// Renvoie la liste de tous les billets, tris par identifiant dcroissant


function getBillets() {
$bdd = getBdd();
$billets = $bdd->query('select BIL_ID as id, BIL_DATE as date,'
. ' BIL_TITRE as titre, BIL_CONTENU as contenu from T_BILLET'
. ' order by BIL_ID desc');
return $billets;
}

// 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;

- 12 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Modele.php
}

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.

Modele.php
...
$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 :

index.php
<?php

require 'Modele.php';

try {
$billets = getBillets();
require 'vueAccueil.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
vueErreur.php ddie leur affichage.

vueErreur.php
<?php $titre = 'Mon Blog'; ?>

<?php ob_start() ?>


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

<?php require 'gabarit.php'; ?>

On modifie ensuite le contrleur pour dclencher le rendu de cette vue en cas d'erreur.

index.php
<?php

require 'Modele.php';

try {
$billets = getBillets();

- 13 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

index.php
require 'vueAccueil.php';
}
catch (Exception $e) {
$msgErreur = $e->getMessage();
require 'vueErreur.php';
}

II-D - Bilan provisoire

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 fonctionnalits 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

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-1 - Prise en compte du nouveau besoin

Commenons par ajouter dans notre modle (fichier Modele.php) les fonctions d'accs aux donnes dont nous
avons besoin.

Modele.php
...
// Renvoie les informations sur un billet
function getBillet($idBillet) {
$bdd = getBdd();
$billet = $bdd->prepare('select BIL_ID as id, BIL_DATE as date,'
. ' BIL_TITRE as titre, BIL_CONTENU as contenu from T_BILLET'
. ' where BIL_ID=?');
$billet->execute(array($idBillet));
if ($billet->rowCount() == 1)
return $billet->fetch(); // Accs la premire ligne de rsultat
else
throw new Exception("Aucun billet ne correspond l'identifiant '$idBillet'");
}

// Renvoie la liste des commentaires associs un billet


function getCommentaires($idBillet) {
$bdd = getBdd();
$commentaires = $bdd->prepare('select COM_ID as id, COM_DATE as date,'
. ' COM_AUTEUR as auteur, COM_CONTENU as contenu from T_COMMENTAIRE'
. ' where BIL_ID=?');
$commentaires->execute(array($idBillet));
return $commentaires;

- 14 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Modele.php
}

Nous crons ensuite une nouvelle vue vueBillet.php dont le rle est d'afficher les informations demandes.

vueBillet.php
<?php $titre = "Mon Blog - " . $billet['titre']; ?>

<?php ob_start(); ?>


<article>
<header>
<h1 class="titreBillet"><?= $billet['titre'] ?></h1>
<time><?= $billet['date'] ?></time>
</header>
<p><?= $billet['contenu'] ?></p>
</article>
<hr />
<header>
<h1 id="titreReponses">Rponses <?= $billet['titre'] ?></h1>
</header>
<?php foreach ($commentaires as $commentaire): ?>
<p><?= $commentaire['auteur'] ?> dit :</p>
<p><?= $commentaire['contenu'] ?></p>
<?php endforeach; ?>
<?php $contenu = ob_get_clean(); ?>

<?php require '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>.

billet.php
<?php

require 'Modele.php';

try {
if (isset($_GET['id'])) {
// intval 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 'vueBillet.php';
}
else
throw new Exception("Identifiant de billet incorrect");
}
else
throw new Exception("Aucun identifiant de billet");
}
catch (Exception $e) {
$msgErreur = $e->getMessage();
require 'vueErreur.php';
}

Il faut galement modifier la vue vueAccueil.php afin d'ajouter un lien vers la page billet.php sur le titre du billet.

vueAccueil.php
...
<header>
<a href="<?= "billet.php?id=" . $billet['id'] ?>">
<h1 class="titreBillet"><?= $billet['titre'] ?></h1>

- 15 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

vueAccueil.php
</a>
<time><?= $billet['date'] ?></time>
</header>
...

Pour finir, on enrichit la feuille de style CSS afin de conserver une prsentation harmonieuse.

style.css
...

#titreReponses {
font-size : 100%;
}

II-E-2 - Affichage obtenu

Le rsultat obtenu est le suivant :

- 16 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Vous trouverez les fichiers source associs l'adresse https://github.com/bpesquet/MonBlog/tree/mvc-simple.

III - Amlioration de l'architecture MVC

III-A - Rappels sur l'architecture actuelle

Pour l'heure, notre blog d'exemple possde la structure suivante :

Rappelons les rles de chaque lment :

Modele.php reprsente la partie Modle (accs aux donnes) ;


vueAccueil.php, vueBillet.php et vueErreur.php constituent la partie Vue (affichage l'utilisateur). Ces
pages utilisent la page gabarit.php (template de mise en forme commune) ;
index.php et billet.php correspondent la partie Contrleur (gestion des requtes entrantes).

III-B - Mise en uvre d'un contrleur frontal (front controller)

L'architecture actuelle, base sur n contrleurs indpendants, 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 billet , le contrleur principal dclenchera l'affichage d'un billet ;

- 17 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

si action n'est pas valoris, le contrleur dclenchera l'affichage de la liste des billets (action par dfaut).

Toutes les actions ralisables sont rassembles sous la forme de fonctions dans le fichier Controleur.php.

Controleur.php
<?php

require 'Modele.php';

// Affiche la liste de tous les billets du blog


function accueil() {
$billets = getBillets();
require 'vueAccueil.php';
}

// Affiche les dtails sur un billet


function billet($idBillet) {
$billet = getBillet($idBillet);
$commentaires = getCommentaires($idBillet);
require 'vueBillet.php';
}

// Affiche une erreur


function erreur($msgErreur) {
require 'vueErreur.php';
}

L'action raliser est dtermine par le fichier index.php de notre blog, rcrit sous la forme d'un contrleur frontal.

index.php
<?php

require('Controleur.php');

try {
if (isset($_GET['action'])) {
if ($_GET['action'] == 'billet') {
if (isset($_GET['id'])) {
$idBillet = intval($_GET['id']);
if ($idBillet != 0)
billet($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 {
accueil(); // action par dfaut
}
}
catch (Exception $e) {
erreur($e->getMessage());
}

Remarque : l'ancien fichier contrleur billet.php est dsormais inutile et peut tre supprim.

Enfin, le lien vers un billet doit tre modifi afin de reflter la nouvelle architecture.

vueAccueil.php
...
<a href="<?= "index.php?action=billet&id=" . $billet['id'] ?>">
<h1 class="titreBillet"><?= $billet['titre'] ?></h1>

- 18 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

vueAccueil.php
</a>
...

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 :

1 Le contrleur frontal analyse la requte entrante et vrifie les paramtres fournis ;


2 Il slectionne et appelle l'action raliser en lui passant les paramtres ncessaires ;
3 Si la requte est incohrente, 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 dans les URL. Cette encapsulation facilite les rorganisations internes, comme celle que nous allons
entreprendre maintenant.

III-C - Rorganisation des fichiers source

Par souci de simplicit, nous avons jusqu' prsent stock tous nos fichiers source 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 vueAccueil.php, vueBillet.php et vueErreur.php, ainsi que la page
commune gabarit.php ;
le rpertoire Controleur contiendra le fichier des actions Controleur.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 suivante :

Il est videmment ncessaire de mettre jour les inclusions et les liens pour prendre en compte la nouvelle
organisation des fichiers source. 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.

Vous trouverez les fichiers source associs l'adresse https://github.com/bpesquet/MonBlog/tree/mvc-


procedural.

- 19 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

III-D - 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.
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.

IV - Passage une architecture MVC oriente objet

IV-A - Aperu du modle objet de PHP

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.

Voici la dfinition PHP d'une classe CompteBancaire.

CompteBancaire.php
<?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;
}
}

- 20 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

En complment, voici la dfinition d'une classe CompteEpargne.

CompteEpargne.php
<?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\'interet est de ' . $this->tauxInteret * 100 . '%.';
}
}

Voici un exemple d'utilisation de ces deux classes.

poo.php
<?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 'Interets pour ce compte : ' . $comptePaul->calculerInterets() .
' ' . $comptePaul->getDevise() . '<br />';
$comptePaul->calculerInterets(true);
echo $comptePaul . '<br />';

Enfin, voici le rsultat de son excution.

- 21 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

IV-A-1 - 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.

IV-A-2 - 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.

IV-B - 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.

IV-B-1 - Rappels sur l'architecture actuelle

Pour mmoire, voici la dfinition actuelle de notre partie Modle puis de notre partie Contrleur.

Modele.php
<?php

// Renvoie la liste des billets du blog


function getBillets() {
$bdd = getBdd();
$billets = $bdd->query('select BIL_ID as id, BIL_DATE as date,'
. ' BIL_TITRE as titre, BIL_CONTENU as contenu from T_BILLET'
. ' order by BIL_ID desc');
return $billets;
}

// Renvoie les informations sur un billet

- 22 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Modele.php
function getBillet($idBillet) {
$bdd = getBdd();
$billet = $bdd->prepare('select BIL_ID as id, BIL_DATE as date,'
. ' BIL_TITRE as titre, BIL_CONTENU as contenu from T_BILLET'
. ' where BIL_ID=?');
$billet->execute(array($idBillet));
if ($billet->rowCount() == 1)
return $billet->fetch(); // Accs la premire ligne de rsultat
else
throw new Exception("Aucun billet ne correspond l'identifiant '$idBillet'");
}

// Renvoie la liste des commentaires associs un billet


function getCommentaires($idBillet) {
$bdd = getBdd();
$commentaires = $bdd->prepare('select COM_ID as id, COM_DATE as date,'
. ' COM_AUTEUR as auteur, COM_CONTENU as contenu from T_COMMENTAIRE'
. ' where BIL_ID=?');
$commentaires->execute(array($idBillet));
return $commentaires;
}

// 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;
}

On rappelle que le rle de la partie Modle est de rassembler la logique mtier et l'accs aux donnes.

Controleur.php
<?php

require 'Modele/Modele.php';

// Affiche la liste de tous les billets du blog


function accueil() {
$billets = getBillets();
require 'Vue/vueAccueil.php';
}

// Affiche les dtails sur un billet


function billet($idBillet) {
$billet = getBillet($idBillet);
$commentaires = getCommentaires($idBillet);
require 'Vue/vueBillet.php';
}

// Affiche une erreur


function erreur($msgErreur) {
require 'Vue/vueErreur.php';
}

La partie Contrleur, quant elle, gre la dynamique d'une application MVC.

IV-B-2 - Passage un Modle orient objet

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 et Commentaire.

- 23 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

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 de la classe Modele.

Modele.php
<?php

class Modele {

// Renvoie la liste des billets du blog


public function getBillets() {
$bdd = $this->getBdd();
$billets = $bdd->query('select BIL_ID as id, BIL_DATE as date,'
. ' BIL_TITRE as titre, BIL_CONTENU as contenu from T_BILLET'
. ' order by BIL_ID desc');
return $billets;
}

// Renvoie les informations sur un billet


public function getBillet($idBillet) {
$bdd = $this->getBdd();
$billet = $bdd->prepare('select BIL_ID as id, BIL_DATE as date,'
. ' BIL_TITRE as titre, BIL_CONTENU as contenu from T_BILLET'
. ' where BIL_ID=?');
$billet->execute(array($idBillet));
if ($billet->rowCount() == 1)
return $billet->fetch(); // Accs la premire ligne de rsultat
else
throw new Exception("Aucun billet ne correspond l'identifiant '$idBillet'");
}

// Renvoie la liste des commentaires associs un billet


public function getCommentaires($idBillet) {
$bdd = $this->getBdd();
$commentaires = $bdd->prepare('select COM_ID as id, COM_DATE as date,'
. ' COM_AUTEUR as auteur, COM_CONTENU as contenu from T_COMMENTAIRE'
. ' where BIL_ID=?');
$commentaires->execute(array($idBillet));
return $commentaires;
}

// 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 (mot-
cl private) 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, qui recommande de regrouper des
lments (par exemple des mthodes) en fonction de leur problmatique.

- 24 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

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

require_once 'Modele/Modele.php';

class Billet extends Modele {

// Renvoie la liste des billets du blog


public function getBillets() {
$sql = 'select BIL_ID as id, BIL_DATE as date,'
. ' BIL_TITRE as titre, BIL_CONTENU as contenu from T_BILLET'
. ' order by BIL_ID desc';
$billets = $this->executerRequete($sql);
return $billets;
}

// Renvoie les informations sur un billet


public function getBillet($idBillet) {
$sql = 'select BIL_ID as id, BIL_DATE as date,'
. ' BIL_TITRE as titre, BIL_CONTENU as contenu from T_BILLET'
. ' where BIL_ID=?';
$billet = $this->executerRequete($sql, array($idBillet));
if ($billet->rowCount() == 1)
return $billet->fetch(); // Accs la premire ligne de rsultat
else
throw new Exception("Aucun billet ne correspond l'identifiant '$idBillet'");
}
}

La classe Modele est dsormais abstraite (mot-cl abstract) et fournit ses classes drives un service d'excution
d'une requte SQL :

Modele.php
<?php

abstract class Modele {

// Objet PDO d'accs la BD


private $bdd;

// Excute une requte SQL ventuellement paramtre


protected function executerRequete($sql, $params = null) {
if ($params == null) {
$resultat = $this->getBdd()->query($sql); // excution directe
}
else {
$resultat = $this->getBdd()->prepare($sql); // requte prpare
$resultat->execute($params);
}
return $resultat;
}

// Renvoie un objet de connexion la BD en initialisant la connexion au besoin


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

- 25 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

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.

On peut crire une classe Commentaire sur le mme modle que la classe Billet.

Commentaire.php
<?php

require_once 'Modele/Modele.php';

class Commentaire extends Modele {

// Renvoie la liste des commentaires associs un billet


public function getCommentaires($idBillet) {
$sql = 'select COM_ID as id, COM_DATE as date,'
. ' COM_AUTEUR as auteur, COM_CONTENU as contenu from T_COMMENTAIRE'
. ' where BIL_ID=?';
$commentaires = $this->executerRequete($sql, array($idBillet));
return $commentaires;

}
}

prsent, l'architecture de la partie Modele tire parti des avantages de la POO (encapsulation, 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 (ici : Auteur) qui s'appuiera sur les services communs fournis
par sa superclasse.

IV-B-3 - Passage une Vue oriente objet

Pour l'instant, nos vues sont des fichiers HTML/PHP qui exploitent des variables PHP contenant les donnes
dynamiques. Elles utilisent un gabarit commun regroupant les lments d'affichage communs. Voici par exemple la
vue d'affichage d'un billet et de ses commentaires.

vueBillet.php
<?php $titre = "Mon Blog - " . $billet['titre']; ?>

<?php ob_start(); ?>


<article>
<header>
<h1 class="titreBillet"><?= $billet['titre'] ?></h1>
<time><?= $billet['date'] ?></time>
</header>
<p><?= $billet['contenu'] ?></p>
</article>
<hr />
<header>
<h1 id="titreReponses">Rponses <?= $billet['titre'] ?></h1>
</header>
<?php foreach ($commentaires as $commentaire): ?>
<p><?= $commentaire['auteur'] ?> dit :</p>
<p><?= $commentaire['contenu'] ?></p>
<?php endforeach; ?>
<?php $contenu = ob_get_clean(); ?>

<?php require 'gabarit.php'; ?>

Le gabarit centralise les lments d'affichage communs et utilise les variables $titre et $contenu pour intgrer les
lments spcifiques.

gabarit.php
<!doctype html>

- 26 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

gabarit.php
<html lang="fr">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="Contenu/style.css" />
<title><?= $titre ?></title>
</head>
<body>
<div id="global">
<header>
<a href="index.php"><h1 id="titreBlog">Mon Blog</h1></a>
<p>Je vous souhaite la bienvenue sur ce modeste blog.</p>
</header>
<div id="contenu">
<?= $contenu ?>
</div> <!-- #contenu -->
<footer id="piedBlog">
Blog ralis avec PHP, HTML5 et CSS.
</footer>
</div> <!-- #global -->
</body>
</html>

Cette approche simple souffre de plusieurs limitations :

les appels aux fonctions PHP ob_start et ob_get_clean sont dupliqus ;


la gnration des fichiers vue (y compris dans le contrleur) utilise directement la fonction PHP require, sans
protection contre une ventuelle absence du fichier demand.

Nous allons crer une classe Vue dont le rle sera de grer la gnration des vues.

Vue.php
class Vue {

// Nom du fichier associ la vue


private $fichier;
// Titre de la vue (dfini dans le fichier vue)
private $titre;

public function __construct($action) {


// Dtermination du nom du fichier vue partir de l'action
$this->fichier = "Vue/vue" . $action . ".php";
}

// Gnre et affiche la vue


public function generer($donnees) {
// Gnration de la partie spcifique de la vue
$contenu = $this->genererFichier($this->fichier, $donnees);
// Gnration du gabarit commun utilisant la partie spcifique
$vue = $this->genererFichier('Vue/gabarit.php',
array('titre' => $this->titre, 'contenu' => $contenu));
// Renvoi de la vue au navigateur
echo $vue;
}

// Gnre un fichier vue et renvoie le rsultat produit


private function genererFichier($fichier, $donnees) {
if (file_exists($fichier)) {
// Rend les lments du tableau $donnees accessibles dans la vue
extract($donnees);
// Dmarrage de la temporisation de sortie
ob_start();
// Inclut le fichier vue
// Son rsultat est plac dans le tampon de sortie
require $fichier;
// Arrt de la temporisation et renvoi du tampon de sortie
return ob_get_clean();
}

- 27 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Vue.php
else {
throw new Exception("Fichier '$fichier' introuvable");
}
}
}

Le constructeur de Vue prend en paramtre une action, qui dtermine le fichier vue utilis. Sa mthode generer()
gnre d'abord la partie spcifique de la vue afin de dfinir son titre (attribut $titre) et son contenu (variable locale
$contenu). Ensuite, le gabarit est gnr en y incluant les lments spcifiques de la vue. Sa mthode interne
genererFichier() encapsule l'utilisation de require et permet en outre de vrifier l'existence du fichier vue afficher.
Elle utilise la fonction extract pour que la vue puisse accder aux variables PHP requises, rassembles dans le
tableau associatif $donnees.

Il n'est pas ncessaire de modifier le fichier gabarit. Par contre, les fichiers de chaque vue doivent tre modifis pour
dfinir $this->titre et supprimer les appels aux fonctions PHP de temporisation. Voici par exemple la nouvelle vue
d'accueil.

vueAccueil.php
<?php $this->titre = "Mon Blog"; ?>

<?php foreach ($billets as $billet):


?>
<article>
<header>
<a href="<?= "index.php?action=billet&id=" . $billet['id'] ?>">
<h1 class="titreBillet"><?= $billet['titre'] ?></h1>
</a>
<time><?= $billet['date'] ?></time>
</header>
<p><?= $billet['contenu'] ?></p>
</article>
<hr />
<?php endforeach; ?>

L'affichage d'une vue se fera dsormais en instanciant un objet de la classe Vue, puis en appelant sa mthode
generer().

IV-B-4 - Passage un Contrleur orient objet

Notre partie Contrleur actuelle se compose d'une srie d'actions crites sous la forme de fonctions et du contrleur
frontal index.php.

Controleur.php
<?php

require 'Modele/Modele.php';

// Affiche la liste de tous les billets du blog


function accueil() {
$billets = getBillets();
require 'Vue/vueAccueil.php';
}

// Affiche les dtails sur un billet


function billet($idBillet) {
$billet = getBillet($idBillet);
$commentaires = getCommentaires($idBillet);
require 'Vue/vueBillet.php';
}

// Affiche une erreur


function erreur($msgErreur) {
require 'Vue/vueErreur.php';

- 28 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Controleur.php
}

index.php
<?php

require 'Controleur/Controleur.php';

try {
if (isset($_GET['action'])) {
if ($_GET['action'] == 'billet') {
if (isset($_GET['id'])) {
$idBillet = intval($_GET['id']);
if ($idBillet != 0) {
billet($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 { // aucune action dfinie : affichage de l'accueil
accueil();
}
}
catch (Exception $e) {
erreur($e->getMessage());
}

Toute volution du site Web va faire augmenter le nombre d'actions possibles, jusqu' rendre les fichiers
Controleur.php et index.php difficiles lire et maintenir.

Une solution plus modulaire consiste rpartir les actions dans plusieurs classes contrleur, en fonction du contexte
associ aux actions. Ici, nous pourrions crer une classe ControleurAccueil pour grer l'accueil et une classe
ControleurBillet pour grer l'affichage d'un billet.

Bien entendu, les nouveaux contrleurs utilisent les services des classes des parties Modle et Vue dfinies
prcdemment.

ControleurAccueil.php
<?php

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

class ControleurAccueil {

private $billet;

public function __construct() {


$this->billet = new Billet();
}

// Affiche la liste de tous les billets du blog


public function accueil() {
$billets = $this->billet->getBillets();
$vue = new Vue("Accueil");
$vue->generer(array('billets' => $billets));
}
}

- 29 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

ControleurBillet.php
<?php

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

class ControleurBillet {

private $billet;
private $commentaire;

public function __construct() {


$this->billet = new Billet();
$this->commentaire = new Commentaire();
}

// Affiche les dtails sur un billet


public function billet($idBillet) {
$billet = $this->billet->getBillet($idBillet);
$commentaires = $this->commentaire->getCommentaires($idBillet);
$vue = new Vue("Billet");
$vue->generer(array('billet' => $billet, 'commentaires' => $commentaires));
}
}

Chaque classe contrleur instancie les classes modle requises, puis utilise leurs mthodes pour rcuprer les
donnes ncessaires aux vues. La mthode generer de la classe Vue dfinie plus haut est utilise en lui passant en
paramtre 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.

Quant au contrleur frontal, on peut le modliser l'aide d'une classe Routeur dont la mthode principale analyse
la requte entrante pour dterminer l'action entreprendre. On parle souvent de routage de la requte.

Routeur.php
require_once 'Controleur/ControleurAccueil.php';
require_once 'Controleur/ControleurBillet.php';
require_once 'Vue/Vue.php';

class Routeur {

private $ctrlAccueil;
private $ctrlBillet;

public function __construct() {


$this->ctrlAccueil = new ControleurAccueil();
$this->ctrlBillet = new ControleurBillet();
}

// Traite une requte entrante


public function routerRequete() {
try {
if (isset($_GET['action'])) {
if ($_GET['action'] == 'billet') {
if (isset($_GET['id'])) {
$idBillet = intval($_GET['id']);
if ($idBillet != 0) {
$this->ctrlBillet->billet($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");
}

- 30 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Routeur.php
else { // aucune action dfinie : affichage de l'accueil
$this->ctrlAccueil->accueil();
}
}
catch (Exception $e) {
$this->erreur($e->getMessage());
}
}

// Affiche une erreur


private function erreur($msgErreur) {
$vue = new Vue("Erreur");
$vue->generer(array('msgErreur' => $msgErreur));
}
}

Le fichier principal index.php est maintenant simplifi l'extrme. Il se contente d'instancier le routeur puis de lui
faire router la requte.

index.php
<?php

require 'Controleur/Routeur.php';

$routeur = new Routeur();


$routeur->routerRequete();

IV-C - Bilan provisoire

La structure actuelle du site est prsente ci-dessous. Elle est videmment beaucoup plus complexe qu'au dpart.
Cette complexit est le prix payer pour disposer de bases robustes qui faciliteront la maintenance et les volutions
futures.

L'ajout de nouvelles fonctionnalits se fait prsent en trois tapes :

- 31 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

ajout ou enrichissement de la classe modle associe ;


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.

IV-D - Application : ajout d'un commentaire

IV-D-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.

- 32 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

IV-D-2 - Prise en compte du nouveau besoin

On commence par ajouter la classe Commentaire une mthode permettant d'insrer un nouveau commentaire
dans la BD.

Commentaire.php
...
// Ajoute un commentaire dans la base
public function ajouterCommentaire($auteur, $contenu, $idBillet) {
$sql = 'insert into T_COMMENTAIRE(COM_DATE, COM_AUTEUR, COM_CONTENU, BIL_ID)'
. ' values(?, ?, ?, ?)';

- 33 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Commentaire.php
$date = date(DATE_W3C); // Rcupre la date courante
$this->executerRequete($sql, array($date, $auteur, $contenu, $idBillet));
}
...

On ajoute ensuite la vue d'un billet le formulaire HTML ncessaire pour saisir un commentaire.

vueBillet.php
...
<form method="post" action="index.php?action=commenter">
<input id="auteur" name="auteur" type="text" placeholder="Votre pseudo"
required /><br />
<textarea id="txtCommentaire" name="contenu" rows="4"
placeholder="Votre commentaire" required></textarea><br />
<input type="hidden" name="id" value="<?= $billet['id'] ?>" />
<input type="submit" value="Commenter" />
</form>

Remarque : l'action associe la soumission du formulaire est nomme commenter.

Au passage, on met jour la feuille de style afin de dfinir la taille par dfaut de la zone de texte du commentaire.

style.css
...

#txtCommentaire {
width: 50%;
}

Il faut galement ajouter au contrleur une mthode associe cette action.

ControleurBillet.php
...
// Ajoute un commentaire un billet
public function commenter($auteur, $contenu, $idBillet) {
// Sauvegarde du commentaire
$this->commentaire->ajouterCommentaire($auteur, $contenu, $idBillet);
// Actualisation de l'affichage du billet
$this->billet($idBillet);
}
...

Cette action consiste appeler un service du Modle, puis excuter l'action d'affichage du billet afin d'obtenir un
rsultat actualis.

Enfin, on met jour le routeur afin de router une requte d'ajout de commentaire vers la nouvelle action. Au passage,
on en profite pour simplifier la mthode de routage (qui tend devenir complexe) en faisant appel une mthode
prive de recherche d'un paramtre dans un tableau. Cette mthode permet de rechercher un paramtre dans $_GET
ou $_POST en fonction du besoin.

Routeur.php
...
// Recherche un paramtre dans un tableau
private function getParametre($tableau, $nom) {
if (isset($tableau[$nom])) {
return $tableau[$nom];
}
else
throw new Exception("Paramtre '$nom' absent");
}
...

- 34 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

La mthode de routage d'une requte est mise jour.

Routeur.php
...
if (isset($_GET['action'])) {
if ($_GET['action'] == 'billet') {
$idBillet = intval($this->getParametre($_GET, 'id'));
if ($idBillet != 0) {
$this->ctrlBillet->billet($idBillet);
}
else
throw new Exception("Identifiant de billet non valide");
}
else if ($_GET['action'] == 'commenter') {
$auteur = $this->getParametre($_POST, 'auteur');
$contenu = $this->getParametre($_POST, 'contenu');
$idBillet = $this->getParametre($_POST, 'id');
$this->ctrlBillet->commenter($auteur, $contenu, $idBillet);
}
else
throw new Exception("Action non valide");
}
else { // aucune action dfinie : affichage de l'accueil
$this->ctrlAccueil->accueil();
}
...

Vous trouverez les fichiers source associs l'adresse https://github.com/bpesquet/MonBlog/tree/mvc-objet.

V - Construction d'un framework MVC

V-A - O aller maintenant ?

Nous avons parcouru beaucoup de chemin depuis le dbut de cet article. 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, d'un routeur orient objet, ainsi que de classes abstraites fournissant des services communs. Pourquoi ne
pas aller au bout de la logique objet en isolant ces services communs au sein d'un framework dont les bases sont
dj construites ?

V-A-1 - Intrt d'un framework

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.

Notre petit framework n'atteindra videmment pas la richesse fonctionnelle et le niveau de qualit des exemples
prcdents. Son but est d'illustrer de l'intrieur et aussi simplement que possible les bases du fonctionnement
d'un framework PHP.

V-A-2 - Limites de l'architecture actuelle

Nous allons profiter de la mise en place du framework pour remdier certains points faibles de l'architecture actuelle :

les paramtres d'accs la base de donnes (serveur, nom de la BD, identifiant de connexion, mot de passe)
ne sont pas configurables ;

- 35 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

chaque classe modle instancie un objet PDO d'accs la BD, ce qui est sous-optimal du point de vue des
performances ;
les URL actuelles, du type monsite.fr/index.php?action=yyy&id=zzz, sont moins lisibles que des URL du
type monsite.fr/action/id ;
le routage de la requte (choix de l'action excuter) est fait manuellement par le routeur ;
les paramtres des requtes ne sont pas filtrs en entre ;
les donnes insres dans les vues ne sont pas nettoyes, ce qui accrot le risque de failles XSS.

V-B - tapes de construction du framework

Ce paragraphe constitue la partie la plus complexe de l'article. Il fait appel des concepts
avancs du dveloppement Web et de la POO.

Toutefois, il n'est pas ncessaire de comprendre tout son dtail pour pouvoir utiliser le
framework ainsi construit.

V-B-1 - Accs gnrique aux donnes

Commenons la construction du framework par la partie Modle. Les classes Billet et Commentaire sont directement
lies notre blog d'exemple et ne peuvent pas tre rutilises dans un autre contexte. En revanche, la classe abstraite
Modele fournit des services totalement indpendants du schma relationnel. On peut envisager de l'intgrer notre
framework.

Avant cela, il nous reste un problme rsoudre. Comme nous l'avons dit plus haut, l'accs la base de donnes
dans la classe abstraite Modele n'est pas paramtrable : les valeurs de la base MonBlog sont en dur dans son
code source.

Modele.php
<?php

abstract class Modele {

// Objet PDO d'accs la BD


private $bdd;

// Excute une requte SQL ventuellement paramtre


protected function executerRequete($sql, $params = null) {
if ($params == null) {
$resultat = $this->getBdd()->query($sql); // excution directe
}
else {
$resultat = $this->getBdd()->prepare($sql); // requte prpare
$resultat->execute($params);
}
return $resultat;
}

// Renvoie un objet de connexion la BD en initialisant la connexion au besoin


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

- 36 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Pour que cette classe soit totalement gnrique et donc intgrable un framework, il faudrait pouvoir dfinir les
paramtres de connexion la BD sans modifier son code source. Pour cela, nous allons crer un nouveau composant
dont le rle sera de grer la configuration du site. Ce composant prend la forme d'une classe appele logiquement
Configuration.

Configuration.php
<?php

class Configuration {

private static $parametres;

// Renvoie la valeur d'un paramtre de configuration


public static function get($nom, $valeurParDefaut = null) {
if (isset(self::getParametres()[$nom])) {
$valeur = self::getParametres()[$nom];
}
else {
$valeur = $valeurParDefaut;
}
return $valeur;
}

// Renvoie le tableau des paramtres en le chargeant au besoin


private static function getParametres() {
if (self::$parametres == null) {
$cheminFichier = "Config/prod.ini";
if (!file_exists($cheminFichier)) {
$cheminFichier = "Config/dev.ini";
}
if (!file_exists($cheminFichier)) {
throw new Exception("Aucun fichier de configuration trouv");
}
else {
self::$parametres = parse_ini_file($cheminFichier);
}
}
return self::$parametres;
}
}

Cette classe encapsule un tableau associatif cls/valeurs (attribut $parametres) stockant les valeurs des paramtres
de configuration. Ce tableau est statique (un seul exemplaire par classe), ce qui permet de l'utiliser sans instancier
d'objet Configuration.

La classe dispose d'une mthode statique publique nomme get() qui permet de rechercher la valeur d'un paramtre
partir de son nom. Si le paramtre en question est trouv dans le tableau associatif, sa valeur est renvoye. Sinon,
une valeur par dfaut est renvoye. On rencontre au passage le mot-cl PHP self qui permet de faire rfrence
un membre statique.

Enfin, la mthode statique prive getParametres() effectue le chargement tardif du fichier contenant les paramtres
de configuration. Afin de faire cohabiter sur un mme serveur une configuration de dveloppement et une
configuration de production, deux fichiers sont recherchs dans le rpertoire Config du site : dev.ini (cherch en
premier) et prod.ini. La lecture du fichier de configuration utilise la fonction PHP parse_ini_file(). Celle-ci instancie
et renvoie un tableau associatif immdiatement attribu l'attribut $parametres.

Grce cette classe, on peut externaliser la configuration d'un site en dehors de son code source. Voici par exemple
le fichier de configuration correspondant notre blog d'exemple.

dev.ini
; Configuration pour le dveloppement

[BD]
dsn = 'mysql:host=localhost;dbname=monblog;charset=utf8'

- 37 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

dev.ini
login = root
mdp =

Un changement de paramtres de connexion, par exemple pour employer un autre utilisateur que root, ncessite
uniquement une mise jour de ce fichier de configuration.

De plus, nous pouvons rendre la classe abstraite Modele totalement gnrique et rutilisable.

Modele.php
<?php

require_once 'Configuration.php';

/**
* Classe abstraite Modle.
* Centralise les services d'accs une base de donnes.
* Utilise l'API PDO de PHP
*
* @version 1.0
* @author Baptiste Pesquet
*/
abstract class Modele {

/** Objet PDO d'accs la BD


Statique donc partag par toutes les instances des classes drives */
private static $bdd;

/**
* Excute une requte SQL
*
* @param string $sql Requte SQL
* @param array $params Paramtres de la requte
* @return PDOStatement Rsultats de la requte
*/
protected function executerRequete($sql, $params = null) {
if ($params == null) {
$resultat = self::getBdd()->query($sql); // excution directe
}
else {
$resultat = self::getBdd()->prepare($sql); // requte prpare
$resultat->execute($params);
}
return $resultat;
}

/**
* Renvoie un objet de connexion la BDD en initialisant la connexion au besoin
*
* @return PDO Objet PDO de connexion la BDD
*/
private static function getBdd() {
if (self::$bdd === null) {
// Rcupration des paramtres de configuration BD
$dsn = Configuration::get("dsn");
$login = Configuration::get("login");
$mdp = Configuration::get("mdp");
// Cration de la connexion
self::$bdd = new PDO($dsn, $login, $mdp,
array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
}
return self::$bdd;
}

- 38 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Au passage, on transforme l'attribut $bdd en attribut de classe (mot-cl static) afin de ne crer qu'une seule instance
de la classe PDO partage par toutes les classes drives de Modele. Ainsi, l'opration de connexion la base de
donnes ne sera ralise qu'une seule fois.

On remarque que la syntaxe d'appel d'une mthode de classe (ici Configuration::get()) utilise la notation :: comme
en C++.

V-B-2 - Automatisation du routage de la requte

prsent, intressons-nous la partie Contrleur de notre exemple. Les actions dfinies (affichage des billets, d'un
billet, commentaire) sont spcifiques notre contexte. En revanche, le routage d'une requte (choix de l'action
excuter en fonction des paramtres de la requte) pourrait tre rendu gnrique et intgr au framework.

Pour atteindre cet objectif complexe, nous allons commencer par ajouter une classe Requete dont le rle est de
modliser une requte. Pour l'instant, le seul attribut de cette classe est un tableau rassemblant les paramtres de
la requte. Par la suite, on pourrait y ajouter d'autres informations sur la requte : en-ttes HTTP, session, etc.

Requete.php
<?php

class Requete {

// paramtres de la requte
private $parametres;

public function __construct($parametres) {


$this->parametres = $parametres;
}

// Renvoie vrai si le paramtre existe dans la requte


public function existeParametre($nom) {
return (isset($this->parametres[$nom]) && $this->parametres[$nom] != "");
}

// Renvoie la valeur du paramtre demand


// Lve une exception si le paramtre est introuvable
public function getParametre($nom) {
if ($this->existeParametre($nom)) {
return $this->parametres[$nom];
}
else
throw new Exception("Paramtre '$nom' absent de la requte");
}
}

Au dbut du routage, un objet Requete sera instanci afin de stocker les paramtres de la requte reue.

Le routage d'une requte entrante consiste analyser cette requte afin d'en dduire le contrleur utiliser et l'action
(mthode du contrleur) appeler. Ce travail est ralis par la classe Routeur, dont voici la version actuelle.

Routeur.php
<?php

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

require_once 'Vue/Vue.php';

class Routeur {

private $ctrlAccueil;
private $ctrlBillet;

- 39 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Routeur.php
public function __construct() {
$this->ctrlAccueil = new ControleurAccueil();
$this->ctrlBillet = new ControleurBillet();
}

// Route une requte entrante : excute l'action associe


public function routerRequete() {
try {
if (isset($_GET['action'])) {
if ($_GET['action'] == 'billet') {
$idBillet = intval($this->getParametre($_GET, 'id'));
if ($idBillet != 0) {
$this->ctrlBillet->billet($idBillet);
}
else
throw new Exception("Identifiant de billet non valide");
}
else if ($_GET['action'] == 'commenter') {
$auteur = $this->getParametre($_POST, 'auteur');
$contenu = $this->getParametre($_POST, 'contenu');
$idBillet = $this->getParametre($_POST, 'id');
$this->ctrlBillet->commenter($auteur, $contenu, $idBillet);
}
else
throw new Exception("Action non valide");
}
else { // aucune action dfinie : affichage de l'accueil
$this->ctrlAccueil->accueil();
}
}
catch (Exception $e) {
$this->erreur($e->getMessage());
}
}

// Affiche une erreur


private function erreur($msgErreur) {
$vue = new Vue("Erreur");
$vue->generer(array('msgErreur' => $msgErreur));
}

// Recherche un paramtre dans un tableau


private function getParametre($tableau, $nom) {
if (isset($tableau[$nom])) {
return $tableau[$nom];
}
else
throw new Exception("Paramtre '$nom' absent");
}
}

Pour l'instant, le choix de l'action s'effectue par comparaison du paramtre action de la requte avec diffrentes
valeurs prdfinies ( billet et commenter ). Cette opration est manuelle et devient complexe avec
l'augmentation du nombre des actions possibles.

Un routage gnrique consisterait dduire automatiquement le constructeur et la mthode d'action en fonction de


la requte. Pour atteindre cet objectif, nous allons enrichir les URL de notre site. Jusqu' prsent, elles taient de la
forme index.php?action=yyy&id=zzz. Nous allons ajouter un troisime paramtre identifiant le contrleur utiliser.
Nos URL sont maintenant de la forme index.php?controleur=xxx&action=yyy&id=zzz.

On peut prsent modifier en profondeur le code du routeur afin de rendre le routage automatique et donc gnrique.

Routeur.php
<?php

require_once 'Requete.php';

- 40 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Routeur.php
require_once 'Vue.php';

class Routeur {

// Route une requte entrante : excute l'action associe


public function routerRequete() {
try {
// Fusion des paramtres GET et POST de la requte
$requete = new Requete(array_merge($_GET, $_POST));

$controleur = $this->creerControleur($requete);
$action = $this->creerAction($requete);

$controleur->executerAction($action);
}
catch (Exception $e) {
$this->gererErreur($e);
}
}

// Cre le contrleur appropri en fonction de la requte reue


private function creerControleur(Requete $requete) {
$controleur = "Accueil"; // Contrleur par dfaut
if ($requete->existeParametre('controleur')) {
$controleur = $requete->getParametre('controleur');
// Premire lettre en majuscule
$controleur = ucfirst(strtolower($controleur));
}
// Cration du nom du fichier du contrleur
$classeControleur = "Controleur" . $controleur;
$fichierControleur = "Controleur/" . $classeControleur . ".php";
if (file_exists($fichierControleur)) {
// Instanciation du contrleur adapt la requte
require($fichierControleur);
$controleur = new $classeControleur();
$controleur->setRequete($requete);
return $controleur;
}
else
throw new Exception("Fichier '$fichierControleur' introuvable");
}

// Dtermine l'action excuter en fonction de la requte reue


private function creerAction(Requete $requete) {
$action = "index"; // Action par dfaut
if ($requete->existeParametre('action')) {
$action = $requete->getParametre('action');
}
return $action;
}

// Gre une erreur d'excution (exception)


private function gererErreur(Exception $exception) {
$vue = new Vue('erreur');
$vue->generer(array('msgErreur' => $exception->getMessage()));
}
}

La mthode principale routerRequete() de cette classe instancie un objet Requete en fusionnant les donnes des
variables superglobales $_GET et $_POST, afin de pouvoir analyser toute requte issue soit d'une commande HTTP
GET, soit d'une commande HTTP POST.

Ensuite, cette mthode fait appel deux mthodes internes afin d'instancier le contrleur appropri et d'excuter
l'action correspondant la requte reue.

La mthode creerControleur() rcupre le paramtre controleur de la requte reue et le concatne pour construire
le nom du fichier contrleur (celui qui contient la classe associe) et renvoyer une instance de la classe associe.

- 41 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

En l'absence de ce paramtre, elle cherche instancier la classe ControleurAccueil qui correspond au contrleur
par dfaut.

La mthode creerAction() rcupre le paramtre action de la requte reue et le renvoie. En l'absence de ce


paramtre, elle renvoie la valeur index qui correspond l'action par dfaut.

Cela n'est possible qu'en imposant tous les contrleurs des contraintes de nommage strictes : chaque contrleur doit
rsider dans le sous-rpertoire Controleur/ sous la forme d'un fichier dfinissant une classe nomme ControleurXXX
(XXX correspondant la valeur du paramtre controleur dans la requte). Le fichier doit porter le mme nom que
la classe.

Enfin, la mthode prive gererErreur() permet d'afficher la vue d'erreur.

Les plus attentifs d'entre vous auront remarqu que notre nouveau routeur fait appel aux mthodes setRequete() et
executerAction() de l'objet contrleur instanci. Il serait maladroit de dupliquer la dfinition de cette mthode dans
tous nos contrleurs. Nous allons donc dfinir une classe abstraite Controleur regroupant les services communs
aux contrleurs.

Controleur.php
<?php

require_once 'Requete.php';
require_once 'Vue.php';

abstract class Controleur {

// Action raliser
private $action;

// Requte entrante
protected $requete;

// Dfinit la requte entrante


public function setRequete(Requete $requete) {
$this->requete = $requete;
}

// Excute l'action raliser


public function executerAction($action) {
if (method_exists($this, $action)) {
$this->action = $action;
$this->{$this->action}();
}
else {
$classeControleur = get_class($this);
throw new Exception("Action '$action' non dfinie dans la classe $classeControleur");
}
}

// Mthode abstraite correspondant l'action par dfaut


// Oblige les classes drives implmenter cette action par dfaut
public abstract function index();

// Gnre la vue associe au contrleur courant


protected function genererVue($donneesVue = array()) {
// Dtermination du nom du fichier vue partir du nom du contrleur actuel
$classeControleur = get_class($this);
$controleur = str_replace("Controleur", "", $classeControleur);
// Instanciation et gnration de la vue
$vue = new Vue($this->action, $controleur);
$vue->generer($donneesVue);
}
}

- 42 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Cette classe a pour attributs l'action raliser et la requte. Sa mthode executerAction() met en uvre le concept
de rflexion : elle utilise les fonctions PHP method_exists et get_class afin de faire appel la mthode ayant pour
nom l'action raliser.

La mthode index() est abstraite. Cela signifie que tous nos contrleurs, qui hriteront de Controleur, devront
obligatoirement dfinir une mthode index() qui correspond l'action par dfaut (quand le paramtre action n'est
pas dfini dans la requte).

Enfin, la mthode genererVue() permet d'automatiser le lien entre contrleur et vue : les paramtres de cration
de la vue sont dduits du nom du contrleur et de l'action raliser. Au passage, le constructeur de la classe Vue
doit voluer.

Vue.php
...
public function __construct($action, $controleur = "") {
// Dtermination du nom du fichier vue partir de l'action et du constructeur
$fichier = "Vue/";
if ($controleur != "") {
$fichier = $fichier . $controleur . "/";
}
$this->fichier = $fichier . $action . ".php";
}
...

Ici encore, on impose une convention de nommage aux vues :

chaque vue doit rsider dans le sous-rpertoire Vue/ ;


dans ce rpertoire, chaque vue est stocke dans un sous-rpertoire portant le nom du contrleur associ la
vue ;
chaque fichier vue ne contient plus le terme vue , mais porte directement le nom de l'action aboutissant
l'affichage de cette vue.

V-B-3 - Mise en place d'URL gnriques

L'automatisation du routage nous a conduit dfinir pour notre site des URL de la forme monsite.fr/index.php?
controleur=xxx&action=yyy&id=zzz. Afin de faciliter leur lisibilit et leur rfrencement, il serait souhaitable de les
remplacer par des URL de la forme monsite.fr/controleur/action/id. Ce format d'URL est adopt par la plupart des
plates-formes PHP modernes (frameworks, CMS, etc.). Par exemple, l'URL pour accder l'action index sur le billet
3 sera monsite.fr/billet/index/3.

Cette amlioration peut tre ralise grce au mcanisme de rcriture d'URL ( URL rewriting ). Il consiste
configurer le serveur Web pour transformer la vole l'URL reue.

Le serveur Web Apache dispose d'un puissant module de rcriture d'URL nomm mod_rewrite. Pour le mettre en
uvre, il faut ajouter la racine de notre site un fichier de configuration nomm .htaccess.

.htaccess
# Rcrit une URL de type xxx/yyy/zzz en index.php?controleur=xxx&action=yyy&id=zzz
RewriteEngine on
RewriteRule ^([a-zA-Z]*)/?([a-zA-Z]*)?/?([a-zA-Z0-9]*)?/?$ index.php?controleur=$1&action=$2&id=
$3 [NC,L]

Cependant, ce nouveau format d'URL provoque des erreurs avec les liens relatifs de nos vues (inclusion de feuilles de
style, d'images, de fichiers JavaScript, etc.). En effet, le navigateur rsout ces liens partir du chemin relatif dfini par
la nouvelle URL et non plus depuis la racine du site. Pour rsoudre ce problme, il faut ajouter dans toutes nos vues
la balise HTML base. La solution naturelle est d'inclure cette balise dans notre gabarit commun toutes les vues.

- 43 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

gabarit.php
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<base href="<?= $racineWeb ?>" >
...

La valeur de l'lment base est donne par la variable PHP $racineWeb qui doit tre dfinie au moment de la
gnration d'une vue. Pour cela, on modifie la mthode associe dans la classe Vue.

Vue.php
...
public function generer($donnees) {
// Gnration de la partie spcifique de la vue
$contenu = $this->genererFichier($this->fichier, $donnees);
// On dfinit une variable locale accessible par la vue pour la racine Web
// Il s'agit du chemin vers le site sur le serveur Web
// Ncessaire pour les URI de type controleur/action/id
$racineWeb = Configuration::get("racineWeb", "/");
// Gnration du gabarit commun utilisant la partie spcifique
$vue = $this->genererFichier('Vue/gabarit.php',
array('titre' => $this->titre, 'contenu' => $contenu,
'racineWeb' => $racineWeb));
// Renvoi de la vue gnre au navigateur
echo $vue;
}
}
...

Grce au composant de configuration cr plus haut, la valeur de racine Web est rcupre depuis le fichier de
configuration du site. Dans ce fichier, elle doit correspondre au chemin relatif de dploiement sur le serveur Web. Par
exemple, pour un site dploy dans monsite.fr/MonBlog, la valeur de racineWeb doit tre la suivante :

dev.ini
; Configuration pour le dveloppement

[Installation]
racineWeb = /MonBlog/
...

V-B-4 - Scurisation des donnes reues et affiches

Jusqu' prsent, nous n'avons que trs peu abord les problmatiques lies la scurit du site. Il s'agit d'un vaste
sujet aux ramifications nombreuses. En matire de dveloppement Web, la rgle de scurit essentielle est de ne
jamais faire confiance aux donnes reues. Le Web est un monde ouvert o il est facile de forger une requte
HTTP contenant des paramtres intentionnellement incorrects. Deux exemples frquents sont l'injection de code sur
la page Web finale (cross-site scripting ou XSS) et l'injection SQL (excution par le SGBD de requtes SQL non
prvues). Afin de se prmunir contre de telles attaques, il est indispensable de nettoyer ( sanitize ) les donnes
reues ou affiches par le site.

Concernant les injections SQL visant la base de donnes, la solution consiste utiliser des requtes paramtres
plutt que de construire la requte SQL par concatnation d'instructions et de paramtres. Dans ce cas, le moteur
du SGBD effectue un nettoyage automatique des paramtres. Notre partie Modle utilise dj des requtes
paramtres : nous n'avons donc pas de prcaution supplmentaire prendre ce sujet.

La mthode de lutte contre les injections de code sur la page Web finale consiste nettoyer ( chapper )
systmatiquement toute donne insre dans la page. Autrement dit, nous devons ajouter nos vues un mcanisme
de nettoyage des valeurs PHP insres. Une solution simple est d'ajouter la classe Vue une mthode de nettoyage.

- 44 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Vue.php
...
// Nettoie une valeur insre dans une page HTML
private function nettoyer($valeur) {
return htmlspecialchars($valeur, ENT_QUOTES, 'UTF-8', false);
}
...

Il existe plusieurs solutions pour nettoyer une valeur avant son insertion dans une page HTML. La fonction
htmlspecialchars utilise ici remplace les caractres spciaux comme < ou > par les entits HTML associes
(ici, < deviendra &lt; et > deviendra &gt; ). Ainsi, si la valeur est un script JavaScript ( <script></
script> ), son nettoyage empchera l'excution du script par le moteur JavaScript du navigateur affichant la page
HTML.

Bien entendu, toutes les vues doivent faire appel cette mthode pour chacune des donnes dynamiques insres
dans les pages HTML.

V-B-5 - Contraintes sur l'architecture du site

La dernire tape de la construction de notre framework consiste regrouper les lments qui le composent. Pour
cela, on cre un rpertoire Framework/ dans lequel on dplace les fichiers source des six classes qui le composent.

Profitons-en pour numrer les contraintes que le framework impose l'architecture du site qui l'utilise :

un rpertoire Config/ contient le ou les fichiers de configuration (dev.ini et prod.ini) lues par la classe
Configuration ;
un rpertoire Controleur/ contient tous les contrleurs ;
chaque classe contrleur (ainsi que le fichier PHP associ) commence par le terme Controleur (exemple :
ControleurAccueil).
chaque action ralisable correspond une mthode publique dans la classe contrleur associe ;
une mthode index() (action par dfaut) est dfinie dans chaque contrleur ;
un rpertoire Vue/ contient toutes les vues ;
chaque vue est dfinie dans un sous-rpertoire de Vue/ portant le mme nom que le contrleur associ la
vue (exemple : Vue/Accueil/index.php pour la vue associe l'action par dfaut du contrleur d'accueil) ;
les fichiers gabarit.php (gabarit commun toutes les vues) et erreur.php (affichage du message d'erreur)
sont stocks dans le rpertoire Vue/ ;

- 45 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

le fichier .htaccess et le fichier index.php rsident la racine du site.

V-C - Application : utilisation du framework sur le contexte d'exemple

prsent que notre framework est dfini, nous pouvons refactoriser notre blog d'exemple pour l'utiliser. Pour
commencer, voici le fichier de configuration utilis.

dev.ini
; Configuration pour le dveloppement

[Installation]
racineWeb = /MonBlog/

[BD]
dsn = 'mysql:host=localhost;dbname=monblog;charset=utf8'
login = root
mdp =

Puisque seule une mthode prive de la classe Modele a volu au cours de la construction du framework, aucune
classe de la partie Modle ne doit tre modifie. La seule mise jour consiste actualiser les inclusions du fichier
PHP contenant la classe Modele.

Billet.php, Commentaire.php
<?php

require_once 'Framework/Modele.php';
...

Nos deux contrleurs (ControleurAccueil et ControleurBillet) hritent maintenant de la classe Controleur du


framework. Ils doivent dfinir une action par dfaut sous la forme d'une mthode index(). Chaque mthode d'action
utilise la mthode genererVue() de Controleur pour gnrer la vue associe l'action. Les inclusions de fichiers du
framework doivent galement tre actualises.

ControleurAccueil.php
<?php

require_once 'Framework/Controleur.php';
require_once 'Modele/Billet.php';

class ControleurAccueil extends Controleur {

private $billet;

public function __construct() {


$this->billet = new Billet();
}

// Affiche la liste de tous les billets du blog


public function index() {
$billets = $this->billet->getBillets();
$this->genererVue(array('billets' => $billets));
}
}

La classe ControleurBillet utilise les services de la classe Requete pour accder aux paramtres de la requte. Elle
utilise galement la mthode executerAction() de sa superclasse afin d'actualiser l'affichage des dtails sur le billet.

ControleurBillet.php
<?php

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

- 46 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

ControleurBillet.php
class ControleurBillet extends Controleur {

private $billet;
private $commentaire;

public function __construct() {


$this->billet = new Billet();
$this->commentaire = new Commentaire();
}

// Affiche les dtails sur un billet


public function index() {
$idBillet = $this->requete->getParametre("id");

$billet = $this->billet->getBillet($idBillet);
$commentaires = $this->commentaire->getCommentaires($idBillet);

$this->genererVue(array('billet' => $billet,


'commentaires' => $commentaires));
}

// Ajoute un commentaire sur un billet


public function commenter() {
$idBillet = $this->requete->getParametre("id");
$auteur = $this->requete->getParametre("auteur");
$contenu = $this->requete->getParametre("contenu");

$this->commentaire->ajouterCommentaire($auteur, $contenu, $idBillet);

// Excution de l'action par dfaut pour actualiser la liste des billets


$this->executerAction("index");
}
}

Enfin, on dplace les fichiers vueAccueil.php et vueBillet.php dans les sous-rpertoires Vue/Accueil et Vue/Billet
respectivement, puis on les renomme tous les deux index.php puisque ces vues sont associes l'action par dfaut
de chacun des deux contrleurs. Le fichier vueErreur.php est renomme erreur.php. Le contenu des vues n'est pas
modifi, l'exception notable des appels systmatiques la mthode nettoyer() pour chaque donne incluse dans
la vue. Voici par exemple un extrait de la vue d'affichage des dtails d'un billet.

Vue/Billet/index.php
<?php $this->titre = "Mon Blog - " . $this->nettoyer($billet['titre']); ?>

<article>
<header>
<h1 class="titreBillet"><?= $this->nettoyer($billet['titre']) ?></h1>
<time><?= $this->nettoyer($billet['date']) ?></time>
</header>
<p><?= $this->nettoyer($billet['contenu']) ?></p>
...

Vous trouverez les fichiers source du contexte final l'adresse https://github.com/bpesquet/MonBlog/.

VI - Conclusion et perspectives

VI-A - Bilan final

Nous arrivons au terme de notre chantier de refactoring. Notre blog d'exemple utilise un framework MVC orient
objet qui lui fournit plusieurs services essentiels : configuration, interactions avec la base de donnes, routage des
requtes, URL gnriques, scurisation des donnes.

L'image ci-dessous illustre l'architecture finale de notre exemple.

- 47 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

Le framework que nous avons bti est totalement gnrique et rutilisable dans un autre contexte : il suffit de rcuprer
ses fichiers source (le contenu de Framework/ ainsi que les fichiers .htaccess et index.php de la racine), puis de
respecter les contraintes qu'il impose l'architecture du site (voir plus haut).

VI-B - Complments

La gestion de l'authentification est un besoin frquent lorsqu'on dveloppe un site Web. Afin de soumettre tout ou
partie du site authentification, on peut dfinir une superclasse abstraite ControleurSecurise pour les contrleurs.
Cette superclasse est charge de vrifier si le visiteur est authentifi et redirige vers la page de login si ce n'est pas
le cas. Cette solution est dtaille dans ce cours et sur cette branche du dpt GitHub.

Un autre besoin classique est d'ajouter aux vues des lments spcifiques lorsqu'un utilisateur est connect
(exemple : un message de bienvenue comportant son nom). Pour construire une vue personnalise si
l'utilisateur est connect, on peux complter la solution prcdente en dfinissant une autre classe abstraite
ControleurPersonnalise qui ajoute automatiquement les donnes client au moment de la gnration de la vue.

abstract class ControleurPersonnalise extends Controleur


{
/**
* Redfinition permettant d'ajouter les infos clients aux donnes des vues
*
* @param type $donneesVue Donnes dynamiques

- 48 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

* @param type $action Action associe la vue


*/
protected function genererVue($donneesVue = array(), $action = null)
{
$client = null;
$nbArticlesPanier = 0;
// Si les infos client sont prsente dans la session...
if ($this->requete->getSession()->existeAttribut("client")) {
// ... on les rcupre ...
$client = $this->requete->getSession()->getAttribut("client");

$panier = new Panier();


$nbArticlesPanier = $panier->getNbArticles($client['idClient']);
}
// ... et on les ajoute aux donnes de la vue

parent::genererVue($donneesVue + array('client' => $client, 'nbArticlesPanier' => $nbArticlesPanier), $action);


}

Un autre projet d'exemple sur GitHub utilise cette solution. Voici par exemple un extrait de la vue partielle Vue/
_Commun/barreNavigation.php

<?php if (isset($client)): ?>


<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span class="glyphicon glyphicon-user"></span>
Bienvenue, <?= $this->nettoyer($client['prenom']) ?> <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="client/">Informations personnelles</a></li>
<li class="divider"></li>
<li><a href="connexion/deconnecter">Se dconnecter</a></li>
</ul>
</li>
<li>
<!--button type="button" class="btn btn-default btn-primary navbar-btn"-->
<a href="panier/">
<span class="glyphicon glyphicon-shopping-cart"></span>
Panier <span class="badge"><?= $this->nettoyer($nbArticlesPanier) ?></span>
</a>
<!--/button-->
</li>
<?php else: ?>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span class="glyphicon glyphicon-user"></span> Non
connect <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="connexion/">S'identifier</a></li>
</ul>
</li>
<?php endif; ?>

La variable dynamique $client est ajoute automatiquement aux donnes de la vue par tout contrleur qui hrite de
ControleurPersonnalise lorsque l'utilisateur est connect.

VI-C - Pour aller encore plus loin

Afin de limiter sa complexit, notre framework reste minimaliste et donc largement perfectible. Les choix effectus
au cours de sa cration sont discutables et il est possible d'arriver un rsultat similaire par d'autres voies. Voici
quelques suggestions d'amliorations qui seront autant d'exercices enrichissants :

mettre en place des espaces de noms (namespaces) ;


utiliser la fonctionnalit PHP d'autochargement de classes ( autoloading ) ;

- 49 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/
voluer vers une architecture MVC en PHP par Baptiste Pesquet

ajouter des mcanismes de validation des donnes entrantes (exemple : si j'attends un entier, je vrifie que je
reois bien un entier) ;
intgrer un mcanisme de journalisation des vnements et des erreurs dans des fichiers journaux (log files) ;
utiliser un moteur de templates comme Twig pour les vues ;
intgrer des composants existants qui ont fait leurs preuves, par exemple l'excellent HttpFoundation issu du
framework Symfony.

Pour obtenir une architecture professionnelle, la meilleure solution consiste intgrer un framework PHP existant.
Ce cours dtaille une solution base sur le framework Silex, petit frre de Symfony.

Ainsi se termine cet article qui vous aura, j'espre, apport une meilleure comprhension des possibilits d'application
en PHP du modle MVC.

VI-D - Liens utiles

Voici les principales sources utilises pendant l'criture de cet article :

Symfony2 versus flat PHP ;


Custom PHP MVC Tutorial ;
An Introduction to the Front Controller Pattern.

Quelques articles complmentaires pour aller plus loin :

PHP tips & tricks ;


Create your own framework... on top of the Symfony2 Components.

Enfin, une slection de quelques projets instructifs :

http://mvcmusicstore.codeplex.com/ (une introduction ASP.NET MVC) ;


https://github.com/ndavison/Nathan-MVC ;
https://github.com/fguillot/simpleFramework ;
https://github.com/lstrojny/EPHPMVC.

VII - Remerciements

Nous tenons remercier Benjamin Delespierre et Frdric Guillot pour leurs conseils aviss, ainsi que
ClaudeLELOUP pour sa relecture attentive.

- 50 -
Le contenu de cet article est rdig par Baptiste Pesquet et est mis disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transpos. Les logos Developpez.com,
en-tte, pied de page, css, et look & feel de l'article sont Copyright 2013 Developpez.com.
http://bpesquet.developpez.com/tutoriels/php/evoluer-architecture-mvc/

Вам также может понравиться