IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Date de publication : 05/02/2008 , Date de mise à jour : 09/04/2009


III. Notre framework MVC
III-A. Contrôleur
III-B. Le routage
III-B-1. Définition des routes
III-B-2. Reconnaissance de l'url et routage
III-C. Le modèle
III-D. La vue
III-E. La session
III-F. Filtres before et after
III-G. Redirection
III-H. Les helpers
III-H-1. Liens
III-H-2. Pager
III-I. Exceptions


III. Notre framework MVC


III-A. Contrôleur

Nous allons créer notre contrôleur. Il sera le coeur de notre espace membre.
Squelette de notre contrôleur user

class userController {

}
Celui-ci consiste en une simple classe PHP, dont chaque méthode publique est une action qui sera appelable par une url.
Ajoutons maintenant nos différentes actions :
Définition des actions

class userController {
    
    /**
     * Constructeur
     */
    public function __construct() {}
    
    /**
     * Connexion
     */
    public function login() {}
    
    /**
     * Déconnexion
     */
    public function logout() {}
    
    /**
     * Enregistrement
     */
    public function register() {}
    
    /**
     * Affichage du profil du membre
     */
    public function profil() {}
}
L'ensemble des actions nécessaires pour gérer l'espace membre sont présentes.

La classe va utiliser différentes sources de données, notamment la base de données (notre modèle), les sessions, etc. Lorsque que le traitement sera terminé, elle enverra sur la sortie spécifiée (notre vue) le résultat du traitement. Ajoutons ces éléments à notre classe :
Notre contrôleur complet

class userController {
    
    /**
     * Notre modèle
     */
    private $db;
    
    /**
     * Notre vue
     */
    private $view;
    
    /**
     * La session
     */
    private $session;
    
    /**
     * Les paramètres POST et GET
     */
    private $params;
    
    /**
     * Un tableau qui contiendra ce qu'on enverra à la vue.
     * La vue ne disposera que de ces données et aucune autre
     */
    private $data;
    
    /**
     * Constructeur
     */
    public function __construct() {}
    
    /**
     * Méthode privée appelée par chaque action et qui se chargera de la vue.
     */
    private function render() {}
    
    /**
     * Connexion
     */
    public function login() {}
    
    /**
     * Déconnexion
     */
    public function logout() {}
    
    /**
     * Enregistrement
     */
    public function register() {}
    
    /**
     * Affichage du profil du membre
     */
    public function profil() {}
}
Dans ces quelques lignes, il y a toute l'architecture de notre espace membre. Toutes les variables globales sont accessibles à chaque action. Reste maintenant à alimenter ces variables, et à rendre nos actions appelables par un visiteur.

Mais avant tout, regardons de plus prêt notre classe. Nous l'avons créée pour gérer un espace membre, or un site n'est jamais constitué de si peu. Il va nous falloir utiliser d'autres contrôleurs pour les autres contextes, par exemple sur une galerie d'images, il nous faudra gérer les images. Pour cela nous allons créer un contrôleur imageController qui devra lui aussi utiliser les sessions, les paramètres, les vues, etc. Grâce à l'héritage, nous allons pouvoir créer une classe Controller mère dont chaque controller héritera. Cette classe mère se chargera des variables et méthodes communes. Ainsi, nous aurons :
La classe Controller dont hérite tous les autres contrôleurs

abstract class Controller {
    
    /**
     * Notre modèle
     */
    private $db;
    
    /**
     * Notre vue
     */
    private $view;
    
    /**
     * La session
     */
    private $session;
    
    /**
     * Les paramètres POST et GET
     */
    private $params;
    
    /**
     * Un tableau qui contiendra se qu'on enverra à la vue.
     * La vue ne disposera que de ces données et aucune autre
     */
    private $data;
    
    /**
     * Constructeur
     */
    public function __construct() {}
    
    /**
     * Méthode privée appelée par chaque action et qui se chargera de la vue.
     */
    private function render() {}
}
Ce qui permet de simplier nos contrôleurs :
Notre contrôleur simplifié

class userController extends Controller {
    
    /**
     * Connexion
     */
    public function login() {}
    
    /**
     * Déconnexion
     */
    public function logout() {}
    
    /**
     * Enregistrement
     */
    public function register() {}
    
    /**
     * Affichage du profil du membre
     */
    public function profil() {}
}
Le contrôleur image

