Accueil du site > INFORMATIQUE > PHP > Motifs de Conception > Motif Modele Vue Controleur et PHP

Traduction d’un article de Harry Fuecks sur phpPatterns, 21.11.2002

Motif Modele Vue Controleur et PHP

1 - MVC Version 1

mardi 18 avril 2006, par Thierry Bothorel

Toutes les versions de cet article : [English ] [français ]

N’ayant pas de “culture informatique scolaire”, mon expérience de programmation depuis des années en ASP puis PHP était limitée à la programmation procédurale. Cela fait un ou deux ans que je “lorgne” sur la programmation orientée objet en PHP, sans toutefois “faire le grand saut”. Je cherchais en quelque sorte une "porte d’entrée", et les articles de Harry Fuecks sur son site et en particulier celui sur le motif “MVC" ont joué ce rôle. Il me semblait important pour moi d’en proposer ici une traduction, en y ajoutant les commentaires d’origine qui était une source d’information non négligeable et qui ont malheureusement disparus dans son nouveau site.

Le motif de conception MVC est très utilisé dans la construction d’applications web. Ce principe nous permet de construire des applications 3-tiers et permet également une séparation utile du code en plusieurs couches. Il aide aussi pour une meilleure collaboration entre les designers et les développeurs et améliore les capacités de ces derniers à maintenir et développer les applications existantes.

 LA VUE

Les “Vues” correspondent généralement au résultat final envoyé vers un navigateur - le code HTML généré par notre script par exemple. En parlant des vues, beaucoup de gens pensent aux moteurs de “templates”, mais on peut débattre sur le fait d’appeler un moteur de template une “vue”.

Peut-être que le point important sur les Vues est qu’elles doivent “connaître” quand une vue est rendue le rôle de chaque élément dans le contexte global. Si on prend l’ XML comme exemple, on pourrait dire que l’ XML analysé par l’API Document Object Model à cette connaissance - un nœud dans un arbre DOM connait sa position dans l’arbre et ce qu’il contient alors que les nœuds d’un document XML analysé par SAX n’ont aucune connaissance sur eux-mêmes jusqu’à ce que l’analyseur les atteigne.

La plupart des solutions de template utilisent un certain langage plutôt simplifié et des balises de template ressemblant à ceci :

Elles n’ont aucune connaissance du document qui va être créé ou ce qu’elles représentent et sont simplement là pour que PHP les remplace par quelque chose d’autre.

Si vous êtes d’accord avec cette description d’une Vue, vous serez également d’accord sur le fait que la plupart des moteurs de templates échouent dans la division désirée du Modèle et de la Vue, la connaissance du résultat de la transformation des balises étant conservée dans le Modèle.

Des questions que vous devez vous poser quand vous créez vos Vues : « Est-il facile de faire des changements globaux pour toutes les vues ? », « Peut-on échanger facilement le langage de présentation délivré par la Vue avec un autre (par ex. délivrer un document SOAP [1] au lieu d’un document HTML à partir de la même Vue) ? »

 LE MODÈLE

Le Modèle représente la logique de l’application (souvent appelée la “Couche Métier” dans les Application de classe Entreprise).

En résumé, le « boulot » du Modèle est de transformer les données brutes en données qui ont une signification pour l’application et qui sont délivrées à la Vue pour affichage. Le Modèle encapsule souvent les requêtes à la base de données, pouvant éventuellement tirer parti d’une classe d’abstraction de base de données (une couche d’accès aux données) pour exécuter les requêtes.

Par exemple, vous voulez calculer les précipitations annuelles au Royaume-Uni (juste pour vous convaincre qu’il y a de meilleurs destinations pour passer ses vacances !), le Modèle recevrait les chiffres des précipitations journalières sur dix ans, calculerait la moyenne et délivrerait le Résultat à la Vue.

 LE CONTRÔLEUR

