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

rawsrc

[Actualité] Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Le retour - Théorie

Note : 7 votes pour une moyenne de 3,29.
par , 24/07/2019 à 10h10 (6358 Affichages)
Salut les développeurs,

aujourd'hui je vais vous faire un billet pour démystifier le concept de programmation en PHP avec l'approche Modèle-Vue-Contrôleur (MVC pour les intimes).

Compte tenu que les applications web ont une fâcheuse tendance de nos jours à l'embonpoint voire à l'obésité , il est important d'organiser le code source de manière à pouvoir faciliter d'une part le développement de nouvelles fonctionnalités et d'autre part, la maintenance du code existant. Dans cette optique, l'expérience a montré qu'il était tout à fait possible de séparer le code source en trois parties distinctes relatives à chaque aspect des traitements :

  • Le modèle qui regroupe tout ce qui est relatif au métier (aspects professionnels de l'application)
  • La vue qui regroupe tout ce qui est relatif au rendu
  • Le contrôleur qui regroupe tout ce qui est relatif aux entrées/contrôle du flux/sorties de l'application


Je pars d'une approche rigide pour bien poser le concept général. Avec l'expérience, il vous sera possible d'assouplir un peu cette approche, mais pour l'instant aucun écart ne vous sera profitable sauf à vous embrouiller.


LE MODÈLE

Comme précisé plus haut, cet aspect regroupe tout ce qui est propre à la couche métier de l'application.
Par couche métier, il faut bien comprendre que cela correspond à la valeur ajoutée de l'application.
Quand vous travaillez par exemple sur une application de gestion, la couche métier va englober la gestion des comptes (création, modification, suppression...), catégories, calculs des soldes selon les règles en vigueur, gestion des arrondis, éditions, etc.
D'une application à l'autre, vous allez très vite vous rendre compte que certains aspects métiers sont communs et réutilisables : connexion à un compte, déconnexion, accès à une base de données (PDO)...
Ainsi, vous allez très probablement vous poser la question du meilleur moyen pour réutiliser très facilement le code entre applications et vous allez inévitablement tomber sur la Programmation Orientée Objet (POO) conçue avec cette idée de portabilité dès le début. Je privilégierai cette approche dans ce billet.


LA VUE

Cet élément doit être pris au sens large du concept, par exemple les éléments de cette liste relèvent de la vue :
  • générer du html
  • générer du xml
  • générer un pdf
  • etc.

Il faut bien comprendre que la vue est une terminaison dans le traitement. La vue est une fin, elle ne doit s'occuper que de bien générer de manière autonome (le plus possible) un élément qui devra être envoyé au navigateur.
Plus clairement : la vue est passive, il faut lui passer en paramètre tous les éléments nécessaires à son travail de génération. Cela implique qu'avant d'appeler la vue, il faut au préalable que le traitement en cours collecte exhaustivement valeurs, variables et autres données qui seront utilisées au moment de la génération du rendu.
Une vue ne doit avoir à sa disposition qu'un accès très limité aux autres parties de l'application. En conséquence, les seules fonctions auxquelles elle peut accéder ne doivent avoir trait qu'à la vue par exemple une fonction d'échappement des caractères dangereux, une fonction de mise en forme de texte...
En aucun cas, elle ne doit faire appel à la couche métier ou au contrôleur.


LE CONTRÔLEUR

C'est le chef d'orchestre du trio, il est le point d'entrée de chaque traitement, il pilote le déroulé du programme (le flux de traitement), appelle les éléments de la couche métier, collecte les données de réponse, appelle les éléments de vue et envoie une réponse au navigateur à la toute fin du traitement.

Ici, il y a un point très important à saisir c'est la notion de point d'entrée de chaque traitement.
Il faut bien percevoir qu'interagir avec une application web est équivalent à l'accomplissement d'une suite d'actions.

Une action va correspondre par exemple à :
  • demander l'affichage d'une page web
  • appeler un formulaire de saisie
  • soumettre un formulaire rempli
  • demander la génération d'un document pdf
  • récupérer des données au format xml
  • trier les données d'un tableau sur clic d'un en-tête de colonne
  • changer de page sur clic d'un élément de pagination
  • etc.

Conformément au principe ci-dessus,
A CHAQUE ACTION DISPONIBLE DANS LE SITE WEB DEVRA CORRESPONDRE UN SEUL ET UNIQUE CONTRÔLEUR QUI SERA DE FAIT L'UNIQUE POINT D'ENTRÉE DE L'APPLICATION POUR CE TRAITEMENT

Ne vous inquiétez pas, sur les sites web d'une certaine taille, il n'est pas rare de trouver plus 1 000 contrôleurs... qui correspondent à 1 000 actions possibles sur le site...


REQUÊTE SERVEUR

Comme vous n'êtes pas sans le savoir , le navigateur ne dispose que d'un seul et unique moyen pour communiquer avec un serveur web : l'URL.
Afin que le serveur web soit à même de différencier les actions et de ne pas s'emmêler les pinceaux, il sera obligatoire d'avoir une correspondance unique entre URL et action.
En conséquence,
A CHAQUE ACTION DISPONIBLE DANS LE SITE WEB DEVRA CORRESPONDRE UNE SEULE ET UNIQUE URL QUI SERA DE FAIT L'UNIQUE POINT D'ENTRÉE DU SITE POUR CE TRAITEMENT


ORGANISATION DU CODE SOURCE

Depuis le début, je vous parle de la notion de point d'entrée, il est essentiel de bien la comprendre, car cela va directement influer sur la manière d'organiser le code source d'une application.
Un serveur web tire son nom du fait qu'il n'a que pour seule et unique tâche de servir des réponses à des requêtes qu'il reçoit. Pour y parvenir, il exécute des scripts avec du code source que vous avez pris grand soin d'écrire.

Maintenant, abordons un aspect plus technique : l'environnement d’exécution PHP a une particularité qui est à la fois une force et une faiblesse : PHP est dit "stateless", ce qui veut tout simplement dire qu'il ne garde rien en mémoire entre chaque requête.
C'est exactement comme s'il s'éteignait et se rallumait entre chaque appel. Comme si vous redémarriez votre ordinateur entre chaque appui de touche au clavier, c'est embêtant, non ? Bah pas tant que ça !

La force réside dans le fait que l'environnement d'exécution est tout neuf et propre à chaque nouveau traitement, la faiblesse c'est que l'environnement d'exécution spécifique à votre application est détruit à chaque fin de traitement.
Par environnement spécifique à votre application, il faut comprendre que cela correspond à votre connexion à la base de données, la définition de constantes utilisées partout dans le code, certains paramétrages d'exécution, la manière de charger des classes, etc.
Bref, c'est tout le socle commun et nécessaire et à la bonne exécution de votre code qui est supprimé de la mémoire une fois la réponse du serveur envoyée.

Comme à chaque nouvelle requête cet environnement devra être reconstruit, il est nécessaire de bien réfléchir à la manière d'organiser son code afin que ce redémarrage ne soit pas une torture insupportable.

On va voir qu'il existe deux manières d'appréhender la problématique, sauf qu'il ne faut en garder qu'une seule à la fin

En simplifiant, dans la configuration d'un serveur web, il est explicitement défini un répertoire racine (généralement /www/) qui va servir à enregistrer tous les fichiers relatifs à votre application (.php, .jpeg, .css, .js, etc.) Il est également possible de créer autant de sous-répertoires que nécessaire afin de faciliter l'organisation de votre code.

MULTIPLES POINTS D'ENTRÉE SUR LE SITE

Comme nous l'avons vu, un serveur web ne fait simplement que correspondre une URL (côté navigateur) à une ressource (au sens abstrait) disponible sur ledit serveur (dans le système de fichiers sur le disque dur).
Dans une approche décentralisée, il y a une correspondance directe entre :

Nom : 2019-07-23_215642.jpg
Affichages : 6638
Taille : 15,1 Ko
Votre application démarrera directement dans le fichier situé dans /abc/def/fichier.php Pourquoi pas, me direz-vous ? OK, poussons plus en avant la logique.

Nom : 2019-07-23_220136.jpg
Affichages : 6132
Taille : 22,5 Ko
Votre application démarrera directement dans le fichier situé dans /abc/def/ghi/jkl/mno/login.php

Maintenant, revenons à nos moutons : votre application a besoin d'un environnement d'exécution spécifique pour que tout le code source puisse être exécuté sans problème.
Donc pour reconstruire cet environnement et comme vous êtes un développeur sensé vous allez probablement créer un répertoire dans votre arborescence sous /www/bootstrap/ afin d'y loger tous les scripts d'initialisation : PDO, constantes, autoloader, durée d'exécution max, etc.
Le hic c'est qu'à chaque nouveau script, vous allez devoir jongler avec les require_once __DIR_.'/../../../../bootstrap/PDO.php; le nombre de /../, c'est 3 ou 4 ?
Ça sera pareil quand il s'agira d'inclure les fichiers du modèle pour bâtir une réponse...
Ça sera très vite ingérable et sacrément gonflant, croyez-moi et vous allez finir par vous maudire ainsi que vos descendants sur des générations et des générations


UNIQUE POINT D'ENTRÉE SUR LE SITE

L'autre approche, dite centralisée, va permettre de vous faciliter grandement la vie et de bénéficier d'un système très vite opérationnel et sans avoir à fournir trop d'efforts.
On va poser le principe que toutes les requêtes arrivant sur le serveur soient redirigées sur un unique point d'entrée, un fichier PHP, appelé, attention ! Roulements de tambour : index.php. Bon vous me direz quelle folie, hein !?!! M'enfin de temps en temps...
Donc comme ce fichier va récupérer absolument toutes les requêtes, il va nous être très facile de procéder à l'amorçage de notre environnement d'exécution.
Plus de gymnastique douloureuse, juste en début de fichier un simple include 'bootstrap/PDO.php'; suivi de la suite des fichiers nécessaires au démarrage.
Et ce qui n'est pas négligeable, c'est que comme vous êtes à la racine de votre site, il est très facile de créer une constante qui contient le chemin physique de la racine du site sur le serveur, le classique define('DIR_ROOT', __DIR__.DIRECTORY_SEPARATOR);.

Pour parvenir à ce résultat : 2 voies possibles : soit vous configurez le serveur web afin que lui se charge d'effectuer la redirection (ma solution préférée), soit vous codez toutes vos URL de manière à forcer la redirection quelque chose dans le genre : http://www.monsite.fr/index.php?page=login ou http://www.monsite.fr/index.php?page=loginsubmit.

Comme les URL sont dé-corrélées de la structure des répertoires et des fichiers sur le disque dur, la contrepartie de cette approche c'est qu'il va falloir disposer d'un système interne à l'application qui va être capable d'identifier quel fichier PHP exécuter pour chaque URL reçue.



Un petit aparté s'impose, comme le vocabulaire est très important dès qu'il s'agit de technique, il faut bien noter ceci :

Nom : 2019-07-23_224723.jpg
Affichages : 6194
Taille : 21,8 Ko

Quand vous êtes dans un navigateur (Firefox, Chrome, Opéra, Safari...), vous allez parler d'URL, mais quand vous allez vous placer du côté du serveur web, vous allez parler de ROUTE. La distinction est subtile, mais importante : une route est une URL qui a été analysée. C'est-à-dire que l'URL a été entièrement parsée afin d'extraire tous les composants séparément selon la norme RFC3986.

Une fois que les éléments constitutifs d'une URL sont connus, une application web va devoir les comparer avec son paramétrage interne afin d'essayer de trouver si elle dispose des ressources adéquates pour être en mesure de construire une réponse appropriée et de l'envoyer au navigateur client. Cette étape essentielle s'appelle le ROUTAGE et s'appuie sur une ou plusieurs table de routage. Si aucune route ne correspond à l'URL alors l'application enverra une réponse de type Error 404.

Dans nos exemples, une table de routage indiquerait que :
- quand page = "login" alors il faut aller transférer le flux de traitement à \Utilisateur\Controller\Login.php,
- quand page = "loginsubmit" alors il faut aller transférer le flux de traitement à \Utilisateur\Controller\LoginSubmit.php,

Maintenant nous pouvons compléter les assertions du début :

A CHAQUE ACTION DISPONIBLE DANS LE SITE WEB DEVRA CORRESPONDRE UN SEUL ET UNIQUE URL/ROUTE/CONTRÔLEUR QUI SERA DE FAIT L'UNIQUE POINT D'ENTRÉE DU SITE POUR CE TRAITEMENT

Pour résumer schématiquement :
Nom : action_url_controleur.jpg
Affichages : 6245
Taille : 28,4 Ko
Simple, non ?




L'énorme avantage de l'approche centralisée, c'est qu'il n'y a plus aucune exposition de votre arborescence partielle du disque dur sur le web.
Par ailleurs, avec ce système, il vous sera tout à fait possible d'utiliser des URL personnalisées à souhait pour peu que vous ayez un système de routage souple.

Nous arrivons à la fin de l'exposé théorique du concept MVC.

Dans un autre billet de blog, je vais coder entièrement un cas pratique afin d'exposer la mise en oeuvre de ce concept abstrait, mais fort pratique. Pour cela, je n'utiliserai que la Programmation Orientée Objet (d'une manière simple) et vous verrez ainsi comment tout cela se met en application.

Bon code à tous

rawsrc

Envoyer le billet « Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Le retour - Théorie » dans le blog Viadeo Envoyer le billet « Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Le retour - Théorie » dans le blog Twitter Envoyer le billet « Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Le retour - Théorie » dans le blog Google Envoyer le billet « Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Le retour - Théorie » dans le blog Facebook Envoyer le billet « Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Le retour - Théorie » dans le blog Digg Envoyer le billet « Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Le retour - Théorie » dans le blog Delicious Envoyer le billet « Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Le retour - Théorie » dans le blog MySpace Envoyer le billet « Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Le retour - Théorie » dans le blog Yahoo

Mis à jour 28/08/2019 à 14h14 par ClaudeLELOUP

Catégories
PHP , Développement Web

Commentaires

  1. Avatar de laurentSc
    • |
    • permalink
    Bravo pour la réactique Je me suis abonné à ton blog et en moins d'un quart d'heure, j'ai reçu un mail ; je commence ma lecture...
  2. Avatar de laurentSc
    • |
    • permalink
    Au sujet de ta définition du rôle des 3 éléments (modèle, vue et contrôleur), j'avais retenu les définitions suivantes :
    modèle : récupération des données (par exemple, requêtes SQL) ;
    vue : affichage ;
    contrôleur : traitements.
    Plus faciles à retenir que les tiennes ; qu'en penses-tu ?

    vous configurez le serveur web afin que lui se charge d'effectuer la redirection (ma solution préférée)
    l'implémentation de ça ne ressemble-t-elle pas à mettre RewriteRule ^([a-zA-Z0-9_\-]*)?/?$ index.php?page=$1&%{QUERY_STRING} dans le fichier .htaccess ?

    Comme vous n'êtes pas sans le savoir
    oui, grâce à un certain rawsrc...
  3. Avatar de Pierre Fauconnier
    • |
    • permalink
    Super billet

    Une belle approche théorique. J'avais déjà goûté du MVC en php avec un tuto amenant à la création d'un framework php, mais ton approche complète le tuto de façon magistrale.
  4. Avatar de rawsrc
    • |
    • permalink
    Merci
    J'ai essayé de rester explicite sans m'embarquer dans des considérations techniques trop abstraites.
    J'espère que ça reste clair pour les p'tits nouveaux qui s'y frottent pour la première fois. Ce n'est pas évident de bien s'en rendre compte quand ça te paraît évident avec les années derrière.
  5. Avatar de Gobble
    • |
    • permalink
    Sympa, ton article.

    De mon coté, je vois le modèle comme une suite de méthode comprenant uniquement les différentes requêtes sql CRUD, la classe du modèle devant être strictement identique à l'entité de la base de donnée qu'elle gère, ses attributs correspondants aux colonnes de la table .

    Le contrôleur est un chef d'orchestre qui s'occupe de récupérer la data du modèle, la traiter au besoin par des méthodes inhérentes à celui ci (tri pour l'affichage principalement)

    J'ai souvent tendance a gonfler mes contrôleurs de multiples méthodes qui deviennent fourre-tout, je vais méditer sur ta pratique d'isoler chaque action sur un contrôleur.
  6. Avatar de rawsrc
    • |
    • permalink
    Salut,
    Citation Envoyé par Gobble
    De mon coté, je vois le modèle comme une suite de méthode comprenant uniquement les différentes requêtes sql CRUD, la classe du modèle devant être strictement identique à l'entité de la base de donnée qu'elle gère, ses attributs correspondants aux colonnes de la table .
    C'est parce que tu n'as jamais eu besoin de sortir de ce schéma de fonctionnement. Le modèle peut être beaucoup plus vaste. Par exemple, j'ai eu à coder un jour une application scientifique avec des calculs d'une autre dimension, j'avais des classes (de la couche modèle) en charge de ces calculs, ces derniers devaient être validés par des physiciens. Mais je n'avais aucun besoin d'accéder à la base de données.

    Citation Envoyé par Gobble
    J'ai souvent tendance a gonfler mes contrôleurs de multiples méthodes qui deviennent fourre-tout, je vais méditer sur ta pratique d'isoler chaque action sur un contrôleur.
    Pour l'ajout des fonctionnalités, il est bien plus aisé de créer un fichier en charge que du nouveau traitement que d'aller modifier un contrôleur existant qui au final va devenir obèse.
    Voici la classe mère de tous mes contrôleurs :
    Code php : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    <?php
     
    declare(strict_types=1);
     
    namespace rawsrc\Core;
     
    use rawsrc\Server\{ Task, Response\Response };
     
    abstract
    class Controller
    {
        /**
         * Function that must bring a response to the current Task
         */
        abstract public function invoke();
     
        /**
         * @var Task
         */
        private $task;
     
        /**
         * @param Task $p
         */
        public function setTask(Task $p)
        {
            $this->task = $p;
        }
     
        /**
         * @return Task
         */
        public function task(): Task
        {
            return $this->task;
        }
     
        /**
         * @param Response $p
         */
        public function setResponse(Response $p)
        {
            $this->task->setResponse($p);
        }
     
        /**
         * @return Response
         */
        public function response(): ?Response
        {
            return $this->task->response();
        }
    }