class imageController extends Controller {
    
}
Nous allons maintenant tester notre contrôleur. Pour cela, il faut créer un index.php qui se chargera d'instancier le bon contrôleur, puis d'appeler la bonne action en fonction de la requète de l'utilisateur.
Fichier index.php pour notre permier test

require_once 'controller.php';
require_once 'userController.php';

if (isset($_GET['controller'])) {
    $controller = $_GET['controller'];
} else {
    // On peut mettre un contrôleur par défaut
}
if (isset($_GET['action'])) {
    $action = $_GET['action'];
} else {
    // On peut mettre une action par défaut
}
$oController = new $controller;
$oController->$action;
Puis, dans notre contrôleur user, ajoutons l'action
Action helloworld pour notre test

public function helloworld() {
    // Notre vue n'étant pas encore écrite, nous utiliserons un simple echo
    echo 'Hello World';
}
Il vous reste à appeler l'adresse correspondante, par exemple en local, si les fichiers sont dans le répertoire framework, nous appelerons http://localhost/framework/index.php?controller=user&action=helloworld

Notre contrôleur est presque fini, nous allons maintenant nous occuper des autres parties de notre framework.


III-B. Le routage

Nous venons de voir un exemple d'appel d'une action de notre contrôleur user : http://localhost/framework/index.php?controller=user&action=helloworld
Cette adresse n'est pas belle du tout, et plutôt lourde. Nous allons réécrire l'index.php de façon à utiliser l'url_rewriting. Cette fonctionnalité qui est un module d'apache est disponible sur la plupart des hébergements. Afin de permettre à notre index.php de gérer les paramètres de l'url, nous allons tout d'abord écrire un fichier .htaccess que l'on placera dans le répertoire "framework" :
Fichier .htaccess pour la réécriture d'url

RewriteEngine on
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
L'expression régulière ^(.*)$ signifie simplement "tout caractère". En sachant que nous somme déjà dans le répertoire "framework", "tout caractère" signifie tout ce qui suis "/framework".
Par exemple, avec l'URL http://localhost/framework/user/helloworld , on obtiendrait la redirection http://localhost/framework/index.php?url=/user/helloworld .
On commence à voir l'intérêt de cette méthode, car la variable $_GET['url'] contiendra alors notre contrôleur et notre action sous la forme 'controller/action'. On pourrait en rester là, on reprend l'index.php, on applique un explode sur la variable $_GET['url'] et tout roule. Mais ça serait triste de ne pas aller plus loin.

La réécriture d'URL permet d'une part d'obtenir des URL plus agréable pour les internautes, mais aussi (et surtout) plus intéressante au niveau des moteurs de recherche.
En efet, la forme et le contenu de l'url constitue une part importante du niveau de référencement de votre page, c'est pourquoi une page avec une adresse comme ceci http://www.articles.com/le-mvc-pour-les-nuls-15/routage-IIIB représentant le chapitre III-B routage de l'article avec un id 15 aura plus les faveurs des moteurs de recherche que http://www.articles.com/?id=15&chap=IIIB

Avant d'étudier notre système de routage, revenons un instant sur notre htaccess. Tel qu'il est, il nous serais impossible de récupérer nos images, style et éventuels script car tout est rediriger vers index.php.
Nous allons donc ajouter des filtres spécifiques à ces ressources qui nous redirigera vers un répertoire "public".

RewriteEngine on

# Acces direct aux ressources publiques
RewriteRule script/(.*)$ public/script/$1 [L]
RewriteRule style/(.*)$ public/style/$1 [L]
RewriteRule images/(.*)$ public/images/$1 [L]

RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]

III-B-1. Définition des routes

Même si ces urls sont entièrement personnalisées, elles ne sont pas aléatoires. Chaque patron d'URL doit être définit afin d'être routé vers le bon couple contrôleur/action. Nous allons créer le fichier routes.php dans un répertoire config.
Fichier routes.php

$routes = array(
    
    array(
        'url' => '/:controller/:action',
        'params' => array(),
        'requirements' => array(
            'controller' => '[a-zA-Z0-9]*',
            'action' => '[a-zA-Z0-9]*'
    )
)
return $routes;
Explication des paramètres
  • url correspond au patron que prendra l'url.
  • params correspond à des paramètres supplémentaires qu'on souhaiterais envoyer manuellement (y compris le contrôleur et/ou l'action)
  • requirements correspond à des expressions régulières permettant de définir chaque élément de l'url.