Le controleur est d’abord le premier point d’entrée pour toutes les requêtes HTTP reçues dans une application Web. Il examine ce qu’il a reçu dans la requête, comme une collection de variables GET, et répond de façon appropriée. En fait, vous pouvez difficilement commencer à coder en PHP sans écrire votre premier contrôleur quelque soit la forme que vous lui donnez. La forme la plus courante et la plus simple peut être une instruction switch() dans votre fichier index.php qui pourrait ressembler à ceci :

switch ($_GET['viewpage']) {
    case "news":
        $page=new NewsRenderer;
        break;
    case "links":
        $page=new LinksRenderer;
        break;
    default:
        $page=new HomePageRenderer;
    break;
}
$page->display();

Cet exemple mixe du code procédural et orienté objet, mais pour un petit site web, c’est souvent un choix parfaitement valide. Ce code pourrait bien entendu être amélioré.

Le rôle essentiel du Contrôleur est de déclencher la liaison entre les données provenant du Modèle aux éléments de la Vue.

 UN EXEMPLE

Voici un exemple du motif MVC en action.

Premièrement nous avons besoin d’une couche d’accès aux données, qui est une classe générique :

DataAccess.php

/**
 *  Une simple classe pour exécuter des requêtes MySQL
 */

class DataAccess {
    /**
    * Private
    * $db stocke une ressource vers la base de données
    */

    var $db;
    /**
    * Private
    * $query stocke une ressource vers le résultat d'une requête
    */

    var $query; // resource de requête
    //! Un constructeur.
    /**
    * Construit un nouvel objet DataAccess
    *
    * @param $host string nom d'hôte pour dbserveur
    * @param $user string identifiant dbserveur
    * @param $pass string mot de passe dbserver
    * @param $db string nom de la base de données
    */

    function DataAccess ($host,$user,$pass,$db) {
        $this->db=mysql_pconnect($host,$user,$pass);
        mysql_select_db($db,$this->db);
    }
    //! Un accesseur
    /**
    * Exécute une requête et stocke sa ressource dans un
    * membre local
    *
    * @param $sql string la requête à éxécuter
    * @return void
    */

    function fetch($sql) {
        // La reqête est exécutée ici
        $this->query=mysql_unbuffered_query($sql,$this->db);
    }
    //! Un accesseur
    /**
    * Retourne un tableau associatif à partir d'une ligne de
    * résultat d'une requête
    *
    * @return mixed
    */

    function getRow () {
        if ( $row=mysql_fetch_array(
            $this->query,MYSQL_ASSOC) )
            return $row;
        else
            return false;
    }
}

Au dessus on place le Modèle :

ProductModel.php

/**
 *  Récupère les "produits" de la base de données
 */

class ProductModel {
    /**
    * Private
    * $dao une instance de la classe DataAccess
    */

    var $dao;
    //! Un constructeur.
    /**
    * Construit un nouvel objet ProductModel
    * @param $dbobject une instance de la classe DataAccess class
    */

    function ProductModel (&$dao) {
        $this->dao=& $dao;
    }
    //! Un manipulateur
    /**
    * Dit à l'objet $dao ("Data Access Object", Objet
    * d'Accès aux Données) de conserver la ressource de
    * cette requête
    *
    * @param $start l'enregistrement de départ
    * @param $rows le nombre d'enregistrements à renvoyer
    * @return void
    */

    function listProducts($start=1,$rows=50) {
        $this->dao->fetch("SELECT * FROM products LIMIT
            "
.$start.", ".$rows);
    }
    //! Un manipulateur
    /**
    * Dit à l'objet $dao ("Data Access Object") de conserver
    * la ressource de cette requête
    *
    * @param $id une clé primaire d'un enregistrement
    * @return void
    */

    function listProduct($id) {
        $this->dao->fetch("SELECT * FROM products WHERE
            PRODUCTID='"
.$id."'");
    }
    //! Un manipulateur
    /**
    * Récupère un Produit en tableau associatif à partir
    * d'un $dao ("Data Access Object")
    *
    * @return mixed
    */

    function getProduct() {
        if ( $product=$this->dao->getRow() )
            return $product;
        else
            return false;
    }
}

