7
 3/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 ce domaine. Le MVC par l'exemple Nous 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  travaill er dans ce cadre là et vous en consta terez les bienfaits sur la lisibilité et la facilité de main-  tenance de vo tre 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 MVC Le 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 sel on 3 responsa bilité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'u tilisateur 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 po ssibilité est d'utiliser le motif Tr ansform Vie w , ce qui se fait le 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 allons explorer ensemble la couche contrôleur , en examinant des possibilités concrètes d'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

48492186-mvc

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]