le motif mvc - 03.2007

Upload: mouadzin

Post on 14-Oct-2015

8 views

Category:

Documents


0 download

TRANSCRIPT

  • 3/2007 (21)

    Fiche technique

    1

    MVC

    www.phpsolmag.org 2

    S'il est un point commun aux trs nom-breux 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 affi-liation 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'exempleNous avons tous dbut en PHP par des pages ressemblant l'exemple 1. Aprs tout, l'ori-gine PHP tait fait pour a : intgrer de la lo-gique 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 de-vient vite un enfer. Il est pourtant trs simple de refactoriser ce code d'une faon plus pro-pre, comme l'illustre l'exemple 2 : un fichier contenant la logique d'accs aux donnes, un fichier HTML avec un tout petit peu de lo-gique de prsentation, et un fichier faisant le lien entre les 2. Le code devient plus facile lire et maintenir, et on peut tout fait modi-

    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 remar-querez 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 res-ponsabilits du code afin de bien le comparti-menter 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 main-tenance de votre code.

    N'esprez d'ailleurs pas trouver dans cet ar-ticle 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 mo-dle 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 dvelopp par Trygve Reenskaug pour le langage Smalltalk la fin des annes 70. Le but de ce framewok tait d'isoler le co-de rgissant l'interface graphique du code de l'application proprement dit. Ainsi, un chan-gement au niveau de l'interface graphique ne

    ncessitait pas de modifier la logique mtier de l'application.

    Le MVC divise donc le code d'une applica-tion selon 3 responsabilits :

    Modle : encapsule la logique mtier, l'ac-cs et la manipulation des sources de don-nes;

    Vue : prsente l'utilisateur les donnes obtenues partir du modle;

    Contrleur : interprte les requtes de l'utilisateur, interagit avec le modle et s-lectionne la vue utiliser.

    Dtaillons maintenant les 2 responsabilits les plus faciles apprhender. La vue tout d'abord, reprsente la rponse de l'application une re-qute. 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 in-tgre 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 fait le plus souvent en faisant gnrer des donnes sous forme de XML par le modle, et en ap-pliquant une feuille de style XSLT ces don-nes. On peut galement utiliser le motif Cus-tom Tag, dans lequel votre moteur de rendu HTML s'appuie sur des tags XML spcifiques placs dans le template pour intgrer 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 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.

    Le motif MVC

    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.

    Niveau de difficult

    Contrleurs et URLs

  • 3/2007 (21)

    Fiche technique

    1

    MVC

    www.phpsolmag.org 2

    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 appli-quant 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 re-fonte 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 logi-que 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 nu-mro, 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 con-trleur et de la vue, mais ces derniers dpen-dent malgr tout du modle, puisqu'ils doivent pouvoir y accder pour rcuprer des donnes. Deux conceptions s'affrontent alors : une pre-mire stratgie est que le contrleur demande les donnes afficher au modle et les fait pas-ser 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'ap-proche pull a tendance recoupler la vue et le modle, et le dveloppeur a vite fait de draper et de dcharger le contrleur de ses responsabi-lits. 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 con-trleur. Il est en effet le plus difficile cerner, principalement parce que ce terme de contr-leur englobe diffrentes significations dans plu-sieurs motifs. Dans un premier temps, nous d-finirons 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 m-thodes 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.

    Le motif PageControllerDans le Listing 2, nous avons appliqu le mo-tif PageController : un contrleur par page de notre application. Ses responsabilits sont les suivantes : analyser la requte, c'est dire

    en extraire les informations ncessaires l'excution de l'action demande (dans no-tre 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,

    Listing 1. Exemple de ce qu'il ne faut pas faire ;)

  • 3/2007 (21)

    Fiche technique

    3

    MVC

    www.phpsolmag.org 4

    Listing 2. L'exemple 1 la sauce MVC

    URL : http://localhost/show_post.php?post_id=n

    // fichier blog_model.php

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

    }

    // fichier show_post_view.php

    Mon blog

    Mon blog

    Le

    commentaire(s)

    , le

  • 3/2007 (21)

    Fiche technique

    3

    MVC

    www.phpsolmag.org 4

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

    class Request {

    public function getParam($key) {

    return filter_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\w+)(/(?P\w+))?(/

    ?(?P\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 = "You are being redirected.";

    }

    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($file) {

    $view = new View();

    $this->response->setBody($view-> render($file, $this-

    >response-> getVars()));

    }

    public function redirect($url) {

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

    }

    public function getResponse() {

    return $this->response;

    }

    private function getCommand($module, $action) {

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

    return new UnknownCommand($this);

    }

    require($path);

    $class = $action.'Command';

    return new $class($this);

    }

    }

    class View {

    public function render($file, $assigns = array()) {

    extract($assigns);

    ob_start();

    include ($file);

    $str = ob_get_contents();

    ob_end_clean();

    Command. Les Listings 3 et 4 vous fournissent un exemple d'implmentation. Le modle uti-lis 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 dtermi-ner 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

  • 3/2007 (21)

    Fiche technique

    5

    MVC

    www.phpsolmag.org 6

    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 uti-lis 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

    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 mme ins-tance de notre sous-classe de Command : cha-que requte a son propre thread, mais tous les threads partagent le mme espace mmoi-re, et donc nos classes doivent tre thread-sa-fe. On utilise donc les paramtres et les re-tours 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 implmenta-tion trop stricte de ce motif, comme l'ont fait les premiers frameworks PHP comme php.MVC, Phrame ou Eocene, largement inspirs du fra-mework 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'impl-mentation 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

    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 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 grce au mod_rewrite d'Apa-che, 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 centra-liser ainsi la logique associe au traitement des requtes :

    $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 gnrer 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 ajoute, que l'on

    ne peut totalement apporter l'aide de solu-tions bases sur mod_rewrite.

    Pour en revenir Martin Fowler, celui-ci dis-tingue un autre intrt possible l'implmenta-tion d'un FrontController : en lui associant le mo-tif 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;

    }

    public function dispatch($defaults =

    null) {

    ...

    foreach ($this->filtersChain as

    $filter) {

    $filter->preFilter();

    }

    $this->forward($recognized[

    'module'], $recognized

    ['action']);

    foreach (array_reverse($this->

    filtersChain) as $filter) {

    $filter->postFilter();

    }

    }

    }

    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-

    >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'));

    }

    }

  • 3/2007 (21)

    Fiche technique

    5

    MVC

    www.phpsolmag.org 6

    Dans cet exemple d'implmentation, on d-finit une interface Filter que nos filtres de-vront donc implmenter, avec 2 mthodes preFilter et postFilter, appeler respecti-vement avant et aprs l'excution de la com-mande.

    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 automatique-ment avant et aprs l'inclusion de notre Page-Controller, 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 Page-Controller, devient vite gnant lorsque l'ap-plication 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.

    Les classes ActionControllerPour rpondre ce problme, les dve-loppeurs des rcents frameworks Symfony ou Zend Framework ont appliqu la mme re-cette 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 exem-ple initial avec le framework Symfony. L en-core, le modle utilis n'est pas dtaill car hors-sujet.

    Vous remarquerez que les actions sont main-tenant des mthodes publiques dont le nom est prcd 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 mthode executeShow(), nous instancions une proprit $this->post non dclare.

    En effet, c'est ainsi que l'on assigne des va-riables au template avec Symfony. Le contenu de la proprit $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'implmentation d'une super-classe ActionController prsent dans le Listing 6.

    Nous utilisons pour cela les mthodes magi-ques __get() et __set(). Notez bien que nous rutilisons les classes Request, Response et View vues dans le Listing 4.

    Certaines responsabilits du Front-Controller se retrouvent intgres dans la classe ActionController, et le FrontController se trouve finalement r-duit un rle d'encapsulation de l'excu-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 mme nom que l'action si aucun

    rendu ou redirection n'a t demand par l'action elle-mme. Notez enfin que nous uti-lisons 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.

    Listing 5. L'exemple 1 avec Symfony

    class blogActions extends sfActions {

    public function executeIndex() {

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

    }

    }

    }

    // fichier showSuccess.php

    Mon blog

    Mon blog

    Le

    commentaire(s)

    , le

  • 3/2007 (21)

    Fiche technique

    7

    Listing 6. Implmentation d'un ActionController

    ConclusionNous venons de survoler ensemble quelques mo-tifs de conception basiques pour l'implmenta-tion d'une couche contrleur. Bien souvent, les dveloppeurs frachement convertis l'orient objet ont tendance ne plus voir que des solu-tions 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

    3 couches du MVC, seule la couche modle m-rite vraiment dans de nombreux cas une appro-che 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 dve-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 problme, il sera tou-jours temps d'exploiter les ressources de PHP et d'Apache pour amliorer 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 (!file_exists($path = 'controllers/'.$request->getPara

    m('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($file) {

    if ($this->performed) {

    throw Exception('Un rendu ou une redirection a dj

    t effectu');

    }

    $view = new View();

    $this->response->setBody($view->render($file, $this-

    >response->getVars()));

    $this->performed = true;

    }

    public function redirect($url) {

    if ($this->performed) {

    throw Exception('Un rendu ou une redirection a dj

    t effectu');

    }

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

    $this->performed = true;

    }

    private function actionExists($action) {

    try {

    $method = new ReflectionMethod(get_class($this),

    $action);

    return ($method->isPublic() && !$method-

    >isConstructor());

    }

    catch (ReflectionException $e) {

    return false;

    }

    }

    }

    RAPHAL ROUGERONRaphal Rougeron est dveloppeur web la Cham-

    bre de Commerce et d'Industrie de Paris, pour la-

    quelle il a ralis diffrentes applications mtiers

    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]