Une chose importante à noter : le Modèle et la classe d’accès aux données n’échangent jamais plus d’un seul enregistrement à la fois, sous forme de tableau - on ne passe pas tous les enregistrements retournés par la requête d’un seul coup, ce qui pourrait sérieusement ralentir une application. Le même principe s’applique à la classe qui utilise le modèle - elle a seulement besoin de conserver une seule ligne en mémoire à la fois - le reste est laissé à la charge de la ressource de la requête - en d’autres termes, on laisse à MySql le soin de nous conserver le résultat.


Ensuite la Vue. J’ai supprimé le code qui produit du HTML pour réduire la taille de l’article mais vous le retrouverez dans le code complet pour cet article.

ProductView.php

/**
 *  Lie les données d'un Produit à la génération du HTML
 */

class ProductView {
    /**
    * Une instance de la classe ProductModel
    *
    * @access private
    * @var object
    */

    var $model;
    /**
    * Le code HTML généré est stocké ici pour affichage
    *
    * @access private
    * @var string
    */

    var $output;
    //! Un constructeur.
    /**
    * Construit un nouvel objet ProductView
    *
    * @param object $model une instance de la classe ProductModel
    */

    function ProductView (&$model) {
        $this->model=& $model;
    }
    //! Un manipulateur
    /**
    * Crée le haut d'une page HTML
    *
    * @return void
    */

    function header () {
        // ...snip....
    }
    //! Un manipulateur
    /**
    * Crée le bas d'une page HTML
    *
    * @return void
    */

    function footer () {
        // ...snip....
    }
    //! Un manipulateur
    /**
    * Affiche un produit unique
    *
    * @return void
    */

    function productItem($id=1) {
        $this->model->listProduct($id);
        while ( $product=$this->model->getProduct() ) {
            // ...snip... Lie les données au code HTML
        }
    }
    //! Un manipulateur
    /**
    * Crée une table (HTML) de produits
    *
    * @return void
    */

    function productTable($rownum=0) {
        $rowsperpage='20';
        $this->model->listProducts($rownum,$rowsperpage);
        //  ... snip ...
        while ( $product=$this->model->getProduct() ) {
            // ...snip... lie les données au code HTML
        }
        // ... snip ...
    }
    //! Un accesseur
    /**
    * Retourne le code HTML généré
    *
    * @return string
    */

    function display () {
        return $this->output;
    }
}

Et enfin le Contrôleur que nous implémenterons comme “fils” de la Vue :

ProductControler.php

/**
 *  Controle lme flux de l'application
 */

class ProductController extends ProductView {
    //! Un constructeur.
    /**
    * Construit un nouvel objet ProductController
    *
    * @param object $model une instance de la classe ProductModel
    * @param array $getvars variables HTTP GET reçues
    */

    function ProductController (&$model,$getvars=null) {
        ProductView::ProductView($model);
        $this->header();
        switch ( $getvars['view'] ) {
            case "product":
                $this->productItem($getvars['id']);
                break;
            default:
                if ( empty ($getvars['rownum']) ) {
                    $this->productTable();
                } else {
                    $this->productTable($getvars['rownum']);
                }
                break;
        }
        $this->footer();
    }
}

Notez que ce qui est présenté ici n’est pas la seule façon de relier les classes dans un motif MVC - on pourrait changer pour un Contrôleur qui implémente le Modèle tout en agrégeant la Vue. Il s’agit juste d’une approche qui présente le motif.

Notre fichier index.php :

require_once('lib/DataAccess.php');
require_once('lib/ProductModel.php');
require_once('lib/ProductView.php');
require_once('lib/ProductController.php');
$dao=& new DataAccess ('localhost','user','pass','dbname');
$productModel=& new ProductModel($dao);
$productController=& new ProductController($productModel,$_GET);
echo $productController->display();

Net et simple.

Il est important de noter que nous aurions pu être plus astucieux pour le contrôleur. En PHP vous pouvez utiliser ce genre de truc :

