La POO, programmer en MVC

L’architecture MVC signifie ModeleViewcontroleur. C’est une façon de travailler qui consiste à séparer les taches d’accès des données (Modèle), de leurs récupération (contrôleur) et de leurs présentation (Vue).

Par exemple, si vous voulez enregistrer un nouveau produit pour un site marchand :

  • le contrôleur (C) va récupérer les informations, via un formulaire, les traiter puis..
  • lancer l’enregistrement dans la bases de données via le modèle (M). Le modèle permet en effet d’accéder aux données et de les conserver.
  • le contrôleur lance ensuite l’affichage via la vue (V). La vue affichera tout le code HTML de la page en récupérant éventuellement des variables fournies par le contrôleur.

Dans cette architecture, c’est le controleur qui orchestre le modèle et la vue : ce dernier sera donc appelé avant les deux autres.

Voici un exemple de programme simple, qui contrôle le format des emails, et qui pourrait ressembler à celui-ci avec l’architecture MVC.

// appel du fichier qui à accès aux données (modèle)
include_once('modele/mail.php');
 
// On effectue le traitement sur les données (contrôleur)
$tabMailFiltre = [];
foreach ($tabMail as $value){
    // vérification du format de chaque email
    if ( preg_match ( " /^.+@.+\.[a-zA-Z]{2,}$/ " , $value ) ){
        $tabMailFiltre[] = $value;
    }
}
 
// On affiche la page (vue)
include_once('vue/affichage.php');

Contenu du fichier mail.php

// tableau contenant des emails dont certains ne sont pas valides
$tabMail = ['toto@gmail' , 'mami@gmail.com' , 'pepe$gmail.com', 'moi@gmail.com'];

Contenu du fichier affichage.php

echo '<ul>';
foreach ($tabMailFiltre as $value){
    echo '<li> - '.$value.'</li>';
}
echo '</ul>';

Affichage

  • mami@gmail.com
  • moi@gmail.com

Le routeur

La création d’un CMS, par exemple, suggère que toutes les pages du site soient générées par une seule page : index.php. L’architecture MVC doit donc recourir à plusieurs contrôleurs et à plusieurs vues.

Le routeur va donc ‘switcher’ les appels en fonction des informations contenues dans l’url de la page (méthode GET ou répertoires virtuels). Dans ce type de projet, on va donc faire appel à un routeur. Ce dernier doit aussi être capable de récupérer les données issues de la page (via un formulaire – paramètres POST) pour les traiter.

L’exemple ci-dessous illustre le rôle d’un contrôleur. Lorsqu’on arrive sur la page index.php, on appelle le contrôleur (sous forme d’objet – POO).

require 'Routeur.php';
 
//instancie un routeur pour traiter la requête entrante
$routeur = new Routeur();
$routeur->routerRequete();

Toutes les URL entrantes sont du type : index.php?controleur=XXX&action=YYY. La classe routeur va donc récupérer la valeur dans la variable controleur pour ensuite ‘router’ vers le bon contrôleur. Voici le contenu du fichier Routeur.php.

class Routeur {
    private $requete;
 
    public function routerRequete() {
        try {
            // Fusion des paramètres GET et POST de la requête
            // Permet de gérer uniformément ces deux types de requête HTTP
            $requete = array_merge($_GET, $_POST));
            // on créé le controleur
            $controleur = $this->creerControleur();
            //----- traitement à faire selon le controleur
        }
        catch (Exception $e) {
            $this->gererErreur($e);
        }
    }
    
    private function creerControleur() {
        $controleur = "Accueil";  // Contrôleur par défaut => page d'accueil
        //return bool Vrai si le paramètre existe et sa valeur n'est pas vide
        if (isset($this->requete['controleur']) && $this->requete['controleur'] != "") {
            $controleur = $this->requete['controleur'];
        }
    }
    
    // gestion et affichage des erreurs
    private function gererErreur(Exception $exception) {
        //-------
    }
}

Si par exemple l’url de la page est index.php?controleur=Inserer, alors la valeur de la variable $controleur sera ‘Inserer’. L’action à prévoir ensuite est de créer le nom du fichier contrôleur à partir de cette variable, d’inclure le fichier, et d’instancier le contrôleur.. Puis le routeur lance la première action sur ce contrôleur.

La fonction creerControleur() pourrait se compléter ainsi :

class Routeur {
    private $requete;
 