Corsons un peu en créant une route spéciale pour notre couple user/helloworld
Route pour notre test helloworld

$routes = array(
    
    array(
        'url' => 'bonjour-le-monde',
        'params'=>array(
            'controller'=>'user',
            'action'=>'helloworld'
        ),
        'requirements'=> array()
    )
)
return $routes;
En tapant http://localhost/bonjour-le-monde nous accèderons bien à notre couple user/helloworld.

Prenons un dernier exemple plus complexe :
Exemple d'une route complexe

$routes = array(
    
    array(
        'url' => '/[a-zA-Z0-9\-]*\-:idarticle/[a-zA-Z0-9\-]*\-:idchapitre',
        'params'=>array(
            'controller'=>'article',
            'action'=>'show'
        ),
        'requirements'=> array(
            'idarticle' => '[0-9]*',
            'idchaptire' => '[a-zA-Z0-9]*'
        )
    )
)
return $routes;
L'adresse http://www.articles.com/le-mvc-pour-les-nuls-15/routage-IIIB nous amènerais au contrôleur article, à l'action show, avec en paramètres idarticle et idchapitre.

warning Attention à avoir toujours au moins le contrôleur et l'action définit.
S'il vous ne souhaitez pas les mettre dans l'url, il faudra obligatoirement qu'ils apparaissent dans les params.
idea Il n'est pas obligatoire de définir une expression régulière dans requirements pour chaque variable contenue dans l'url, mais dans ce cas, ça sera \W qui sera pris par défaut pour cette variable, ce qui n'autorise que les caractères alphanumériques.
Nous avons donc une convention pour définir nos routes, il nous fait maintenant un traducteur permettant de trouver la bonne route pour une URL


III-B-2. Reconnaissance de l'url et routage

Pour cela, nous allons écrire une classe Route qui se chargera de comprendre l'URL, et une classe Dispatcher qui appelera le bon couple contrôleur/action.

Le méthode recognizeURL de la classe Route va donc prendre le paramètre $_GET['url'] ainsi que l'ensemble des routes définit dans routes.php, puis va chercher une correspondance. S'il trouve, il nous renverra un tableau contenant l'ensemble des paramètres liées à cette route (ceux dans "params" et ceux définit directement dans l'url)
Fonction de reconnaissance de l'url

class Route {

    /**
     * Construit un tableau de paramètres à partir d'une URL
     * @return 
     */
    public static function recognizeURL($url) {
		
        $routes = require './app/config/routes.php';
        $tabUrl = explode('/', $url);

        foreach ($routes as $route) {

            $routeUrl = substr($route['url'], 1, strlen($route['url'])-1);
			 
            // 1er test, le nombre de params
            $tabRouteUrl = explode('/', $routeUrl);
            if (count($tabRouteUrl) != count($tabUrl)) {
                continue;
            }

            // ensuite, test si la regexp de l'url est bonne
            preg_match_all("/:([a-zA-Z0-9]*)/", $routeUrl, $neededParams);
            $url_regexp = $routeUrl;
            foreach ($neededParams['1'] as $p) {
                // On cherche si une regexp est definit dans la route
                $regexp = (isset($route['requirements'][$p]) ? $route['requirements'][$p] : "\W");
                $regexp = '(?P<'.$p.'>'.$regexp.')';
                $url_regexp = preg_replace("/(:".$p.")/", $regexp, $url_regexp);
				
            }
            $url_regexp = '#^'.$url_regexp.'$#i';
            if (preg_match($url_regexp, $url, $matches)) {
                foreach($matches as $key => $match) {
                    if (is_int($key)) {
                        unset($matches[$key]);
                    }
                }
                if (! isset($route['params'])) {
                    $route['params'] = array();                
                }
                return array_merge($matches, $route['params']);
            }
		}
	}
}
warning Le système de reconnaissance d'url s'arrètera dès qu'il trouve une route valide, l'ordre des routes dans le fichier routes.php est donc très important. Première écrite, première servie.
Maintenant que nous avons décortiqué notre url, nous avons (logiquement) les informations nécessaires pour appeler le bon couple contrôleur/action ainsi que les paramètres.
La classe Dispatcher

class Dispatcher {
	