$this->{$_GET['method']}($_GET['param']);

Cette approche suggère que vous définissiez pour vos applications un espace de noms pour les URL pour qu’elles se conforment à un standard comme celui-ci :

index.php?class=ProductView&method=productItem&id=4

En utilisant cette technique on pourrait utiliser le code suivant dans notre contrôleur :

$view=new $_GET['class'];
$view->{$_GET['method']}($_GET['id']);

D’une certaine façon, la construction du contrôleur est le point le plus difficile lorsqu’il s’agit de trouver le bon équilibre entre rapidité de développement et capacité à monter en charge. Un bon emplacement pour trouver de l’inspiration est Java Struts du groupe Apache où votre contrôleur est essentiellement définit par des documents XML.


COMMENTAIRES

Sujet : Auteur :
Quelques pistes pour améliorer le code Captain Proton 24.11.2002

J’ai eu deux ou trois commentaires sur cet article …

Votre classe ProductController est héritée de la classe ProductView. Ce faisant, on n’obtient pas une séparation du contrôleur et de la vue, puisque le contrôleur ’Est’ la vue (à cause de la relation d’héritage). Vos classes View et Controller devraient être complètement séparées et le contrôleur devrait renvoyer une Vue comme dans cet exemple :

$controller = new ProductController(...);
$view = $controller->getView();
echo $view->display();

2) Dans le motif de conception MVC, une vue n’a toujours qu’un contrôleur et un seul. Dans votre code exemple, vous utilisez un contrôleur pour en fait deux vues : productItem et productTable. Vous devriez utiliser deux contrôleurs séparés pour ces deux vues.

3) Le but d’un Contrôleur dans une application de bureau MVC est de réagir aux actions de l’utilisateur comme taper du texte, cliquer sur la souris etc… Comme PHP est un langage côté serveur il n’a pas besoin de contrôler ces choses. A mon avis, le seul objet d’un contrôleur en PHP serait pour les vues qui utilisent un formulaire dont le but est d’afficher et d’éditer des informations venant du Modèle. Le contrôleur « réagit à cette saisie » en appelant les méthodes appropriées du Modèle (par exemple $obj->saveToDatabase()). Dans votre exemple, c’est le contrôleur qui décide quelle vue aficher.

4) Une autre tâche du Contrôleur est de charger les objets du Modèle. Dans votre exemple, vous passez l’objet du modèle au contrôleur comme paramètre. Comme un contrôleur ne peut seulement être utilisé qu’avec un seul type de Modèle, le passer en paramètre au Contrôleur n’a pas de sens.

Je comprend qu’il est difficile voire impossible d’appliquer directement le motif MVC tel qu’il a été conçu pour les applications de bureau, à cause du mode de fonctionnement même du web et de PHP (mode déconnecté ou asynchrone). Il faut donc modifier un peu le motif pour un langage comme PHP. Mais si vous voulez coller au plus près au motif MVC original, votre fàçon de le faire n’est certainement pas la bonne. Vous êtes sur la bonne voie, mais je vous conseille de faire plus de recherches sur MVC et ensuite retourner à votre clavier :)

Réponse
RE : Quelques pistes pour améliorer le code Harry Fuecks 24.11.2002

Pas de problème :) Et un grand merci pour votre commentaire. J’espère mettre en ligne une nouvelle version de cet article dans la semaine qui vient.

Réponse
RE : Quelques pistes pour améliorer le code Harry Fuecks 27.11.2002

Au delà de çà, il semble qu’en fait je mélange le motif « Contrôleur Principal » (’Front Controller’) avec le « Contrôleur ».

Je vais essayer de mettre en place quelque chose pour réviser le motif MVC que j’ai présenté et incorporer en même temps l’idée de Contrôleur Principal.

P.-S.

N’utilisez pas le code présenté sur cet article comme une référence. Ce premier article avait donné lieu a une version révisée du code des classes Controleur et Vue et publier une deuxième version nommée “MVC2” que je vous présente dans la deuxième partie de l’article.

