Upload
sowhat-01
View
22
Download
0
Embed Size (px)
Citation preview
5/10/2018 48492186-mvc - slidepdf.com
http://slidepdf.com/reader/full/48492186-mvc 1/73/2007 (21)
Fiche technique
1
S'il est un point commun aux très nom-
breux frameworks web disponibles en
PHP, c'est bien l'emploi du motif MVC
ou Modèle-Vue-Contrôleur. De Symfony au
Zend Framework en passant par CakePHP
et CodeIgniter, tous revendiquent leur affi-
liation au MVC. Nous allons voir que si ces
frameworks diffèrent de façon importante
dans leur implémentation des couches Vue
ou Modèle, leur approche du Contrôleur est
assez semblable, et tire profit de plusieurs
années d'expérimentations diverses dans cedomaine.
Le MVC par l'exempleNous avons tous débuté en PHP par des pages
ressemblant à l'exemple 1. Après tout, à l'ori-
gine PHP était fait pour ça : intégrer de la lo-
gique dans le HTML des pages pour les rendre
dynamiques. Cette approche peut fonctionner
pour des sites très simples, mais dès lors que le
nombre de pages s'accroit, la maintenance de-
vient vite un enfer. Il est pourtant très simple
de refactoriser ce code d'une façon plus pro-
pre, comme l'illustre l'exemple 2 : un fichier
contenant la logique d'accès aux données, un
fichier HTML avec un tout petit peu de lo-
gique de présentation, et un fichier faisant le
lien entre les 2. Le code devient plus facile à
lire et à maintenir, et on peut tout à fait modi-
fier complètement la présentation (le HTML),
ou au contraire modifier l'accès aux données
(en changeant de SGBD par exemple) sans
toucher au reste du code. Sans le savoir, nous
avons appliqué le modèle MVC. Vous remar-querez que nous n'avons pas utilisé l'orienté
objet : il est tout à fait possible d'appliquer ce
modèle en conservant un code uniquement
fonctionnel.
Comme vous le constaterez assez vite, il n'est
pas toujours facile d'appliquer les principes du
MVC avec succès : cerner les différentes res-
ponsabilités du code afin de bien le comparti-
menter est loin d'être une tâche aisée. Toutefois,
l'expérience aidant, vous apprécierez très vite de
travailler dans ce cadre là et vous en constaterez
les bienfaits sur la lisibilité et la facilité de main-
tenance de votre code.N'espérez d'ailleurs pas trouver dans cet ar-
ticle une méthode idéale d'implémentation du
MVC : cela n'existe pas. On ne peut pas relier
le MVC à un ensemble fini de classes de base,
comme dans la plupart des design patterns. Plus
qu'un motif de conception, le MVC est un mo-
dèle d'architecture, un ensemble de principes à
suivre, dans lequel un grand nombre de motifs
de conception peuvent s'appliquer.
Principes du MVCLe motif MVC est en fait apparu dans un
framework développé par Trygve Reenskaug
pour le langage Smalltalk à la fin des années
70. Le but de ce framewok était d'isoler le co-
de régissant l'interface graphique du code de
l'application proprement dit. Ainsi, un chan-
gement au niveau de l'interface graphique ne
nécessitait pas de modifier la logique métier
de l'application.
Le MVC divise donc le code d'une applica-
tion selon 3 responsabilités :
• Modèle : encapsule la logique métier, l'ac-
cès et la manipulation des sources de don-
nées;
• Vue : présente à l'utilisateur les données
obtenues à partir du modèle;
• Contrôleur : interprète les requêtes de
l'utilisateur, interagit avec le modèle et sé-
lectionne la vue à utiliser.
Détaillons maintenant les 2 responsabilités les
plus faciles à appréhender. La vue tout d'abord,
représente la réponse de l'application à une re-
quête. C'est une représentation de l'état du
modèle à un instant. Deux stratégies existent
pour implémenter la vue. Le motif Template
View est bien sûr le plus courant : soit on in-
tègre du PHP à une page HTML, soit on utili-
se un des nombreux moteurs de template dis-
ponibles, comme Smarty ou Savant pour ne ci-
ter que les plus connus. L'autre possibilité est
d'utiliser le motif Transform View, ce qui se faitle plus souvent en faisant générer des données
sous forme de XML par le modèle, et en ap-
pliquant une feuille de style XSLT à ces don-
nées. On peut également utiliser le motif Cus-
tom Tag , dans lequel votre moteur de rendu
HTML s'appuie sur des tags XML spécifiques
placés dans le template pour intégrer des mor-
ceaux de HTML dynamiques.
Ce qu'il est important de comprendre est
que la vue ne doit en aucun cas contenir de la
logique métier, mais uniquement de la logique
de présentation (et vice-versa). Si par exemple
nous souhaitons afficher certains objets en
rouge dans une page (par exemple des comptes
bancaires avec un solde négatif), le modèle doit
fournir un moyen de déterminer les comptes en
question, et c'est dans la vue qu'une condition
permettra de les afficher en rouge.
Le motif MVC
Une très large majorité des frameworks PHP exploitent le motif MVC. Ce motif simple en apparence, peut être appliqué de bien des façons. Nous allonsexplorer ensemble la couche contrôleur, en examinant des possibilités concrètesd'implémentation.
Cet article explique :• Diférentes possibilités d'implémentation de la
couche contrôleur, en insistant sur la relation
avec les URLs correspondantes.
Ce qu'il faut savoir :• Notions de base de l'orienté objet en PHP.
Niveau de difficulté
Contrôleurs et URLs
5/10/2018 48492186-mvc - slidepdf.com
http://slidepdf.com/reader/full/48492186-mvc 2/7
MVC
www.phpsolmag.org 2
Le modèle quand à lui est le coeur même de
l'application. C'est lui qui a un accès direct aux
sources de données, et les manipule en appli-
quant les règles métier. Il doit toujours rester
indépendant du contrôleur et de la vue, car
c'est à cette seule condition qu'il pourra être
réutilisable dans d'autres contextes, commepar exemple si l'application doit subir une re-
fonte graphique, ou que l'ordre de visualisation
des pages doit changer. Cela permet également
aux développeurs en charge de cette partie de
l'application de ne se préoccuper que de la logi-
que métier, et de plus le modèle est de ce fait
beaucoup plus facile à tester. Du point de vue
de l'implémentation, de nombreux motifs de
conception et outils d'ORM (Object Relational
Mapping ) sont dédiés à la couche modèle, mais
cela sort du cadre de cet article. Vous pouvez
vous référer à mon article du précédent nu-
méro, Implémentation du motif ActiveRecorden PHP5, pour avoir un aperçu de quelques uns
de ces motifs.
Arrêtons nous un moment sur les relations
possibles entre la vue et le modèle. Nous avons
dit que le modèle doit être indépendant du con-
trôleur et de la vue, mais ces derniers dépen-
dent malgré tout du modèle, puisqu'ils doivent
pouvoir y accéder pour récupérer des données.
Deux conceptions s'affrontent alors : une pre-
mière stratégie est que le contrôleur demande
les données à afficher au modèle et les fait pas-
ser à la vue. Dans ce cas, la vue n'a pas de relation
directe avec le modèle : c'est une approche push,les données sont poussées dans le template par
le contrôleur. L'autre stratégie, l'approche pull ,
consiste à laisser la vue faire appel directement
au modèle pour récupérer les données dont elle
a besoin. Bien sûr, elle ne doit pas appeler de
méthodes du modèle pouvant modifier l'état
de ce dernier, car tel est le rôle du contrôleur.
Les 2 stratégies se valent, mais attention car l'ap-
proche pull a tendance à recoupler la vue et le
modèle, et le développeur a vite fait de déraper
et de décharger le contrôleur de ses responsabi-
lités. De plus, un changement d'API du modèle
peut casser des vues existantes. On lui préfèreradonc l'approche push ou le découplage est total.
Vous voici donc maintenant confrontés à
la vraie problématique de cet article : le con-
trôleur. Il est en effet le plus difficile à cerner,
principalement parce que ce terme de contrô-
leur englobe différentes significations dans plu-
sieurs motifs. Dans un premier temps, nous dé-
finirons donc simplement le rôle du contrôleur,
quelle que soit sa forme, de la façon suivante :
le contrôleur reçoit et analyse les requêtes de
l'utilisateur, a la responsabilité d'appeler les mé-
thodes du modèle susceptibles de modifier son
état, et sélectionne la vue à afficher.
Martin Fowler, dans son ouvrage de référence
Patterns of Enterprise Application Architecture,
présente plusieurs motifs de conception relatifs
à la couche contrôleur ; nous allons les examiner
en détail.
Le motif PageController Dans le Listing 2, nous avons appliqué le mo-
tif PageController : un contrôleur par page de
notre application. Ses responsabilités sont les
suivantes : analyser la requête, c'est à dire
en extraire les informations nécessaires à
l'exécution de l'action demandeé (dans no-
tre exemple, il s'agit de déterminer si l'appel
de la page s'est fait par la méthode POST ou
non), mettre à jour le modèle si nécessaire,
Listing 1. Exemple de ce qu'il ne faut pas faire ;)
<?php
$connexion = mysql_connect ("localhost", "root", "password");
mysql_select_db("article_mvc");
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$post_id = $_POST['post_id'];
mysql_query("INSERT INTO comments SET post_id = '$post_id', author = '{$_
POST['author']}',
content = '{$_POST['content']}', created_on = NOW()");
header("location: {$_SERVER['PHP_SELF']}?post_id=$post_id");
die();
} else {
$post_id = $_GET['post_id']; }
?>
<html>
<head>
<title>Mon blog</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div id="conteneur">
<div id="header">
<h1>Mon blog</h1>
</div>
<div id="contenu"> <?php
$rs = mysql_query("SELECT * FROM posts WHERE id = '$post_id'");
$post = mysql_fetch_array($rs);
?>
<h2><?php echo $post['title']; ?></h2>
<strong>Le <?php echo $post['created_on']; ?></strong>
<p><?php echo $post['content']; ?></p>
<?php
$rs = mysql_query("SELECT * FROM comments WHERE post_id = '$post_id'");
$comments_count = mysql_num_rows($rs);
?>
<h3><?php echo $comments_count; ?> commentaire(s)</h3>
<?php while ($comment = mysql_fetch_array($rs)) { ?> <h5><?php echo $comment['author']; ?>, le <?php echo $comment['created_on'];
?></h5>
<p><?php echo $comment['content']; ?></p>
<?php } ?>
<form name="add-comment" method="post" action="
<?php echo $_SERVER['PHP_SELF']; ?>">
<input type="hidden" name="post_id" value="<?php echo $post['id'];?>"/>
<input type="text" name="author" size="40" maxlength="50"
value="Votre nom" /><br />
<textarea name="content" rows="5">Vos commentaires</textarea><br />
<input name="comment_submit" type="submit" value="Envoyer" />
<input type="reset" value="Effacer" />
</form>
</div>
</div>
</body>
</html>
5/10/2018 48492186-mvc - slidepdf.com
http://slidepdf.com/reader/full/48492186-mvc 3/73/2007 (21)
Fiche technique
3
Listing 2. L'exemple 1 à la sauce MVC
URL : http://localhost/show_post.php?post_id=n
// chier blog_model.phpfunction db_connection() {
static $conn = null;
if ($conn === null) {
$conn = mysql_connect ("localhost", "root", "password");
mysql_select_db("article_mvc");
}
return $conn;
}
function get_post($id) {
$rs = mysql_query("SELECT * FROM posts WHERE id = '$id'",
db_connection());
return mysql_fetch_array($rs);
}function get_comments($post_id) {
$rs = mysql_query("SELECT * FROM comments WHERE post_id =
'$post_id'", db_connection());
$comments = array();
while ($row = mysql_fetch_array($rs)) {
$comments[] = $row;
}
return $comments;
}
function insert_comment($comment) {
// oui je sais qu'on doit utiliser mysql_real_escape_string
;)
$sql = "INSERT INTO comments SET post_id = '".mysql_escape_string($comment['post_id'])."',
author = '".mysql_escape_string($comment['author'])
."',
content = '".mysql_escape_string($comment['content']
)."',
created_on = NOW()";
mysql_query($sql, db_connection());
}
// chier show_post_view.php
<html>
<head>
<title>Mon blog</title>
<meta http-equiv="Content-Type" content="text/html;
charset=utf-8" />
</head>
<body>
<div id="conteneur">
<div id="header">
<h1>Mon blog</h1> </div>
<div id="contenu">
<h2><?php echo $post['title']; ?></h2>
<strong>Le <?php echo $post['created_on']; ?></
strong>
<p><?php echo $post['content']; ?></p>
<h3><?php echo count($comments); ?>
commentaire(s)</h3>
<?php foreach ($comments as $comment) { ?>
<h5><?php echo $comment['author']; ?>, le <?php
echo $comment['created_on']; ?></h5>
<p><?php echo $comment['content']; ?></p>
<?php } ?> <form name="add-comment" method="post"
action="<?php echo $_SERVER['PHP_SELF'];
?>">
<input type="hidden" name="post_id" value="<?php
echo $post['id']; ?>" />
<input type="text" name="author" size="40"
maxlength="50" value="Votre nom" /><br
/>
<textarea name="content" rows="5">Vos
commentaires</textarea><br />
<input name="comment_submit" type="submit"
value="Envoyer" />
<input type="reset" value="Effacer" /> </form>
</div>
</div>
</body>
</html>
// chier show_post.php
include('blog_model.php');
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
insert_comment($_POST);
header("location: {$_SERVER['PHP_SELF']}?post_id={$_
POST['post_id']}");
die();
} else
{
$post = get_post($_GET['post_id']);
$comments = get_comments($_GET['post_id']);
include('show_post_view.php');
}
en lui fournissant les données issues de la
requête (ici en appelant la fonction insert_
comment() avec les données dans $_POST),
et déterminer la vue à afficher, en lui faisant
passer les données issues du modèle. Les Pa-
gesControllers regroupent donc l'ensemble des
responsibilités de la couche contrôleur, cequi en fait le motif le plus simple à mettre en
oeuvre dans le cadre du MVC : un contrôleur
par page, appellé directement dans l'url. Nous
n'avons même pas besoin d'utiliser l'orienté
objet pour cela ! Nous pouvons d'ailleurs re-
marquer que le code du contrôleur pourrait
tout à fait être placé dans le même fichier que
la vue, ce qui dans le monde Java est appellé
le MVC modèle 1. Ce motif fonctionne très
bien dans les cas les plus simples, mais dès
que l'application devient plus complexe, des
redondances commencent à apparaître dans
le code.
Le motif FrontController Pour éviter ce problème de redondances,
l'étape suivante selon Martin Fowler est de
confier la responsabilité de la prise en charge
et de l'analyse de la requête à un composant
spécialisé : le FrontController . Ce composant
sera donc l'unique point d'entrée de notre
application, et décidera de la suite des évè-
nements. En pratique, ce point d'entrée sera
le plus souvent un fichier index.php chargé
d'instancier le FrontController . L'action à réa-
liser (ou la page à afficher) sera donc fourni
en paramètre dans l'url. Par exemple, l'url de
notre exemple 2 deviendrait : http://localhost/
index.php?action=show_post&post_id=n
ou bien, si nous souhaitons mieux struc- turer notre application : http://localhost/inde
x.php?module=blog&action=show_post&post_
id=n.
Ce motif s'associe normalement au motif
Command , qui utilise des objets pour re-
présenter des action. Une grande partie de
la logique de nos PageControllers se retrouve
encapsulée dans des sous-classes de la classe
5/10/2018 48492186-mvc - slidepdf.com
http://slidepdf.com/reader/full/48492186-mvc 4/7
MVC
www.phpsolmag.org 4
Listing 3. Implémentation d'un FrontController en PHP (et classes associées)
class Request {
public function getParam($key) {
return lter_var($this->getTaintedParam($key), FILTER_
SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_
QUOTES);
}
public function getTaintedParam($key) {
if ($this->getRequestMethod() == 'POST' && isset($_
POST[$key])) {
return $_POST[$key];
} else {
return $_GET[$key];
} }
public function getMethod() {
return $_SERVER['REQUEST_METHOD'];
}
public function parseUri() {
$requestUri = substr($_SERVER['REQUEST_URI'],
strlen(str_replace('/index.php', '/',
$_SERVER['SCRIPT_NAME'])));
if (empty($requestUri)) return array();
if (strpos($requestUri, '?') !== false) {
list($path, $queryString) = explode('?', $requestUri);
} else {
list($path, $queryString) = array($requestUri, ''); }
if (substr($path, -1) == '/') $path = substr($path, 0,
-1);
preg_match('#^(?P<module>\w+)(/(?P<command>\w+))?(/
?(?P<id>\w+))?$#', $path, $matches);
if (isset($matches['id'])) $_GET['id'] = $matches['id'];
return $matches;
}
}
class Response {
private $assigns = array();
private $headers = array();
private $body;public function addVar($key, $value) {
$this->assigns[$key] = $value;
}
public function getVars() {
return $this->assigns;
}
public function setBody($body) {
$this->body = $body;
}
public function redirect($url, $permanently = false) {
if ($permanently) {
$this->headers['Status'] = '301 Moved Permanently';
} else {
$this->headers['Status'] = '302 Found';
}
$this->headers['location'] = $url;
$this->body = "<html><body>You are being <a href=\
"{$url}\">redirected</a>.</body></
html>";
}
public function out() {
foreach($this->headers as $key => $value) {
header($key.': '.$value);
}
echo $this->body;
}
}
class FrontController {
private $defaults = array('module' => 'home', 'action' =>
'index');private $request;
private $response;
public function __construct() {
$this->request = new Request();
$this->response = new Response();
}
public function dispatch($defaults = null) {
$recognized = $this->request->parseUri();
$recognized = array_merge($this->defaults, $recognized);
$this->forward($recognized['module'],
$recognized['action']);
}
public function forward($module, $action) { $command = $this->getCommand($module, $action);
$command->execute($this->request, $this->response);
}
public function render($le) {
$view = new View();
$this->response->setBody($view-> render($le, $this-
>response-> getVars()));
}
public function redirect($url) {
$this->response->redirect($url);
}
public function getResponse() {
return $this->response; }
private function getCommand($module, $action) {
if (! le_exists($path = "$module/$action.php")) {
return new UnknownCommand($this);
}
require($path);
$class = $action.'Command';
return new $class($this);
}
}
class View {
public function render($le, $assigns = array()) {
extract($assigns);
ob_start();
include ($le);
$str = ob_get_contents();
ob_end_clean();
Command. Les Listings 3 et 4 vous fournissent
un exemple d'implémentation. Le modèle uti-
lisé n'est pas détaillé, car c'est le contrôleur
qui nous intéresse !
La classe Request analyse l'url fournie par
l'utilisateur, ce qui nous permet de détermi-
ner le module et la commande souhaitée. Le
FrontController appelle le fichier correspon-
dant à la commande, instancie la sous-classe
de Command, et appelle sa méthode execute().
Une fois l'action effectuée, la commande
appelle le rendu d'une vue (via la méthode
5/10/2018 48492186-mvc - slidepdf.com
http://slidepdf.com/reader/full/48492186-mvc 5/73/2007 (21)
Fiche technique
5
render()), demande une redirection HTTP
(via redirect()), ou fait suivre à une autre
commande (via forward()).
Vous remarquerez immédiatement, je
l'espère, que l'approche n'est plus du tout la
même que dans nos précédents exemples : on
passe d'un code procédural à une approcheorientée objet, et la complexité du code uti-
lisé croit d'une manière significative. Et c'est
là le vrai problème du motif FrontController en
PHP : dans de nombreux cas le jeu n'en vaut
pas la chandelle et on a tendance à réinventer
la roue en s'efforçant d'appliquer ce motif tel
qu'il a été décrit dans la littérature. En effet,
ce motif a été appliqué initialement dans le
monde Java, où les contraintes sont totalement
différentes. Examinons l'interface d'une classe
Command en Java :
class Command...public void init(ServletContext
context, HttpServletRequest
request, HttpServletResponse
response)...
public void process()...
Pour initialiser une sous-classe de Command, on
doit notamment lui passer en argument une
instance de la classe HttpServletRequest .
La raison en est qu'en Java, plusieurs requê-
tes peuvent être servies par une même ins-
tance de notre sous-classe de Command : cha-
que requête a son propre thread, mais tousles threads partagent le même espace mémoi-
re, et donc nos classes doivent être thread-sa-
fe. On utilise donc les paramètres et les re-
tours de méthodes pour passer l'information
à la commande et l'en faire sortir. D'ailleurs
HttpServletRequest fait partie intégrante de
J2EE et non d'un framework quelconque.
Le fonctionnement de PHP est totalement
différent, puisqu'il recrée son environnement
à chaque requête ! Et c'est pour cela que PHP
peut nous fournir les variables super-globales
$_GET, $_POST ou $_SESSION. Une implémenta-
tion trop stricte de ce motif, comme l'ont fait lespremiers frameworks PHP comme php.MVC,
Phrame ou Eocene, largement inspirés du fra-
mework Java Struts, n'a donc pas toujours de
sens en PHP. En fait, on peut même arguer que
le FrontController est déjà fourni par Apache et
le moteur de PHP !
Pour autant, ce motif peut tout de même
avoir un intérêt : pour cela, il faut que l'implé-
mentation des classes Request et Response par
exemple, présente des fonctionnalités à valeur
ajoutée : c'est ce que j'ai voulu vous montrer en
implémentant d'un côté l'assainissement des
paramètres de la requête directement dans la
classe Request, en utilisant l'extension PECL
filter (vous remarquerez que l'on dispose tout
de même de la méthode getTaintedParam
pour récupérer les paramètres non filtrés),
et la gestion des en-têtes HTTP dans la classe
Response. D'autre part, la classe Request nous
permet d'utiliser des URLs plus jolies, du type :
http://localhost/module/command/id
Toutefois, l'implémentation que je vous pro-
pose ne va pas encore assez loin pour justifier
la complexité qu'elle apporte. Le support des
URLs propres par exemple, pourrait assez aisé-ment être ajouté grâce au mod_rewrite d'Apa-
che, et avec des performances bien supérieures
! En pratique, je pense que qu'il faut avoir à
sa disposition un vrai composant de routage,
comme celui que propose le Zend Framework,
pour commencer à trouver un intérêt à centra-
liser ainsi la logique associée au traitement des
requêtes :
$router = new Zend_Controller_
Router_Rewrite();
$router->addRoute('user', new
Zend_Controller_Router_Route(':controller/:action'));
$router->addRoute('user', new
Zend_Controller_Router_
Route('user/:username'));
$router->addRoute('archive',
new Zend_Controller_Router_
Route('archive/:year', array('year'
=> 2006), array('year' => '\d+')));
Par ce type de code, la classe Zend _
Controller _ Router nous permet non seu-
lement de faire de la reconnaissance d'URLs
complexes, mais également de générer auto-matiquement les URLs de notre application
dans les templates, ce qui nous permet de mo -
difier l'architecture de nos URLs sans avoir à
aller modifier de nombreux templates. Nous
avons donc une vraie valeur ajoutée, que l'on
ne peut totalement apporter à l'aide de solu-
tions basées sur mod_rewrite.
Pour en revenir à Martin Fowler, celui-ci dis-
tingue un autre intérêt possible à l'implémenta-
tion d'un FrontController : en lui associant le mo-
tif InterceptingFilter , on peut plus facilement gérer
le problème de l'authentification ou du logging :puisque nous avons du code qui doit être exécuté
à chaque requête, cela fait du FrontController un
bon candidat pour intégrer ce code.
interface Filter {
public function preFilter();
public function postFilter();
}
class FrontController {
...
private $ltersChain = array();
...
public function addFilter($lter) {$this->ltersChain[] = $lter;
}
public function dispatch($defaults =
null) {
...
foreach ($this->ltersChain as
$lter) {
$lter->preFilter();
}
$this->forward($recognized[
'module'], $recognized
['action']);
foreach (array_reverse($this->ltersChain) as $lter) {
$lter->postFilter();
}
}
}
Listing 4. Implémentation des sous-classes de Command relatives à notre exemple précédent
class ShowPostCommand extends Command {
public function getRequestMethod() {
return METHOD_GET;
}
public function execute($request, $response) { $response->setVar('post', PostDAO::ndById($request->getParameter('id')));
$response->setVar('comments', CommentDAO::ndByPostId($request-
>getParameter('id')));
$this->render('show_posts.php');
}
}
class AddCommentCommand extends Command {
public function getRequestMethod() {
return METHOD_POST;
}
public function execute($request, $response) {
CommentDAO::insert($request->getParameter('post_id'),
$request->getParameter('author'),
$request->getParameter('content'));
$controller->redirect('/blog/showPost/'.$request->getParameter('post_id'));
}
}
5/10/2018 48492186-mvc - slidepdf.com
http://slidepdf.com/reader/full/48492186-mvc 6/7
MVC
www.phpsolmag.org 6
Dans cet exemple d'implémentation, on dé-
finit une interface Filter que nos filtres de-
vront donc implémenter, avec 2 méthodes
preFilter et postFilter, à appeler respecti-
vement avant et après l'exécution de la com-
mande.
Mais attention : là encore PHP nous fournitdes solutions simples pour faire exactement
la même chose : en utilisant les directives du
php.ini auto_prepend_file et auto_append_
file, on peut charger des scripts automatique-
ment avant et après l'inclusion de notre Page-
Controller, réglant notre problème du même
coup !
Pour terminer cette analyse critique du
motif FrontController , il convient de signaler
que le motif Command, tout comme le Page-
Controller , devient vite gênant lorsque l'ap-
plication et en particulier les relations entre
les différentes pages devient plus complexe.Avoir une image claire du fonctionnement
d'un ensemble de pages devient difficile, et là
encore, des redondances dans le code peuvent
apparaître.
Les classes ActionController Pour répondre à ce problème, les déve-
loppeurs des récents frameworks Symfony
ou Zend Framework ont appliqué la même re-
cette que RubyOnRails : regrouper les actions
concernant un même type d'entités dans une
seule classe. Vous trouverez dans l'exemple 5
un exemple d'implémentation de notre exem-ple initial avec le framework Symfony. Là en-
core, le modèle utilisé n'est pas détaillé car
hors-sujet.
Vous remarquerez que les actions sont main-
tenant des méthodes publiques dont le nom est
précédé de execute. On ainsi une meilleure vi-
sibilité du code applicatif de chaque partie de
son application. D'autre part, vous remarque-
rez que dans la méthode executeShow() , nous
instancions une propriété $this->post non
déclarée.
En effet, c'est ainsi que l'on assigne des va-
riables au template avec Symfony. Le contenude la propriété $this->post se retrouve acces-
sible automatiquement dans le template sous
la forme de la variable $post. Ce principe a
été conservé dans l'exemple d'implémentation
d'une super-classe ActionController présenté
dans le Listing 6.
Nous utilisons pour cela les méthodes magi-
ques __get() et __set(). Notez bien que nous
réutilisons les classes Request, Response et
View vues dans le Listing 4.
Certaines responsabilités du Front-
Controller se retrouvent intégrées
dans la classe ActionController, et le
FrontController se trouve finalement ré-
duit à un rôle d'encapsulation de l'exécu-
tion du code : le bloc try/catch nous permet
d'attraper les exceptions éventuelles, et de
demander le rendu d'une page 404 ou 500
pour ne pas montrer des messages d'erreur
critiques à l'utilisateur.
Nous avons ajouté à la classe
ActionController la possibilité de deman-
der automatiquement le rendu d'une vue
portant le même nom que l'action si aucun
rendu ou redirection n'a été demandé par
l'action elle-même. Notez enfin que nous uti-
lisons l'API de Réflexion de PHP pour véri fier
que l'action demandée dans l'URL ex iste bien
dans le contrôleur, et surtout qu'elle est bien
publique.
Listing 5. L'exemple 1 avec Symfony
class blogActions extends sfActions {
public function executeIndex() {
// afchage des derniers billets
}
public function executeShow() {
$this->post = PostPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($this->post);
}
public function executeAddComment() {
if ($this->getRequest()->getMethod() == sfRequest::POST) {
$post_id = $this->getRequestParameter('post_id'); $comment = new Comment();
$comment->setPostId($post_id);
$comment->setAuthor($this->getRequestParameter('author'));
$comment->setContent($this->getRequestParameter('content'));
$comment->save();
return $this->redirect('blog/show?id='.$post_id);
}
}
}
// chier showSuccess.php
<html>
<head>
<title>Mon blog</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div id="conteneur">
<div id="header">
<h1>Mon blog</h1>
</div>
<div id="contenu">
<h2><?php echo $post->getTitle(); ?></h2>
<strong>Le <?php echo $post->getCreatedOn(); ?></strong>
<p><?php echo $post->getContent(); ?></p>
<h3><?php echo count($post->getComments()); ?> commentaire(s)</h3>
<?php foreach ($post->getComments() as $comment) : ?> <h5><?php echo $comment->getAuthor(); ?>, le <?php echo $comment-
>getCreatedOn(); ?></h5>
<p><?php echo $comment->getContent(); ?></p>
<?php endforeach; ?>
<?php echo form_tag('blog/addComment') ?>
<input type="hidden" name="post_id" value="<?php echo $post->getId();
?>" />
<input type="text" name="author" size="40" maxlength="50" value="Votre
nom" /><br />
<textarea name="content" rows="5">Vos commentaires</textarea><br />
<input name="comment_submit" type="submit" value="Envoyer" />
<input type="reset" value="Effacer" />
</form>
</div>
</div>
</body>
</html>
5/10/2018 48492186-mvc - slidepdf.com
http://slidepdf.com/reader/full/48492186-mvc 7/73/2007 (21)
Fiche technique
7
Listing 6. Implémentation d'un ActionController
ConclusionNous venons de survoler ensemble quelques mo-
tifs de conception basiques pour l'implémenta-
tion d'une couche contrôleur. Bien souvent, les
développeurs fraîchement convertis à l'orienté
objet ont tendance à ne plus voir que des solu-
tions objets à tous les problèmes. Gardez-vous
pourtant de trop vite opter pour des solutions
comme le FrontController, car PHP et Apache
permettent déjà de faire beaucoup de choses,
et avec des performances bien supérieures. On
peut d'ailleurs se risquer à avancer que dans les
3 couches du MVC, seule la couche modèle mé-
rite vraiment dans de nombreux cas une appro-
che orientée objet, ce que Martin Fowler appelle
un Rich Domain Model . Toutefois, l'optimisation
prématurée est souvent source de problèmes par
la suite, aussi si votre application est réellement
complexe, vous gagnerez certainement à la déve-
lopper ou tout du moins à la prototyper à l'aide
d'un framework moderne comme le Zend Fra-
mework, Symfony ou CakePHP. L'essentiel est
que l'utilisation de ce framework vous permette
d'écrire du code plus clair. Si les performances
deviennent par la suite un problème, il sera tou-
jours temps d'exploiter les ressources de PHP et
d'Apache pour améliorer la situation.
class FrontController {
public function dispatch() {
try {
$request = new Request();
$response = new Response();ActionController::factory($request, $response)->out();
} catch (Exception $e) {
ActionController::rescue($request, $response, $e)-
>out();
}
}
}
class ActionController {
private $request;
private $response;
private $performed;
public static function factory($request, $response) {
if (! le_exists($path = 'controllers/'.$request->getParam('controller'))) {
throw new UnknownControllerException();
}
require_once($path);
$className = $request->getParam('controller').'Control
ler';
$controller = new $className($request, $response);
return $controller->process();
}
public static function rescue($request, $response, $e) {
$controller = new ActionController($request, $reponse);
return $controller->processWithException($e);
}public function __construct($request, $reponse) {
$this->request = $request;
$this->response = $response;
$this->performed = false;
}
public function __get($name) {
return $this->response->getVar($name);
}
public function __set($name, $value) {
$this->response->setVar($name, $value);
}
public function process() {
$action = $this->request->getParam('action'); if (!$this->actionExists($action)) {
throw new UnknownActionException();
}
// lots of stuff before...
$this->$action();
// lots of stuff after...
if (!$this->performed) {
$this->render($this->request->getParam('action').'.p
hp'); }
return $this->response;
}
public function processWithException($e) {
if (in_array(get_class($e), array('UnknownControllerExcep
tion', 'UnknownActionException'))) {
$this->render('404.php');
} else {
$this->render('500.php');
}
return $this->response;
}
public function render($le) { if ($this->performed) {
throw Exception('Un rendu ou une redirection a déjà
été effectué');
}
$view = new View();
$this->response->setBody($view->render($le, $this-
>response->getVars()));
$this->performed = true;
}
public function redirect($url) {
if ($this->performed) {
throw Exception('Un rendu ou une redirection a déjà
été effectué'); }
$this->response->redirect($url);
$this->performed = true;
}
private function actionExists($action) {
try {
$method = new ReectionMethod(get_class($this),
$action);
return ($method->isPublic() && !$method-
>isConstructor());
}
catch (ReectionException $e) {
return false; }
}
}
RAPHAËL ROUGERONRaphaël Rougeron est développeur web à la Cham-
bre de Commerce et d'Industrie de Paris, pour la-
quelle il a réalisé différentes applications métiers
en PHP. Dans le cadre de son travail, il a créé le fra-
mework Stato, publié sous licence MIT.
Pour contacter l'auteur : [email protected]