    /**
     * Le dispatcher qui va lancer l'action en fonction des paramètres
     * @return void
     */
    public function dispatch() {
        $options = Route::recognizeURL($_GET['url']);
        if (empty($options['controller']) || (empty($options['action']))) {
            throw new Exception ('Impossible de trouver une route pour cette URL');
        }
		
        // On s'interesse maintenant aux paramètres en eux mêmes (POST et GET)
        // Les GET sont déjà contenus en parti dans $options, mais il faut traiter ceux ecrits par ?
        $params = array_merge($options, $_GET, $_POST);

        // On enlève notre url que l'on a déjà traitée
        unset($params['url']);
		
        // On insert notre controller
        if (file_exists('./app/controller/'.$params['controller'].'Controller.php')) {
            require_once './app/controller/'.$params['controller'].'Controller.php';
        } else {
            throw new Exception('Controleur '.$params['controller'].' introuvable');
        }

        $controller = $params['controller'].'Controller';
        if ( ! class_exists($controller)) {
            throw new Exception ('Controller introuvable');	
        }
    
        $oController = new $controller;
        $oController->setParams($params);
		
        if ( ! method_exists($oController, $params['action'])) {
            throw new Exception ('Action introuvable');
        }
        $oController->{$params['action']}();
	}
}
Nous introduisons ici une méthode de notre contrôleur setParams qui se charge de transmettre tous les paramètres de l'url, ainsi que les superglobales $_GET et $_POST.
Il faut donc l'ajouter à notre classe Controller.
Méthode à ajouter à la classe Controller pour gérer les paramètres

/**
 * Envoie des params
 */
public function setParams ($params) {
    $this->params = $params;
}
idea Vous trouverez par ce lien les sources de notre framework dans l'état actuel ou il est, avec un exemple simple maintenant en pratique ce que nous avons vu.
Soyez sûr de maîtriser son code actuel avant de continuer.

III-C. Le modèle

Pour comprendre le but de cette partie, il faut bien comprendre le vocabulaire associé. Le modèle n'est pas dépendant de la source de données, ni du moyen d'y accéder. Nous avons ainsi :

  • Une source de données (XML, BDD, texte, etc)
  • Une accès à cette source (PDO, DOM, etc)
  • La définition du modèle
Ainsi, même si le modèle est liè - à l'utilisation - à sa source de données, il n'est pas dépendant de celle-ci.
Vous utilisez sûrement habituellement la source de données et sa méthode d'accès. Qu'est donc ce "modèle" alors ?

Un modèle est une entité définit par des caractéristiques propres. La mémorisation de ces caractéristiques suffira à sauvegarder notre entités. Par exemple, le modèle "Homme" à l'échelle insitutionnelle sera caractérisé par un numéro de carte d'identité, de sécu, etc... A l'échelle biologique, la principale caractéristique de "Homme" sera sont ADN.

On voit d'une part que la définition d'un modèle dépend du contexte, et d'autre part, que les modèles peuvent hériter des caractéristiques d'autres modèles (par exemple, "Homme" hérite de "Espèce").

Pourquoi utiliser ces modèles ?
  • On détache l'entité de son espace de stockage
  • On n'a plus de SQL à écrire
  • Une entité est intuitif à gérer (create, delete, update deviennent naturels)