Documents joints

  • Exemple de code MVC Exemple de code MVC (Zip – 12.5 ko)

    Fichiers PHP présentés dans cet article et code SQL

Répondre à cet article | RétroLiens :0


6 Messages de forum

  • Motif Modele Vue Controleur et PHP Le 7 novembre 2007 à 18:05 , par ZiWaM

    Bonjour,

    Je suis tombé sur cet article par hasard étant moi même entrain de revoir mon MVC et j’ai un peu de mal avec les remarques :

    "2) Dans le motif de conception MVC, une vue n’a toujours qu’un contrôleur et un seul. Dans votre code exemple, vous utilisez un contrôleur pour en fait deux vues : productItem et productTable. Vous devriez utiliser deux contrôleurs séparés pour ces deux vues."

    J’ai envie de dire : 1 vue n’a qu’1 contrôleur ne signifie par qu’un contrôleur n’a qu’une seule vue.

    Pour le reste je suis d’accord, c’est perfectible et l’arrivée de php5 permet pas mal de choses mais pour tout un tas de raison j’ai encore besoin de ce bon vieux PHP4.

    La remarque 3 est étrange aussi, ce n’est pas parce que PHP est un langage coté serveur qu’il ne doit pas réagir sur les actions de l’utilisateur, c’est absurde. L’exemple donné est bien vrai et c’est justement suite à une saisie de texte et un click…

    Pour le reste je suis plutôt d’accord mais j’ai peur de n’être assez puriste pour comprendre les 2 subtilités précédentes.

    L’article est cependant pas mal fait et très bien référencé, autant le mettre à jour, d’autant que les sources de données à ce sujet sont bien plus répandues aujourd’hui qu’en 2002.

    Répondre à ce message

  • Motif Modele Vue Controleur et PHP Le 3 décembre 2008 à 02:30 , par hide

    Article très très intéressant, je suis actuellement en train d’essayer de refaire mon petit site en suivant le motif MVC (surtout pour la clarté que ça apportera a mon code car avec un blog très basique je suis déjà a peine capable de me relire), mais je ne me leurre pas je sens que j’y suis encore l’année prochaine, j espère que l’article sera toujours là.

    Répondre à ce message

    • Motif Modele Vue Controleur et PHP Le 11 janvier 2009 à 21:35 , par pocalips25

      Article très intéressant, le plus important c’est que cette méthode explique le concept du design paterne MVC qui reste un point noir pour les Novices de la programmation orientée objet, et surtout la manière dont les objet communiques entre eux, je vous remercier pour ces efforts et je vais essayé de publier un article sur ce sujet pour donner plus éclaircissements.

      Répondre à ce message

      • Motif Modele Vue Controleur et PHP Le 21 janvier 2009 à 19:51 , par Thierry Bothorel

        Merci,

        J’avais à l’époque transposé ce modèle vers un moteur de template très léger lui même développé par Harry Fuecks uniquement pour développer une idée qu’il avait eu dans un forum fin 2003 et qui a aboutit à SimpleT, moteur de template tout simple et intégrant un système de Cache. Depuis 4 ans je me dis qu’il faut que je le mette en ligne pour faire une suite à cet article …

        Plus récemment pour apprendre Symfony, j’ai aussi transposé cet exemple avec ce framework.

        Répondre à ce message

  • Motif Modele Vue Controleur et PHP Le 7 décembre 2012 à 11:53 , par Gilles Savary

    Je n’ai jamais vu du code autant illisible.

    Répondre à ce message

  • Motif Modele Vue Controleur et PHP Le 19 février 2013 à 16:32 , par Démonarc

    Il y a des problème d’affichage dans mon navigateur, j’ai fait des captures pour que tu vois :

    - les retours à la ligne ne se font pas correctement http://image.noelshack.com/fichiers…

    - ton menu se déporte en bas de la page et sort de l’écran http://www.noelshack.com/2013-08-13…

    Mon blog satanique http://www.demonarche.eu

    Répondre à ce message

Répondre à cet article