Sie sind auf Seite 1von 7

Fiche technique

Le motif MVC
Contrleurs et URLs
Une trs large majorit des frameworks PHP exploitent le motif MVC. Ce motif simple en apparence, peut tre appliqu de bien des faons. Nous allons explorer ensemble la couche contrleur, en examinant des possibilits concrtes d'implmentation.
Cet article explique :
Diffrentes possibilits d'implmentation de la couche contrleur, en insistant sur la relation avec les URLs correspondantes.

Ce qu'il faut savoir :


Notions de base de l'orient objet en PHP.

ncessitait pas de modifier la logique mtier de l'application. Le MVC divise donc le code d'une application selon 3 responsabilits : Modle : encapsule la logique mtier, l'accs et la manipulation des sources de donnes; Vue : prsente l'utilisateur les donnes obtenues partir du modle; Contrleur : interprte les requtes de l'utilisateur, interagit avec le modle et slectionne la vue utiliser. Dtaillons maintenant les 2 responsabilits les plus faciles apprhender. La vue tout d'abord, reprsente la rponse de l'application une requte. C'est une reprsentation de l'tat du modle un instant. Deux stratgies existent pour implmenter la vue. Le motif Template View est bien sr le plus courant : soit on intgre du PHP une page HTML, soit on utilise un des nombreux moteurs de template disponibles, comme Smarty ou Savant pour ne citer que les plus connus. L'autre possibilit est d'utiliser le motif Transform View, ce qui se fait le plus souvent en faisant gnrer des donnes sous forme de XML par le modle, et en appliquant une feuille de style XSLT ces donnes. On peut galement utiliser le motif Custom Tag, dans lequel votre moteur de rendu HTML s'appuie sur des tags XML spcifiques placs dans le template pour intgrer des morceaux de HTML dynamiques. Ce qu'il est important de comprendre est que la vue ne doit en aucun cas contenir de la logique mtier, mais uniquement de la logique de prsentation (et vice-versa). Si par exemple nous souhaitons afficher certains objets en rouge dans une page (par exemple des comptes bancaires avec un solde ngatif), le modle doit fournir un moyen de dterminer les comptes en question, et c'est dans la vue qu'une condition permettra de les afficher en rouge.
3/2007 (21)

Niveau de difficult

'il est un point commun aux trs nombreux frameworks web disponibles en PHP, c'est bien l'emploi du motif MVC ou Modle-Vue-Contrleur. De Symfony au Zend Framework en passant par CakePHP et CodeIgniter, tous revendiquent leur affiliation au MVC. Nous allons voir que si ces frameworks diffrent de faon importante dans leur implmentation des couches Vue ou Modle, leur approche du Contrleur est assez semblable, et tire profit de plusieurs annes d'exprimentations diverses dans ce domaine.

Le MVC par l'exemple


Nous avons tous dbut en PHP par des pages ressemblant l'exemple 1. Aprs tout, l'origine PHP tait fait pour a : intgrer de la logique dans le HTML des pages pour les rendre dynamiques. Cette approche peut fonctionner pour des sites trs simples, mais ds lors que le nombre de pages s'accroit, la maintenance devient vite un enfer. Il est pourtant trs simple de refactoriser ce code d'une faon plus propre, comme l'illustre l'exemple 2 : un fichier contenant la logique d'accs aux donnes, un fichier HTML avec un tout petit peu de logique de prsentation, et un fichier faisant le lien entre les 2. Le code devient plus facile lire et maintenir, et on peut tout fait modi1