    public function routerRequete() {
        try {
            $requete = array_merge($_GET, $_POST));
            $controleur = $this->creerControleur();
            //----- première action
            $controleur->executerAction();
        }
        catch (Exception $e) {
            $this->gererErreur($e);
        }
    }
 
    private function creerControleur() {
        $controleur = "Accueil";  // Contrôleur par défaut => page d'accueil
        //return bool Vrai si le paramètre existe et sa valeur n'est pas vide
        if (isset($this->requete['controleur']) && $this->requete['controleur'] != "") {
            $controleur = $this->requete['controleur'];
        }
        // Création du nom du fichier du contrôleur
        // La convention de nommage des fichiers controleurs est : Controleur/Controleur<$controleur>.php
        // Première lettre en majuscules
        $controleur = ucfirst(strtolower($controleur));
        $classeControleur = "Controleur" . $controleur;
        $fichierControleur = "Controleur/" . $classeControleur . ".php";
        if (file_exists($fichierControleur)) {
            // Instanciation du contrôleur adapté à la requête
            require($fichierControleur);
            $controleur = new $classeControleur();
            // mémorisation de la requête dans le controleur
            $controleur->setRequete($requete);
            // retour de l'instance
            return $controleur;
        }
        else {
            throw new Exception("Fichier '$fichierControleur' introuvable");
        }
    }
    
    // gestion et affichage des erreurs
    private function gererErreur(Exception $exception) {
        //-------
    }
}

La première action est soit :

  • la génération direct de la vue.
  • la récupération des données que la vue a besoin avant d’être générée.
  • une action d’écriture (insertion, modification, ajout de données) suivit de l’affichage.

Le contrôleur

Dans la logique du modèle MVC, le contrôleur est appelé par le routeur. Son rôle est de lier le modèle et la vue.

Si on revient sur l’exemple ci-dessus, il faut prévoir plusieurs contrôleurs, mais chaque contrôleur possédera les mêmes méthodes : création de la requête (GET et POST), génération de la vue, action à lancer (traitement formulaire) et traitement par défaut à implémenter obligatoirement (classe abstraite)

L’ ensemble des contrôleurs peut s’organiser autour d’un contrôleur de base : la classe mère et des différents contrôleurs appelés par le routeur qui représentent les classes filles. Le routeur n’appelle pas la classe mère mais seulement les classes filles. Au moment de leurs instanciations, chacune d’elle va hériter des propriétés et des méthodes de la classe mère.

abstract class Controleur {
 
    // Action à réaliser
    protected $action;
    
    // Requête entrante
    protected $requete;
 
    // Mémorisation de la requête entrante
    public function setRequete(Requete $requete){
        $this->requete = $requete;
    }
 
    // Action à réaliser.
    public function executerAction(){
       //..
    }
 
    /*
     Méthode abstraite correspondant à l'action par défaut
     Oblige les classes dérivées à implémenter cette action par défaut
    */
    public abstract function index();
 
    // Génère la vue associée au contrôleur courant
    protected function genererVue($donneesVue = array()){
        //..
    }
    
}

Note : puisque la classe possède une méthode abstraite, elle doit être forcément abstraite.

La vue

La vue est le traitement qui consiste à afficher le contenu dans une page. Il doit y avoir autant de vues que de pages différentes. La vue est appelée par le contrôleur après le modèle si nécessaire. Voici un exemple de méthode (qui complète celle utilisé plus haut) du fichier contrôleur qui génère la vue :

// Génère la vue associée au contrôleur courant
protected function genererVue($donneesVue = array()){
    // Détermination du nom du fichier vue à partir du nom du contrôleur actuel
    $classeControleur = get_class($this);
    // instanciation de la vue
    $vue = new Vue($classeControleur);
    // lancement de la vue
    $vue->generer($donneesVue);
}

L’affichage d’une même page peut faire appel à plusieurs fichiers : le gabarit de base, le contenu en détail… IL faut, bien souvent, injecter en plus des variables sur ces fichiers.

La technique à utiliser est la temporisation de sortie qui consiste à mettre temporairement les données en tampon. Au lieu d’afficher le contenu directement sur la page, il est mis en cache (pour être ensuite enregistré dans une variable).

Concrètement le processus est le suivant :

  • extraction des variables à partir d’un tableau
  • démarrage de la temporisation de sortie
  • inclusion du fichier de vue : ce dernier absorbe les variables extraites
  • arrêt de la temporisation et renvoi du tampon de sortie dans une nouvelle variable

Il est possible de répéter ce processus plusieurs fois : il est donc intéressant de créer une méthode spécifique.

private function genererFichier($fichier, $donnees) {
    if (file_exists($fichier)) {
        // Rend les éléments du tableau $donnees accessibles dans la vue
        extract($donnees);
        // Démarrage de la temporisation de sortie
        ob_start();
        // Inclut le fichier vue
        // Son résultat est placé dans le tampon de sortie
        require $fichier;
        // Arrêt de la temporisation et renvoi du tampon de sortie
        return ob_get_clean();
    }
    else {
        throw new Exception("Fichier '$fichier' introuvable");
    }
}

