Accueil > 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 thierrybo

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 :

  1. switch ($_GET['viewpage']) {
  2. case "news":
  3. $page=new NewsRenderer;
  4. break;
  5. case "links":
  6. $page=new LinksRenderer;
  7. break;
  8. default:
  9. $page=new HomePageRenderer;
  10. break;
  11. }
  12. $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

  1. /**
  2.  * Une simple classe pour exécuter des requêtes MySQL
  3.  */
  4. class DataAccess {
  5. /**
  6.   * Private
  7.   * $db stocke une ressource vers la base de données
  8.   */
  9. var $db;
  10. /**
  11.   * Private
  12.   * $query stocke une ressource vers le résultat d'une requête
  13.   */
  14. var $query; // resource de requête
  15.  
  16. //! Un constructeur.
  17. /**
  18.   * Construit un nouvel objet DataAccess
  19.   *
  20.   * @param $host string nom d'hôte pour dbserveur
  21.   * @param $user string identifiant dbserveur
  22.   * @param $pass string mot de passe dbserver
  23.   * @param $db string nom de la base de données
  24.   */
  25. function DataAccess ($host,$user,$pass,$db) {
  26. $this->db=mysql_pconnect($host,$user,$pass);
  27. mysql_select_db($db,$this->db);
  28. }
  29.  
  30. //! Un accesseur
  31. /**
  32.   * Exécute une requête et stocke sa ressource dans un
  33.   * membre local
  34.   *
  35.   * @param $sql string la requête à éxécuter
  36.   * @return void
  37.   */
  38. function fetch($sql) {
  39. // La reqête est exécutée ici
  40. $this->query=mysql_unbuffered_query($sql,$this->db);
  41. }
  42.  
  43. //! Un accesseur
  44. /**
  45.   * Retourne un tableau associatif à partir d'une ligne de
  46.   * résultat d'une requête
  47.   *
  48.   * @return mixed
  49.   */
  50. function getRow () {
  51. if ( $row=mysql_fetch_array(
  52. $this->query,MYSQL_ASSOC) )
  53.  
  54. return $row;
  55. else
  56. return false;
  57. }
  58. }

Au dessus on place le Modèle :