fier compltement la prsentation (le HTML), ou au contraire modifier l'accs aux donnes (en changeant de SGBD par exemple) sans toucher au reste du code. Sans le savoir, nous avons appliqu le modle MVC. Vous remarquerez que nous n'avons pas utilis l'orient objet : il est tout fait possible d'appliquer ce modle 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 succs : cerner les diffrentes responsabilits du code afin de bien le compartimenter est loin d'tre une tche aise. Toutefois, l'exprience aidant, vous apprcierez trs vite de travailler dans ce cadre l et vous en constaterez les bienfaits sur la lisibilit et la facilit de maintenance de votre code. N'esprez d'ailleurs pas trouver dans cet article une mthode idale d'implmentation 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 modle 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 dvelopp par Trygve Reenskaug pour le langage Smalltalk la fin des annes 70. Le but de ce framewok tait d'isoler le code rgissant l'interface graphique du code de l'application proprement dit. Ainsi, un changement au niveau de l'interface graphique ne

MVC

Le modle quand lui est le coeur mme de l'application. C'est lui qui a un accs direct aux sources de donnes, et les manipule en appliquant les rgles mtier. Il doit toujours rester indpendant du contrleur et de la vue, car c'est cette seule condition qu'il pourra tre rutilisable dans d'autres contextes, comme par exemple si l'application doit subir une refonte graphique, ou que l'ordre de visualisation des pages doit changer. Cela permet galement aux dveloppeurs en charge de cette partie de l'application de ne se proccuper que de la logique mtier, et de plus le modle est de ce fait beaucoup plus facile tester. Du point de vue de l'implmentation, de nombreux motifs de conception et outils d'ORM (Object Relational Mapping) sont ddis la couche modle, mais cela sort du cadre de cet article. Vous pouvez vous rfrer mon article du prcdent numro, Implmentation du motif ActiveRecord en PHP5, pour avoir un aperu de quelques uns de ces motifs. Arrtons nous un moment sur les relations possibles entre la vue et le modle. Nous avons dit que le modle doit tre indpendant du contrleur et de la vue, mais ces derniers dpendent malgr tout du modle, puisqu'ils doivent pouvoir y accder pour rcuprer des donnes. Deux conceptions s'affrontent alors : une premire stratgie est que le contrleur demande les donnes afficher au modle et les fait passer la vue. Dans ce cas, la vue n'a pas de relation directe avec le modle : c'est une approche push, les donnes sont pousses dans le template par le contrleur. L'autre stratgie, l'approche pull, consiste laisser la vue faire appel directement au modle pour rcuprer les donnes dont elle a besoin. Bien sr, elle ne doit pas appeler de mthodes du modle pouvant modifier l'tat de ce dernier, car tel est le rle du contrleur. Les 2 stratgies se valent, mais attention car l'approche pull a tendance recoupler la vue et le modle, et le dveloppeur a vite fait de draper et de dcharger le contrleur de ses responsabilits. De plus, un changement d'API du modle peut casser des vues existantes. On lui prfrera donc l'approche push ou le dcouplage est total. Vous voici donc maintenant confronts la vraie problmatique de cet article : le contrleur. Il est en effet le plus difficile cerner, principalement parce que ce terme de contrleur englobe diffrentes significations dans plusieurs motifs. Dans un premier temps, nous dfinirons donc simplement le rle du contrleur, quelle que soit sa forme, de la faon suivante : le contrleur reoit et analyse les requtes de l'utilisateur, a la responsabilit d'appeler les mthodes du modle susceptibles de modifier son tat, et slectionne la vue afficher. Martin Fowler, dans son ouvrage de rfrence Patterns of Enterprise Application Architecture, prsente plusieurs motifs de conception relatifs la couche contrleur ; nous allons les examiner en dtail.
www.phpsolmag.org

Le motif PageController
Dans le Listing 2, nous avons appliqu le motif PageController : un contrleur par page de notre application. Ses responsabilits sont les suivantes : analyser la requte, c'est dire
Listing 1. Exemple de ce qu'il ne faut pas faire ;)
<?php

en extraire les informations ncessaires l'excution de l'action demande (dans notre exemple, il s'agit de dterminer si l'appel de la page s'est fait par la mthode POST ou non), mettre jour le modle si ncessaire,

$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 = '{$_ content = '{$_POST['content']}', created_on = NOW()"); die(); POST['author']}',

header("location: {$_SERVER['PHP_SELF']}?post_id=$post_id"); } else { }