Cette méthode est à intégrer à la classe vue :

class Vue {
    // Nom du fichier associé à la vue
    private $fichier;
    
    // Constructeur
    public function __construct($controleur = "") {
        // La convention de nommage des fichiers vues est : Vue/<$controleur>/index.php
        $fichier = "Vue/";
        if ($controleur != "") {
            $fichier = $fichier . $controleur . "/";
        }
        $this->fichier = $fichier . "index.php";
    }
 
    // affichage du contenu
    public function generer($donnees) {
        // Génération de la partie spécifique de la vue
        $vue = $this->genererFichier($this->fichier, $donnees);
        // il est possible de répéter la temporisation ...
        
        // Renvoi de la vue générée au navigateur
        echo $vue;
    }
 
    // temporisation
    private function genererFichier($fichier, $donnees) {
        if (file_exists($fichier)) {
            // Rend les éléments du tableau $donnees accessibles dans la vue
            extract($donnees);
            // Démarrage de la temporisation de sortie
            ob_start();
            // Inclut le fichier vue
            // Son résultat est placé dans le tampon de sortie
            require $fichier;
            // Arrêt de la temporisation et renvoi du tampon de sortie
            return ob_get_clean();
        }
        else {
            throw new Exception("Fichier '$fichier' introuvable");
        }
    }
}

La modèle

C’est le modèle qui permet d’accéder aux données pour les fournir au contrôleur. Le modèle peut récupérer les données mais aussi les stocker. Bien souvent le modèle travaille avec une base de données. Il peut aussi gérer les flux XML ou RSS.

Le contrôleur peut solliciter le modèle pour différentes actions :

  • chercher dans la base de données toutes les informations nécessaires à l’affichage de la page
  • insérer des données
  • modifier des données
  • effectuer des vérifications avant de lancer des actions
  • supprimer des données

En POO, le modèle gère les requêtes SQL. Comme avec les contrôleurs on peut se retrouver avec plusieurs modèles (insertion, modification, suppression). Tous ces modèles ont en commun deux méthodes : la connexion à une base de données, et l’exécution de la requête. On peut donc regrouper ces deux méthodes dans une classe mère et les autres modèles étendront cette classe. voici ci-dessous un exemple de classe mère modèle :

abstract class Modele {
    // Exécute une requête SQL
    protected function executerRequete($sql, $params = null) {
        if ($params == null) {
            $resultat = self::getBdd()->query($sql);   // exécution directe
        }
        else {
            $resultat = self::getBdd()->prepare($sql); // requête préparée
            $resultat->execute($params);
        }
        return $resultat;
    }
 
    // Objet de connexion à la BDD en initialisant la connexion au besoin
    private static function getBdd() {
    if (self::$bdd === null) {
        // Récupération des paramètres de configuration BD
        $host  = Configuration::get("adresse_de_host");
        $name  = Configuration::get("nom_de_la_base");
        $login = Configuration::get("nom_du_user");
        $mdp   = Configuration::get("mot_de_passe");
        // Création de la connexion
        self::$bdd = new PDO('mysql:host='.$host.';dbname='.$name, $login,
        $mdp,array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
        ));
        }
        return self::$bdd;
    }
}

Note : un classe abstraite est une classe qui ne peut pas être instanciée.

On va pouvoir créer les autres modèles par héritage, comme par exemple le modèle insertion ci-dessous.

require_once 'Modele.php';
 
class Membre extends Modele {
 
    // Renvoie la liste des membres triés par nom dans l'ordre alphabétique
    public function getMembre() {
        $sql = 'SELECT Id, Nom, Prenom FROM membre ORDER BY Nom';
        return $this->executerRequete($sql);
    }
    
    // autre méthode ...
}

La gestion des erreurs

La technique consiste à lever manuellement des exceptions pour personnaliser vos erreurs et de les récupérer dans la fonction catch() au niveau du routeur.

public function routerRequete() {
    try {
        //----- traitement à faire selon le controleur
    }
    catch (Exception $e) { // récuperation de l'exception
        $this->gererErreur($e);
    }
}

Dans le traitement des données, il suffira d’instancier une exception (- new Exception() -) avec la syntaxe throw.

// quelque part dans le script
public function test(){
    if(/*le teste ici*/){
        // instructions
    }else{
        throw new Exception("chaine personnalisée de l'erreur");
    }
}

Politique d'utilisation des cookies

En poursuivant votre navigation sur ce site, vous acceptez l’utilisation de cookies. Les cookies nous permettent de personnaliser le contenu, les sorties et d’optimiser notre trafic.