ProductModel.php

  1. /**
  2.  * Récupère les "produits" de la base de données
  3.  */
  4. class ProductModel {
  5. /**
  6.   * Private
  7.   * $dao une instance de la classe DataAccess
  8.   */
  9. var $dao;
  10.  
  11. //! Un constructeur.
  12. /**
  13.   * Construit un nouvel objet ProductModel
  14.   * @param $dbobject une instance de la classe DataAccess class
  15.   */
  16. function ProductModel (&$dao) {
  17. $this->dao=& $dao;
  18. }
  19.  
  20. //! Un manipulateur
  21. /**
  22.   * Dit à l'objet $dao ("Data Access Object", Objet
  23.   * d'Accès aux Données) de conserver la ressource de
  24.   * cette requête
  25.   *
  26.   * @param $start l'enregistrement de départ
  27.   * @param $rows le nombre d'enregistrements à renvoyer
  28.   * @return void
  29.   */
  30. function listProducts($start=1,$rows=50) {
  31. $this->dao->fetch("SELECT * FROM products LIMIT
  32. ".$start.", ".$rows);
  33. }
  34.  
  35. //! Un manipulateur
  36. /**
  37.   * Dit à l'objet $dao ("Data Access Object") de conserver
  38.   * la ressource de cette requête
  39.   *
  40.   * @param $id une clé primaire d'un enregistrement
  41.   * @return void
  42.   */
  43. function listProduct($id) {
  44. $this->dao->fetch("SELECT * FROM products WHERE
  45. PRODUCTID='".$id."'");
  46. }
  47.  
  48. //! Un manipulateur
  49. /**
  50.   * Récupère un Produit en tableau associatif à partir
  51.   * d'un $dao ("Data Access Object")
  52.   *
  53.   * @return mixed
  54.   */
  55. function getProduct() {
  56. if ( $product=$this->dao->getRow() )
  57. return $product;
  58. else
  59. return false;
  60. }
  61. }

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

  1. /**
  2.  * Lie les données d'un Produit à la génération du HTML
  3.  */
  4. class ProductView {
  5. /**
  6.   * Une instance de la classe ProductModel
  7.   *
  8.   * @access private
  9.   * @var object
  10.   */
  11. var $model;
  12.  
  13. /**
  14.   * Le code HTML généré est stocké ici pour affichage
  15.   *
  16.   * @access private
  17.   * @var string
  18.   */
  19. var $output;
  20.  
  21. //! Un constructeur.
  22. /**
  23.   * Construit un nouvel objet ProductView
  24.   *
  25.   * @param object $model une instance de la classe ProductModel
  26.   */
  27. function ProductView (&$model) {
  28. $this->model=& $model;
  29. }
  30.  
  31. //! Un manipulateur
  32. /**
  33.   * Crée le haut d'une page HTML
  34.   *
  35.   * @return void
  36.   */
  37. function header () {
  38. // ...snip....
  39. }
  40.  
  41. //! Un manipulateur
  42. /**
  43.   * Crée le bas d'une page HTML
  44.   *
  45.   * @return void
  46.   */
  47. function footer () {
  48. // ...snip....
  49. }
  50.  
  51. //! Un manipulateur
  52. /**
  53.   * Affiche un produit unique
  54.   *
  55.   * @return void
  56.   */
  57. function productItem($id=1) {
  58. $this->model->listProduct($id);
  59. while ( $product=$this->model->getProduct() ) {
  60. // ...snip... Lie les données au code HTML
  61. }
  62. }
  63.  
  64. //! Un manipulateur
  65. /**
  66.   * Crée une table (HTML) de produits
  67.   *
  68.   * @return void
  69.   */
  70. function productTable($rownum=0) {
  71. $rowsperpage='20';
  72. $this->model->listProducts($rownum,$rowsperpage);
  73. // ... snip ...
  74. while ( $product=$this->model->getProduct() ) {
  75. // ...snip... lie les données au code HTML
  76. }
  77. // ... snip ...
  78. }
  79.  
  80. //! Un accesseur
  81. /**
  82.   * Retourne le code HTML généré
  83.   *
  84.   * @return string
  85.   */
  86. function display () {
  87. return $this->output;
  88. }
  89. }

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

ProductControler.php

  1. /**
  2.  * Controle lme flux de l'application
  3.  */
  4. class ProductController extends ProductView {
  5.  
  6. //! Un constructeur.
  7. /**
  8.   * Construit un nouvel objet ProductController
  9.   *
  10.   * @param object $model une instance de la classe ProductModel
  11.   * @param array $getvars variables HTTP GET reçues
  12.   */
  13. function ProductController (&$model,$getvars=null) {
  14. ProductView::ProductView($model);
  15. $this->header();
  16. switch ( $getvars['view'] ) {
  17. case "product":
  18. $this->productItem($getvars['id']);
  19. break;
  20. default:
  21. if ( empty ($getvars['rownum']) ) {
  22. $this->productTable();
  23. } else {
  24. $this->productTable($getvars['rownum']);
  25. }
  26. break;
  27. }
  28. $this->footer();
  29. }
  30. }

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 :

  1. require_once('lib/DataAccess.php');
  2. require_once('lib/ProductModel.php');
  3. require_once('lib/ProductView.php');
  4. require_once('lib/ProductController.php');
  5.  
  6. $dao=& new DataAccess ('localhost','user','pass','dbname');
  7. $productModel=& new ProductModel($dao);
  8. $productController=& new ProductController($productModel,$_GET);
  9. 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 :

  1. $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 :

  1. $view=new $_GET['class'];
  2. $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 :

  1. $controller = new ProductController(...);
  2. $view = $controller->getView();
  3. 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.

titre documents joints

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

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

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


7 Messages

  • 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/2013/08/1361287586-capture-d-ecran-19022013-16-26-12.png

    - ton menu se déporte en bas de la page et sort de l’écran
    http://www.noelshack.com/2013-08-1361287763-capture-d-ecran-19022013-16-29-10.png

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

    Répondre à ce message

    • Motif Modele Vue Controleur et PHP Le 9 mars 2013 à 17:36, par Thierry Bothorel

      Ah oui merci,

      en fait je ne m’occupe plus trop de mon site depuis un moment, et les balises de code Geshi pour SPIP ne semblent plus fonctionner depuis la dernière mise à jour que j’avais faite un peu à l’arrache.

      Répondre à ce message

Répondre à cet article