$post_id = $_GET['post_id'];

?>

<html>

<head>

<title>Mon blog</title>

</head> <body>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<div id="conteneur"> <div id="header"> </div> <h1>Mon blog</h1>

<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> <p><?php echo $post['content']; ?></p> <?php

<strong>Le <?php echo $post['created_on']; ?></strong>

$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> <h5><?php echo $comment['author']; ?>, le <?php echo $comment['created_on']; <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" /> </form> <input type="reset" value="Effacer" />

</html>

</body>

</div>

</div>

Fiche technique

en lui fournissant les donnes issues de la requte (ici en appelant la fonction insert_ comment() avec les donnes dans $_POST), et dterminer la vue afficher, en lui faisant passer les donnes issues du modle. Les PagesControllers regroupent donc l'ensemble des responsibilits de la couche contrleur, ce qui en fait le motif le plus simple mettre en oeuvre dans le cadre du MVC : un contrleur par page, appell directement dans l'url. Nous n'avons mme pas besoin d'utiliser l'orient objet pour cela ! Nous pouvons d'ailleurs remarquer que le code du contrleur pourrait tout fait tre plac dans le mme fichier que la vue, ce qui dans le monde Java est appell
Listing 2. L'exemple 1 la sauce MVC

le MVC modle 1. Ce motif fonctionne trs bien dans les cas les plus simples, mais ds que l'application devient plus complexe, des redondances commencent apparatre dans le code.

Le motif FrontController
Pour viter ce problme de redondances, l'tape suivante selon Martin Fowler est de confier la responsabilit de la prise en charge et de l'analyse de la requte un composant spcialis : le FrontController. Ce composant sera donc l'unique point d'entre de notre application, et dcidera de la suite des vnements. En pratique, ce point d'entre sera

le plus souvent un fichier index.php charg d'instancier le FrontController. L'action raliser (ou la page afficher) sera donc fourni en paramtre 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 structurer 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 reprsenter des action. Une grande partie de la logique de nos PageControllers se retrouve encapsule dans des sous-classes de la classe

URL : http://localhost/show_post.php?post_id=n // fichier blog_model.php static $conn = null; function db_connection() { if ($conn === null) {

<div id="header"> </div>

<h1>Mon blog</h1>

<div id="contenu">

$conn = mysql_connect ("localhost", "root", "password"); mysql_select_db("article_mvc");

<h2><?php echo $post['title']; ?></h2> strong>

<strong>Le <?php echo $post['created_on']; ?></ <p><?php echo $post['content']; ?></p> <h3><?php echo count($comments); ?> commentaire(s)</h3>

} }

return $conn;

function get_post($id) {

$rs = mysql_query("SELECT * FROM posts WHERE id = '$id'", db_connection());

<?php foreach ($comments as $comment) { ?>

<h5><?php echo $comment['author']; ?>, le <?php <p><?php echo $comment['content']; ?></p> <?php } ?> <form name="add-comment" method="post" ?>"> echo $comment['created_on']; ?></h5>

return mysql_fetch_array($rs);

function get_comments($post_id) {

$rs = mysql_query("SELECT * FROM comments WHERE post_id = $comments = array(); '$post_id'", db_connection());

action="<?php echo $_SERVER['PHP_SELF'];

while ($row = mysql_fetch_array($rs)) { } } $comments[] = $row;

<input type="hidden" name="post_id" value="<?php <input type="text" name="author" size="40" /> echo $post['id']; ?>" />

return $comments;

maxlength="50" value="Votre nom" /><br

function insert_comment($comment) { ;)

<textarea name="content" rows="5">Vos <input name="comment_submit" type="submit" <input type="reset" value="Effacer" /> value="Envoyer" /> commentaires</textarea><br />

// oui je sais qu'on doit utiliser mysql_real_escape_string $sql = "INSERT INTO comments SET post_id = '".mysql_escape_ author = '".mysql_escape_string($comment['author']) content = '".mysql_escape_string($comment['content'] created_on = NOW()"; )."', ."', string($comment['post_id'])."',

</html>

</body>

</div>

</div>

</form>

// fichier show_post.php include('blog_model.php'); insert_comment($_POST); if ($_SERVER['REQUEST_METHOD'] == 'POST') { header("location: {$_SERVER['PHP_SELF']}?post_id={$_ die(); POST['post_id']}");

mysql_query($sql, db_connection());

// fichier show_post_view.php <html> <head>

<title>Mon blog</title>

<meta http-equiv="Content-Type" content="text/html; </head> <body> charset=utf-8" />

} else {

$post = get_post($_GET['post_id']); include('show_post_view.php');

$comments = get_comments($_GET['post_id']); }