Nous aurons ici un modèle user, contenant un id pour les distinguer, un login, un mot de passe et une adresse mail.
De retour dans notre code, nous allons créer une classe pour définir notre modèle.
Mais avant cela, on peut réfléchir à ce qu'il nous faudra dedans. Nous avons déjà parler des méthodes create, delete, update ci-dessus. Ces méthodes ne sont pas spécifiques à notre modèle user. Nous allons donc créer une class model dont hériteront nos modèles, et qui contiendra les méthodes communes.
Ensuite, il faut faudra des setters et getters, ainsi d'un tableau data contenant les données. N'oublions pas que chaque entité aura des caractéristiques propres à conserver. Nous utiliserons les méthodes magiques __set et __get.
Enfin, de manière à automatiser le plus possible, nous allons écrire une fonction qui récupère les noms des colonnes (c'est à dire le nom des caractéristiques) pour le modèle. Ceci pour éviter d'avoir à le préciser nous même dans chaque modèle.

class model {
    private $data;

    public function __construct() {}
    
    // save regroupe create et update
    public function save() {}
    
    public function delete() {}
    
    public function __get() {}
    public function __set() {}
    public function __isset() {}
    public function __unset() {}
    
    public function query_columns() {}
}

class user extends model {
}
Notre modèle est indépendant de la source de données. Dans la pratique, cela signifie que nous allons créer une classe pour appeler les méthodes spécifiques à la source de données choisis.
Pour l'exemple, j'ai choisis une base MySQL, interrogée avec PDO.
Il faut bien comprendre que le but de cette classe est seulement d'offrir un découplage et non pas de d'ajouter des traitements quels qu'ils soient.
Elle sera accessible par un singleton qui permet de ne créer qu'une seule fois la connexion.
Notre classe DB

class db {
	
	private $dsn;
	private $user;
	private $pass;
	
	private $pdo;
	
	private $pdoStatement;
	
	private static $instance;
	
	public static function getInstance($config) {
		if ( ! isset(self::$instance)) {
			self::$instance = new db($config);
		}
		return self::$instance;
	}
	
	public function __construct($config) {
		
		$this->dsn = $config['dsn'];
		$this->user = $config['user'];
		$this->pass = $config['pass'];
	}
	
	public function connect() {
		$this->pdo = new PDO($this->dsn, $this->user, $this->pass);
	}
	
	public function disconnect() {
		$this->pdo = null;
	}
	
	public function query($query, $params=array()) {
		if ($this->pdo === null) {
			$this->connect();
		}
		$this->pdoStatement = $this->pdo->prepare($query);
		$this->pdoStatement->setFetchMode(PDO::FETCH_ASSOC);
		$this->pdoStatement->execute($params);
	}
	
	public function lastId() {
		return $this->pdo->lastInsertId();
	}
	
	public function fetchAll() {
		return $this->pdoStatement->fetchAll();
	}
}
Une telle classe peut être modifiée en quelques minutes pour changer les méthodes d'accès (par exemple pour utiliser directement les fonctions mysql_*).

Maintenant que nous avons nos méthodes d'accès à la base, retournons à notre modèle générique, et ajoutons lui un objet de notre classe DB.

class model {
    private $data;
    private $db;

    public function __construct($params=array()) {
        $this->data = $params;
		
		$config = require './app/config/config.php';
		$this->db = db::getInstance($config);
    }
    
    //....
    
    //.....
}
Nous introduisons un fichier config, placé dans app/config/
Fichier config.php

<?php
return array (

	'dsn' => 'mysql:host=localhost;dbname=framework',
	'user' => 'root',
	'pass' => 'ors2008'
);
?>
idea Encore une étape de franchis pour notre framework.
Vous trouverez par ce lien le framework correspondant à ce stade du cours.
Vous y trouverez de plus un rapide exemple d'utilisation de notre espace membre.

III-D. La vue

Le vue ayant pour seul but d'être affichée, les données qu'elle utilisera seront explicitement envoyées par le contrôleur sans modification ultérieure possible.
Dans notre classe Controller, nous avions défini le tableau private $data. Ce tableau sera le seul envoyé à la vue. Notre contrôleur le remplira donc, puis l'enverra à la vue juste avant l'affichage. Pour faciliter son remplissage, nous allons ajouter quatres méthodes "magiques" à notre classe Controller :

/**
 * Setter et getter
 */
 protected function __set($key, $value) {
    $this->data[$key] = $value;
}

protected function __get($key) {
    if (isset($this->data[$key])) {
        return $this->data[$key];
    }
    return null;
}
	
/**
 * Isset et unset
 */
protected function __isset($key) {
    return isset($this->data[$key]);
}
	
protected function __unset($key) {
    unset($this->data[$key]);
    return null;
}
Ainsi, à l'intérieur de nos contrôleur, nous pourrons utiliser $this->une_variable pour remplir notre tableau. (Attention à ne pas redéfinir les attributs de la classe, comme $data, $db, etc).

Créons maintenant notre classe View.
Nous voulons avoir la main sur le fichier template utilisé, mais aussi et afin de limiter nos efforts, de définir un template par défaut dépendant du contrôleur et de l'action en cours.
De plus, nous voulons pouvoir définir un fichier patron qui serait commun à tous les templates et choisir de l'utiliser ou pas.
Squelette de notre class View

class View {

    /**
     * Notre tableau de données provenant de la vue
     */
    private $data;

    /**
     * Fonction principale pour l'affichage "standard"
     * @return void
     * @param $data Array
     * @param $params Array
     */
    public function render( $data, $params, $base, $template ) {
        $this->data = $data;
        $template = './view/'.$template.'.tpl.php';
        if (!file_exists($template)) {
            throw new Exception('Fichier template '.$template.' introuvable');
        }

        ob_start();
        require_once $template;
        $content = ob_get_contents();
        ob_end_clean();

        if ($base != null) {
            $base = './view/layout/'.$base.'.tpl.php';

            if (!file_exists($base)) {
                throw new Exception('Fichier base '.$base.' introuvable');
            }
            $this->layout_content = $content;
            ob_start();
            require_once $base;
            $content = ob_get_contents();
            ob_end_clean();
        }
        echo $content;
    }
	
	/**
	 * Un Getter, mais PAS de Setter (on est dans la vue hein !!
	 * Il sera utilisé dans le fichier de template
	 */
	protected function __get($key) {
		if (isset($this->data[$key])) {
			return $this->data[$key];
		}
		return null;
	}
	
	protected function __isset($key) {
		return isset($this->data[$key]);
	}
}
Paramètres de la fonction render
  • $data => Tableau de données provenant du contrôleur
  • $params => Tableau des paramètres communs à toute l'application (url, GET et POST)
  • $base => Patron commun à plusieurs templates
  • $template => Fichier de template pour ce rendu
Si nous appelons directement la fonction render depuis notre propre contrôleur, il faudra indiquer ces paramètres à chaque fois. De plus, notre contrôleur deviendrait trop dépendant de cette vue.
Ajoutons dans notre classe contrôleur un intermédiaire :
méthode render de la classe Controller

/**
 * 
 * @return 
 * @param $params Object
 */
public function render($base=null, $template=null) {

    $this->view = new View();

	if ($template == null) {
        $template = $params['controller'].'/'.$params['action'];
    }
    $this->view->render($this->data, $this->params, $base, $template);
}
Enfin, nous allons créer les répertoires app/view pour nos templates et app/view/layout pour les patrons communs. Si vous avons bien regardé la méthode render de la classe view, vous aurez noter que les templates seront stockés selon l'arborescence app/view/:controller/:action.php.

N'oubliez pas d'inclure le fichier view.php dans l'index.php

idea Comme à chaque grande étape, vous trouverez par ce lien le framework intégrant la vue.

III-E. La session

Nous allons simplement ajouter une abstraction aux sessions PHP tradionnelles de façon à autoriser l'utilisation d'autres types de support de session.
Nous utilisons l'interface ArrayAccess de façon à avoir accès aux données de session par $session['my_variable'] par les méthodes offetsetGet et offsetSet.
Notre classe session simple

class PHPSession implements ArrayAccess {
	
	public function start() {
		session_start();
	}
	
	public function stop() {
		$_SESSION = array();
        session_unset();
		session_destroy();
	}
	
	public function getSessionId() {
		return session_id();
	}
	
	public function offsetExists($offset)
    {
        return isset($_SESSION[$offset]);
    }
    
    public function offsetGet($offset)
    {
        if ($this->offsetExists($offset)) return $_SESSION[$offset];
        return null;
    }
    
    public function offsetSet($offset, $value)
    {
        $_SESSION[$offset] = $value;
        return;
    }
    
    public function offsetUnset($offset)
    {
        if ($this->offsetExists($offset)) unset($_SESSION[$offset]);
        return;
    }
}
Comme pour la vue, nous devons inclure notre fichier session.php dans l'index.php et créer une instance de Session dans le constructeur de la classe Controller.
Création de notre session dans la classe Controller

$this->session = new PHPSession;
$this->session->start();

III-F. Filtres before et after

Imaginez que vous définissiez une action, par exemple l'action edit qui permettra à chaque membre de modifier son profil. Vous allez alors, au début de l'action, vérifier si l'internaute est bien connecté.
Imaginez que vous ayez aussi un menu commun à plusieurs pages, dont les éléments sont contenus dans la base de donnée et qu'il faut donc récupèrer à chaque page. Il serait laborieux et peu rigoureux de remettre le même code au début ou à la fin de chaque action.

Nous allons à la place définir 2 filtres : before et after, qui s'executeront respectivement avant et après l'action.

Commençons par définir le moyen d'exprimer ces filtres dans nos contrôleurs :
Expression de nos filtres

$this->filter = array(
    'before' => array(
        'login' => array('edit'),
        'menu' => array('login', 'logout', 'edit', 'profil', 'register')
    ),
    'after' => array(
        'filtreX' => array('action1', 'action2')
    )	
);
Nous allons devoir écrire une méthode pour traiter ce tableau et appeler les bons filtres, puis notre méthode.
On pourrait écrire cette méthode dans le dispatcher, vu que l'appel à notre fonction y est déjà, mais pour des raisons pratiques, nous l'écrirons dans la classe Controller. Nous aurons ainsi déjà tous les filtres et les méthodes à disposition.
Méthode runAction de la classe Controller

/**
 * Execute nos filtres et notre action
 * @return void
 */
public function runAction($action) {

    // Les filtres befores
    if (isset($this->filter['before']) && (is_array($this->filter['before']))) {
        foreach ($this->filter['before'] as $key => $value) {
            if (in_array($action, $value)) {
                if ( ! method_exists($this, $key)) {
                    throw new Exception ('Filtre before '.$key.' inextistant');
                } else {
                    $this->$key();
                }
            }
        }
    }
    // Notre action principale
    if ( ! method_exists($this, $action)) {
        throw new Exception ('Action '.$action.'introuvable');
    }
    $this->$action();
    // Nos filtres after
    if (isset($this->filter['after']) && (is_array($this->filter['after']))) {
        foreach ($this->filter['after'] as $key => $value) {
            if (in_array($action, $value)) {
                if ( ! method_exists($this, $key)) {
                    throw new Exception ('Filtre after '.$key.' inextistant');
                } else {
                    $this->$key();
                }
            }
        }
    }
}
Nous remplaçons donc l'ancien appel à l'action que l'on a écrit dans le dispatcher par l'appel à la méthode runAction.
Remplacement de l'appel à notre action

/** Remplacez ce qui suis
if ( ! method_exists($oController, $params['action'])) {
    throw new Exception ('Action introuvable');
}
$oController->{$params['action']}();*/

// Par ceci :
$oController->runAction($params['action']);
Il nous reste à nous occuper de cette variable $this->filter que nous avons décrit au début.
Nous allons la créer dans le constructeur de la classe Controller en protected. Nous la redéfinirons en cas de besoin dans nos propres contrôleurs.

/**
 * Nos filtres (before et/ou after)
 */
protected $filter;
Enfin, nous définissons nos filtres dans notre contrôleur user :

/**
 * On redéfinit l'attribut filter
 */
protected $filter = array(
    'before' => array(
        // On vérifie si le visiteur est connecté avant de l'autoriser à éditer
        'isConnected' => array('edit')
    ),
    'after' => array(
        // On profite de l'enregistrement d'un membre pour nettoyer la base
        'logoutOldUser' => array('register')
    )
);

/**
 * Nos filtres : 
 */
protected function isConnected() {
    if ( ( ! $this->session['isConnected']) || ( ! is_object($this->session['user']))) {
        throw new Exception ('Vous devez être connecté pour accéder à cette page');		
    }
}
protected function logoutOldUser() {
    // Pour tester le bon fonctionnement du filtre after
    //throw new Exception ('Nettoyage');
}
idea Voici de nouveau par ce lien le framework à ce stade du cours.

III-G. Redirection

Dans le dernier exemple, vous pouvez voir que j'ai utilisé une exception si le visiteur essaie d'accèder à une page protégée sans être loggué. Il serait plus judicieux de le redirigier vers la bonne action (ici l'action login).
Nous allons donc créer dans la classe Controller une méthode redirectTo($params) que nous pourrons utiliser dans toutes nos actions.
Comme nous sommes sur qu'à ce stade, aucun élément de la vue n'a été envoyé, nous pouvons sans problème utiliser les entêtes http pour cette redirection.

Le point sensible sera la construction des url à partir de paramètres.
En effet, nous avons un système dans le dispatcher qui permmet de trouver les paramètres à partir d'une url en utilisant les routes définis dans config/routes.php.
Ici nous voulons l'inverse, c'est-à-dire obtenir l'url à partir des paramètres en utilisant nos routes.
Afin de
Méthode redirectTo de la classe Controller

III-H. Les helpers


III-H-1. Liens


III-H-2. Pager


III-I. Exceptions

 

Valid XHTML 1.1!Valid CSS!

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © Guillaume Affringue. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.