<div id="conteneur">

3/2007 (21)

MVC

Command.

Les Listings 3 et 4 vous fournissent un exemple d'implmentation. Le modle utilis n'est pas dtaill, car c'est le contrleur qui nous intresse !

La classe Request analyse l'url fournie par l'utilisateur, ce qui nous permet de dterminer le module et la commande souhaite. Le FrontController appelle le fichier correspon-

dant la commande, instancie la sous-classe de Command, et appelle sa mthode execute(). Une fois l'action effectue, la commande appelle le rendu d'une vue (via la mthode

Listing 3. Implmentation d'un FrontController en PHP (et classes associes)


class Request { "{$url}\">redirected</a>.</body></ } html>";

public function getParam($key) {

return filter_var($this->getTaintedParam($key), FILTER_ QUOTES);

SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_

public function out() {

public function getTaintedParam($key) { POST[$key])) {

foreach($this->headers as $key => $value) { } header($key.': '.$value);

if ($this->getRequestMethod() == 'POST' && isset($_ return $_POST[$key]; return $_GET[$key]; }

echo $this->body; }

} else { }

class FrontController {

private $defaults = array('module' => 'home', 'action' => private $request; 'index');

public function getMethod() { }

return $_SERVER['REQUEST_METHOD'];

private $response;

public function __construct() {

public function parseUri() {

$this->request = new Request();

$requestUri = substr($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME'])));

strlen(str_replace('/index.php', '/',

public function dispatch($defaults = null) { $recognized = $this->request->parseUri(); $this->forward($recognized['module'], } $recognized = array_merge($this->defaults, $recognized); $recognized['action']);

$this->response = new Response();

if (empty($requestUri)) return array();

if (strpos($requestUri, '?') !== false) { } else { }

list($path, $queryString) = explode('?', $requestUri); list($path, $queryString) = array($requestUri, '');

public function forward($module, $action) {

if (substr($path, -1) == '/') $path = substr($path, 0, preg_match('#^(?P<module>\w+)(/(?P<command>\w+))?(/ -1);

$command = $this->getCommand($module, $action);

if (isset($matches['id'])) $_GET['id'] = $matches['id']; } return $matches; }

?(?P<id>\w+))?$#', $path, $matches);

public function render($file) { $view = new View(); $this->response->setBody($view-> render($file, $this>response-> getVars()));

$command->execute($this->request, $this->response);

class Response {

public function redirect($url) { }

private $assigns = array(); private $headers = array(); private $body; public function addVar($key, $value) { } $this->assigns[$key] = $value;

$this->response->redirect($url);

public function getResponse() { } return $this->response;

private function getCommand($module, $action) { return new UnknownCommand($this);

public function getVars() { } return $this->assigns;

if (!file_exists($path = "$module/$action.php")) { }

public function setBody($body) { } $this->body = $body; }

require($path);

$class = $action.'Command'; return new $class($this);

public function redirect($url, $permanently = false) { if ($permanently) { } else { } $this->headers['Status'] = '301 Moved Permanently'; $this->headers['Status'] = '302 Found';

class View {

public function render($file, $assigns = array()) { extract($assigns); ob_start(); include ($file);

$this->headers['location'] = $url;

$this->body = "<html><body>You are being <a href=\

ob_end_clean();

$str = ob_get_contents();

www.phpsolmag.org

Fiche technique

render()),

demande une redirection HTTP (via redirect()), ou fait suivre une autre commande (via forward()). Vous remarquerez immdiatement, je l'espre, que l'approche n'est plus du tout la mme que dans nos prcdents exemples : on passe d'un code procdural une approche oriente objet, et la complexit du code utilis croit d'une manire significative. Et c'est l le vrai problme du motif FrontController en PHP : dans de nombreux cas le jeu n'en vaut pas la chandelle et on a tendance rinventer la roue en s'efforant d'appliquer ce motif tel qu'il a t dcrit dans la littrature. En effet, ce motif a t appliqu initialement dans le monde Java, o les contraintes sont totalement diffrentes. Examinons l'interface d'une classe Command en Java :
class Command... public void init(ServletContext

Response.

D'autre part, la classe Request nous permet d'utiliser des URLs plus jolies, du type : http://localhost/module/command/id Toutefois, l'implmentation que je vous propose ne va pas encore assez loin pour justifier la complexit qu'elle apporte. Le support des URLs propres par exemple, pourrait assez aisment tre ajout grce au mod_rewrite d'Apache, et avec des performances bien suprieures ! 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 intrt centraliser ainsi la logique associe au traitement des requtes :
$router = new Zend_Controller_ $router->addRoute('user', new Zend_Controller_Router_ Route(':controller/:action')); Zend_Controller_Router_ Router_Rewrite();

ne peut totalement apporter l'aide de solutions bases sur mod_rewrite. Pour en revenir Martin Fowler, celui-ci distingue un autre intrt possible l'implmentation d'un FrontController : en lui associant le motif InterceptingFilter, on peut plus facilement grer le problme de l'authentification ou du logging : puisque nous avons du code qui doit tre excut chaque requte, cela fait du FrontController un bon candidat pour intgrer ce code.
interface Filter {

public function preFilter();

public function postFilter();

class FrontController { ... ... private $filtersChain = array(); public function addFilter($filter) { } $this->filtersChain[] = $filter;

context, HttpServletRequest response)...

request, HttpServletResponse public void process()...

$router->addRoute('user', new

$router->addRoute('archive',

Route('user/:username')); new Zend_Controller_Router_

public function dispatch($defaults = ... null) {

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 requtes peuvent tre servies par une mme instance de notre sous-classe de Command : chaque requte a son propre thread, mais tous les threads partagent le mme espace mmoire, et donc nos classes doivent tre thread-safe. On utilise donc les paramtres et les retours de mthodes pour passer l'information la commande et l'en faire sortir. D'ailleurs HttpServletRequest fait partie intgrante de J2EE et non d'un framework quelconque. Le fonctionnement de PHP est totalement diffrent, puisqu'il recre son environnement chaque requte ! Et c'est pour cela que PHP peut nous fournir les variables super-globales $_GET, $_POST ou $_SESSION. Une implmentation trop stricte de ce motif, comme l'ont fait les premiers frameworks PHP comme php.MVC, Phrame ou Eocene, largement inspirs du framework Java Struts, n'a donc pas toujours de sens en PHP. En fait, on peut mme arguer que le FrontController est dj fourni par Apache et le moteur de PHP ! Pour autant, ce motif peut tout de mme avoir un intrt : pour cela, il faut que l'implmentation des classes Request et Response par exemple, prsente des fonctionnalits valeur ajoute : c'est ce que j'ai voulu vous montrer en implmentant d'un ct l'assainissement des paramtres de la requte directement dans la classe Request, en utilisant l'extension PECL filter (vous remarquerez que l'on dispose tout de mme de la mthode getTaintedParam pour rcuprer les paramtres non filtrs), et la gestion des en-ttes HTTP dans la classe
5

Route('archive/:year', array('year' => 2006), array('year' => '\d+')));

foreach ($this->filtersChain as $filter->preFilter(); $filter) {

Par ce type de code, la classe Zend _ Controller _ Router nous permet non seulement de faire de la reconnaissance d'URLs complexes, mais galement de gnrer automatiquement les URLs de notre application dans les templates, ce qui nous permet de modifier l'architecture de nos URLs sans avoir aller modifier de nombreux templates. Nous avons donc une vraie valeur ajoute, que l'on

$this->forward($recognized[ ['action']);

'module'], $recognized

foreach (array_reverse($this-> $filter->postFilter();

filtersChain) as $filter) {

Listing 4. Implmentation des sous-classes de Command relatives notre exemple prcdent


class ShowPostCommand extends Command { public function getRequestMethod() { } return METHOD_GET;

public function execute($request, $response) {

$response->setVar('post', PostDAO::findById($request->getParameter('id'))); $response->setVar('comments', CommentDAO::findByPostId($request$this->render('show_posts.php'); >getParameter('id')));

class AddCommentCommand extends Command { public function getRequestMethod() { } return METHOD_POST;

public function execute($request, $response) { $request->getParameter('author'),

CommentDAO::insert($request->getParameter('post_id'), $request->getParameter('content'));

$controller->redirect('/blog/showPost/'.$request->getParameter('post_id'));

3/2007 (21)

MVC

Dans cet exemple d'implmentation, on dfinit une interface Filter que nos filtres devront donc implmenter, avec 2 mthodes preFilter et postFilter, appeler respectivement avant et aprs l'excution de la commande. Mais attention : l encore PHP nous fournit des solutions simples pour faire exactement la mme chose : en utilisant les directives du php.ini auto_prepend_file et auto_append_ file, on peut charger des scripts automatiquement avant et aprs l'inclusion de notre PageController, rglant notre problme du mme coup ! Pour terminer cette analyse critique du motif FrontController, il convient de signaler que le motif Command, tout comme le PageController, devient vite gnant lorsque l'application et en particulier les relations entre les diffrentes 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 apparatre.

pour ne pas montrer des messages d'erreur critiques l'utilisateur. Nous avons ajout la classe ActionController la possibilit de demander automatiquement le rendu d'une vue portant le mme nom que l'action si aucun
Listing 5. L'exemple 1 avec Symfony
class blogActions extends sfActions { public function executeIndex() { } // affichage des derniers billets

rendu ou redirection n'a t demand par l'action elle-mme. Notez enfin que nous utilisons l'API de Rflexion de PHP pour vrifier que l'action demande dans l'URL existe bien dans le contrleur, et surtout qu'elle est bien publique.

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->save(); }

$comment->setContent($this->getRequestParameter('content')); return $this->redirect('blog/show?id='.$post_id);

Les classes ActionController


Pour rpondre ce problme, les dveloppeurs des rcents frameworks Symfony ou Zend Framework ont appliqu la mme recette que RubyOnRails : regrouper les actions concernant un mme type d'entits dans une seule classe. Vous trouverez dans l'exemple 5 un exemple d'implmentation de notre exemple initial avec le framework Symfony. L encore, le modle utilis n'est pas dtaill car hors-sujet. Vous remarquerez que les actions sont maintenant des mthodes publiques dont le nom est prcd de execute. On ainsi une meilleure visibilit du code applicatif de chaque partie de son application. D'autre part, vous remarquerez que dans la mthode executeShow(), nous instancions une proprit $this->post non dclare. En effet, c'est ainsi que l'on assigne des variables au template avec Symfony. Le contenu de la proprit $this->post se retrouve accessible automatiquement dans le template sous la forme de la variable $post. Ce principe a t conserv dans l'exemple d'implmentation d'une super-classe ActionController prsent dans le Listing 6. Nous utilisons pour cela les mthodes magiques __get() et __set(). Notez bien que nous rutilisons les classes Request, Response et View vues dans le Listing 4. Certaines responsabilits du FrontController se retrouvent intgres dans la classe ActionController, et le FrontController se trouve finalement rduit un rle d'encapsulation de l'excution du code : le bloc try/catch nous permet d'attraper les exceptions ventuelles, et de demander le rendu d'une page 404 ou 500
www.phpsolmag.org
} }

// fichier showSuccess.php <html> <head>

<title>Mon blog</title>

</head> <body>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<div id="conteneur"> <div id="header"> </div> <h1>Mon blog</h1>

<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) : ?> >getCreatedOn(); ?></h5> <h5><?php echo $comment->getAuthor(); ?>, le <?php echo $comment<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 <textarea name="content" rows="5">Vos commentaires</textarea><br /> <input name="comment_submit" type="submit" value="Envoyer" /> </form> <input type="reset" value="Effacer" /> nom" /><br />

</html>

</body>

</div>

</div>

Fiche technique

Listing 6. Implmentation d'un ActionController


class FrontController { try { public function dispatch() { $request = new Request(); // lots of stuff after... if (!$this->performed) { hp'); $this->render($this->request->getParam('action').'.p $this->$action();

} catch (Exception $e) { >out();

ActionController::factory($request, $response)->out(); ActionController::rescue($request, $response, $e)}

$response = new Response();

return $this->response;

public function processWithException($e) {

if (in_array(get_class($e), array('UnknownControllerExcep $this->render('404.php'); $this->render('500.php'); tion', 'UnknownActionException'))) {

class ActionController { private $request; private $response;

} else { } }

private $performed;

public static function factory($request, $response) { m('controller'))) {

return $this->response;

if (!file_exists($path = 'controllers/'.$request->getPara throw new UnknownControllerException();

public function render($file) { if ($this->performed) { throw Exception('Un rendu ou une redirection a dj t effectu');

require_once($path);

$className = $request->getParam('controller').'Control $controller = new $className($request, $response); ler';

$view = new View();

$this->response->setBody($view->render($file, $this$this->performed = true; >response->getVars()));

public static function rescue($request, $response, $e) { return $controller->processWithException($e);

return $controller->process();

$controller = new ActionController($request, $reponse);

public function redirect($url) { if ($this->performed) { throw Exception('Un rendu ou une redirection a dj t effectu');

public function __construct($request, $reponse) { $this->request = $request; $this->performed = false; $this->response = $response; }

$this->response->redirect($url); } $this->performed = true;

public function __get($name) { }

private function actionExists($action) { try { $method = new ReflectionMethod(get_class($this), return ($method->isPublic() && !$method} >isConstructor()); $action);

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(); }

catch (ReflectionException $e) { } return false;

// lots of stuff before...

Conclusion
Nous venons de survoler ensemble quelques motifs de conception basiques pour l'implmentation d'une couche contrleur. Bien souvent, les dveloppeurs frachement convertis l'orient objet ont tendance ne plus voir que des solutions objets tous les problmes. Gardez-vous pourtant de trop vite opter pour des solutions comme le FrontController, car PHP et Apache permettent dj de faire beaucoup de choses, et avec des performances bien suprieures. On peut d'ailleurs se risquer avancer que dans les
7

3 couches du MVC, seule la couche modle mrite vraiment dans de nombreux cas une approche oriente objet, ce que Martin Fowler appelle un Rich Domain Model. Toutefois, l'optimisation prmature est souvent source de problmes par la suite, aussi si votre application est rellement complexe, vous gagnerez certainement la dvelopper ou tout du moins la prototyper l'aide d'un framework moderne comme le Zend Framework, 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 problme, il sera toujours temps d'exploiter les ressources de PHP et d'Apache pour amliorer la situation.

RAPHAL ROUGERON
Raphal Rougeron est dveloppeur web la Chambre de Commerce et d'Industrie de Paris, pour laquelle il a ralis diffrentes applications mtiers en PHP. Dans le cadre de son travail, il a cr le framework Stato, publi sous licence MIT. Pour contacter l'auteur : goldoraf@gmail.com

3/2007 (21)

Das könnte Ihnen auch gefallen