Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Programmation en PHP 7 - Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Cas pratique,
Par rawsrc

Le , par rawsrc

0PARTAGES

Bonjour les développeurs,

j'espère que vous avez bien assimilé la théorie du développement selon l'approche MVC Modèle - Vue - Contrôleur de mon précédent billet parce qu'aujourd'hui on va se lancer dans le grand bain avec la mise en application de ce concept.
Pour ceux qui auraient besoin de se rafraîchir la mémoire, c'est par que ça se passe.

Pour corser le tout, je vais privilégier une approche objet de ce paradigme.
Je vais essayer de vous exposer simplement les fondements de la Programmation Orientée Objet (POO pour les intimes) et qui sait, en convertir un ou deux
Enfin, il faut aussi avoir assimilé au préalable la théorie et le fonctionnement des espaces de nom (namespace) et de l'autochargement des classes (autoloading), vous trouverez tout ce qu'il faut sur cet autre billet rédigé aussi par mes soins.

INTRODUCTION AUX BASES DE LA PROGRAMMATION ORIENTÉE OBJET

On va prendre un cas hyper simple qui va parler à tout développeur web : un échange entre un navigateur et un serveur web.



L’utilité principale de la programmation objet réside dans la possibilité de représenter des éléments tangibles sous forme de concepts abstraits (équivalent à une représentation purement informatique).

Dans notre cas de figure, le serveur web devra envoyer une réponse (à ce stade on se pas encore laquelle, mais il doit envoyer une réponse), donc pour faciliter le traitement du côté du serveur web, on va modéliser une représentation abstraite de la réponse. C’est cette représentation qui va être manipulée par le programme. Autrement dit on va créer une class Response qui va être l’alter ego abstrait de la réponse physique du serveur web.

Ainsi quand le développeur manipulera une instance de Response, il saura immédiatement qu’il manipule la réponse finale du serveur qui va être envoyée au navigateur à la toute fin du traitement.

Il va de soi que c’est exactement pareil pour une requête. Pour faciliter sa manipulation, elle aura une représentation abstraite dans le monde informatique qui sera l’alter ego de la requête physique reçue par le serveur. Nous aurons donc une autre classe, class Request en charge de tout ce qui se rapporte à une requête web.

Plus généralement, il faut bien comprendre que le passage au monde objet correspond dans un premier temps à une modélisation d'une problématique (ou d'une réalité) sous forme de concepts abstraits.
Cette abstraction va permettre à un développeur de savoir précisément ce qu'il manipule dans son monde dématérialisé. Généralement, le code devient plus parlant au premier coup d’œil.

La POO offre énormément en terme de fonctionnalités. Pour vous en convaincre, prenez n'importe quel livre consacré à la théorie de ce paradigme et vous verrez que d'une part il est généralement gros et d'autre part que vous allez y consacrer un certain temps d'apprentissage pour vous familiariser avec le contenu. Dans un second temps, l'expérience finira par vous convaincre de que c'est, somme toute, "évident" .
Pour couvrir la POO, il faudrait bien plus que ce billet, je vais rester succinct, juste ce qu'il faut pour vous faire saliver.

Il faut reconnaître qu'il faut un peu de doigté pour modéliser correctement en POO. Le travers c'est qu'un débutant à tendance à créer des classes pour tout et n'importe quoi et cela finit immanquablement par une jolie noyade.

Revenons à nos moutons ; comme la réalité diffère, il va de soi que les classes dans le monde informatique vont avoir leurs spécificités.
Par exemple, le traitement d'une réponse est totalement différent d'une requête.

Pour une réponse, on va avoir besoin au minimum de :
  • connaître à l'avance les en-têtes à envoyer au navigateur en fonction du type de données à transmettre
  • avoir des données à envoyer


Pour une requête, on va avoir besoin au minimum de :
  • d'avoir l'URL d'appel morcelée en composants selon la norme en vigueur RFC3986


Donc, selon cette analyse, on va pouvoir créer 2 classes qui vont se charger de répondre aux besoins :

Classe Response :
Code php : Sélectionner tout
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
54
55
56
57
<?php 
  
declare(strict_types=1); 
  
namespace rawsrc; 
  
/** 
 * TUTORIAL DVP SUR LE CONCEPT MVC : MODÈLE-VUE-CONTRÔLEUR 
 * @link https://www.developpez.net/forums/blogs/32058-rawsrc/b7786/developpement-selon-l-approche-mvc-modele-vue-controleur-retour-theorie/ 
 * @link https://www.developpez.net/forums/blogs/32058-rawsrc/b7804/developpement-selon-lapproche-mvc-modele-vue-controleur-cas-pratique/ 
 * 
 * Classe en charge de la gestion d'une réponse générique 
 * Pour une réponse valide, le serveur doit envoyer d'abord les en-têtes et ensuite la réponse proprement dite 
 * 
 * Cette classe est instanciable, car il est tout à fait possible de créer une réponse à la volée selon ses besoins 
 * par exemple : renvoyer du xml, pdf... Il faudra juste adapter les en-têtes ($headers) et le corps de la réponse ($data) 
 */ 
class Response 
{ 
    /** 
     * @var array 
     */ 
    public $headers = []; 
  
    /** 
     * Stocke les données relatives à la réponse 
     * 
     * @var mixed 
     */ 
    public $data = null; 
  
    /** 
     * @param mixed $data 
     * @param array $headers  Array of headers to send first 
     */ 
    public function __construct($data = null, array $headers = []) 
    { 
        $this->data    = $data; 
        $this->headers = $headers; 
    } 
  
    /** 
     * Envoi des données de la réponse 
     */ 
    public function send() 
    { 
        // envoi des en-têtes (le type des données qui vont suivre) 
        foreach ($this->headers as $h) { 
            header($h); 
        } 
  
        // envoi des données (le navigateur sait à quoi s'attendre) 
        if ($this->data !== null) { 
            echo $this->data; 
        } 
    } 
}
Classe Request :
Si vous prenez le temps de lire la norme RFC, vous verrez que la classe ne fait que reprendre d'une manière simplifiée tous les composants d'une URL
Code php : Sélectionner tout
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
parse($url); 
    } 
 
    /** 
     * @return string 
     */ 
    public function scheme(): string 
    { 
        return $this->scheme; 
    } 
 
    /** 
     * @return string 
     */ 
    public function user(): string 
    { 
        return $this->user; 
    } 
 
    /** 
     * @return string 
     */ 
    public function host(): string 
    { 
        return $this->host; 
    } 
 
    /** 
     * @return array 
     */ 
    public function path(): array 
    { 
        return $this->path; 
    } 
 
    /** 
     * @return string 
     */ 
    public function port(): string 
    { 
        return $this->port; 
    } 
 
    /** 
     * @return array 
     */ 
    public function query(): array 
    { 
        return $this->query; 
    } 
 
    /** 
     * @return string 
     */ 
    public function fragment(): string 
    { 
        return $this->fragment; 
    } 
 
    /** 
     * @return bool 
     */ 
    public function isAjax(): bool 
    { 
        return $this->is_ajax; 
    } 
 
    /** 
     * @return bool 
     */ 
    public function isValid(): bool 
    { 
        return $this->is_valid; 
    } 
 
    /** 
     * PARSEUR d'url 
     * Sépare l'URL en composants selon la norme RFC3986 
     */ 
    private function parse(string $url) 
    { 
        // ici la fonction décompose l'url en composants 
        // quand vous manipulez une instance de la classe Request 
        // vous ne savez pas comment ce travail est fait => on va parler d'IMPLÉMENTATION 
        // pour vous c'est transparent : vous utilisez simplement la classe qui ENCAPSULE cette implémentation 
 
        $parts = parse_url($url); 
 
        if ($parts === false) { 
            return; 
        } 
 
        if (isset($parts['scheme'])) { 
            $this->scheme = $parts['scheme']; 
        } 
 
        if (isset($parts['host'])) { 
            $this->host = $parts['host']; 
        } 
 
        if (isset($parts['port'])) { 
            $this->port = $parts['port']; 
        } 
 
        if (isset($parts['user'])) { 
            $this->user = $parts['user']; 
            $this->pwd  = $parts['pass'] ?? ''; 
        } 
 
        if (isset($parts['path'])) { 
            $this->path = explode('/', trim($parts['path'], '/')); 
        } 
 
        if (isset($parts['query'])) { 
            $this->query = parse_str($parts['query']); 
        } 
 
        if (isset($parts['fragment'])) { 
            $this->fragment = $parts['fragment']; 
        } 
 
        if (isset($_SERVER['HTTP_X_REQUESTED_WITH'])) { 
            $this->is_ajax = (strtoupper($_SERVER['HTTP_X_REQUESTED_WITH']) === 'XMLHTTPREQUEST'); 
        } 
 
        $this->is_valid = true; 
    } 
}

Pour manipuler ces 2 concepts, rien de plus simple :
Code php : Sélectionner tout
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
<?php 
  
declare(strict_types=1); 
  
// ici on déclare les dépendances du code ci-dessous 
// cela permet au mécanisme d'autoloading de localiser les fichiers contenant la définition des classes 
use rawsrc\Request; 
use rawsrc\Response; 
  
// vous n'avez plus à vous préoccuper de comment ça fonctionne en interne 
// les classes encapsulent tout le code, vous ne manipulez que des concepts abstraits 
  
// par exemple, on veut avoir des détails sur une requête  
$request = new Request('https://www.developpez.net/forums/blogs/32058-rawsrc/b7804/developpement-selon-lapproche-mvc-modele-vue-controleur-cas-pratique/') 
// notez que le constructeur de la classe attend en paramètre : string $url 
// et il déclenche l'analyse automatiquement dès l'instanciation avec : $this->parse($url); 
  
// ici comme vous pouvez le constater vous ne vous préoccupez pas de savoir comment 
// le parsage de l'url a été fait, vous accédez directement au résultat : la classe Request  
// encapsule les traitements et devient pour ainsi dire une boite noire 
echo $request->scheme(), '';    // https 
echo $request->host(), '';      // www.developpez.net 
echo $request->path()[0], '';   // forums 
echo $request->path()[1], '';   // blogs 
echo $request->path()[2], '';   // 32058-rawsrc 
echo $request->path()[3], '';   // b7804 
echo $request->path()[4], '';   // developpement-selon-lapproche-mvc-modele-vue-controleur-cas-pratique 
  
  
// maintenant voyons une réponse 
$response = new Response(); 
  
// je souhaite envoyer des données json 
// je paramètre la réponse :  
$response->headers[] = 'content-type: application/json'; 
$response->data      = json_encode(['a', 'b', 'c']); 
$response->send();   // le navigateur recevra du json 
  
// il aurait aussi été possible de faire tout en une seule ligne 
// le constructeur de la classe Response le permet 
(new Response(json_encode(['a', 'b', 'c']), ['content-type: application/json']))->send();
Ces 2 exemples triviaux ne servent que d'illustration dans le cadre d'une initiation au vaste monde de la POO.

UNE PINCÉE D'HÉRITAGE

Il va falloir aborder ce concept assez succinctement, car il va être nécessaire dans notre cas pratique MVC.

Comme les classes ne sont qu'une représentation théorique, elles peuvent être très générales et très abstraites. Comme la POO a été conçue pour répondre à des besoins réels, la spécialisation d'une classe très générale est tout à fait possible via un mécanisme appelé l'héritage. Une classe fille va hériter de sa classe mère, elle va spécialiser la classe mère.

Par exemple, prenons le cas de la classe générique Response, le code de cette classe est très ouvert, vous pouvez à la volée paramétrer une réponse avec 2 lignes de code.
Si par exemple, vous devez envoyer très souvent des réponses au format JSON, cela va s'avérer très vite fastidieux de toujours recopier le paramétrage de la réponse, sans compter la redondance de code et le risque d'erreur qui va avec.
Donc pour palier à cet état de fait, on va spécialiser la classe mère et figer le paramétrage des en-têtes dans la classe fille de manière à n'avoir plus que des données à passer :
Code php : Sélectionner tout
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
<?php 
  
declare(strict_types=1); 
  
namespace rawsrc; 
  
use rawsrc\Response; 
  
/** 
 * TUTORIAL DVP SUR LE CONCEPT MVC : MODÈLE-VUE-CONTRÔLEUR 
 * @link https://www.developpez.net/forums/blogs/32058-rawsrc/b7786/developpement-selon-l-approche-mvc-modele-vue-controleur-retour-theorie/ 
 * @link https://www.developpez.net/forums/blogs/32058-rawsrc/b7804/developpement-selon-lapproche-mvc-modele-vue-controleur-cas-pratique/ 
 * 
 * Réponse : au format JSON 
 */ 
class Json 
extends Response 
{ 
    // redéfinition de la méthode send() de la classe parente 
    public function send() 
    { 
        // ici on force les en-têtes, on ne tient pas compte des en-têtes qui auraient pu être définis dans $headers 
        // comparez ce code avec celui de la classe parente 
        // on a la certitude d'envoyer le bon type au navigateur 
        // LA CLASSE MÈRE A ÉTÉ SPÉCIALISÉE 
        // il est tout à fait possible au développeur de mettre n'importe quoi dans $data 
        // généralement on considère le développeur comme intelligent 
        // après le navigateur fera ce qu'il pourra avec ce qu'il reçoit si le mime-type ne correspond pas 
  
        header('content-type: application/json'); 
        echo $this->data; 
    } 
}

Un exemple :
Code php : Sélectionner tout
1
2
3
4
5
6
7
8
<?php 
  
declare(strict_types=1); 
  
use rawsrc\Json; 
  
$response = new Json(json_encode(['a', 'b', 'c']); 
$response->send();   // quoi qu'il arrive les bons en-têtes seront envoyés au client, la classe Json s'en charge
J'espère que vous commencez à saisir l'étendue du paradigme objet.

On en a vu assez pour se lancer dans notre cas pratique : attachez votre ceinture et mettez vos bretelles

MISE EN APPLICATION DU MVC : CAS PRATIQUE

Le support à la démonstration sera une page d'authentification basique : identifiant/mot de passe avec formulaire et accès à la base de données.

ANALYSE DE LA SITUATION

2 problèmes seront traités simultanément :
  • modélisation sous forme objet du MVC
  • modélisation sous forme objet de la problématique d'exemple


Dites-vous bien que les choix que j'opère ne sont pas l'unique solution possible, ils sont simplifiés de manière à pouvoir appréhender le concept plus facilement.

MODÉLISATION SOUS FORME OBJET DU MVC

Le serveur traitera chaque requête reçue comme une tâche (Task). Chaque tâche sera donc composée d'une requête et d'une réponse, comme ceci :



La tâche devra être capable de prendre en charge absolument TOUTES LES REQUÊTES qui se présenteront selon un processus standardisé. Ceci va permettre de rajouter des fonctionnalités au site sans avoir à chaque fois à se poser des tas de questions sur le comment vais-je bien donc pouvoir faire.
La class Task devra dispatcher la requête, trouver le modèle qui sera en mesure de la traiter, passer le flux de traitement au contrôleur approprié et au final collecter la réponse à envoyer.

Code source de la class Task
Code php : Sélectionner tout
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
 séparation de l'url en ses constituants) 
 *  - recherche d'une action correspondant à la requête (étape du ROUTAGE => analyse des constituants de l'URL) 
 *  - si action trouvée : transfert du flux de traitement au contrôleur rattaché à l'action 
 *  - sinon             : génération d'une réponse de type Error avec le code (400 Bad Request, 404 Not Found, etc.) 
 * 
 * Le contrôleur doit par principe fournir une réponse quelle qu'elle soit, c'est-à-dire mouvementer la valeur de $response de la présente classe 
 */ 
class Task 
{ 
    /** 
     * @var Request 
     */ 
    private $request = null; 
    /** 
     * @var Response 
     */ 
    private $response = null; 
    /** 
     * @var Action 
     */ 
    private $action = null; 
 
    /** 
     * @return Url 
     */ 
    public function request(): ?Request 
    { 
        return $this->request; 
    } 
 
    /** 
     * @param Response $p 
     */ 
    public function setResponse(Response $p) 
    { 
        $this->response = $p; 
    } 
 
    /** 
     * @return Response 
     */ 
    public function response(): ?Response 
    { 
        return $this->response; 
    } 
 
    /** 
     * @return Action 
     */ 
    public function action(): ?Action 
    { 
        return $this->action; 
    } 
 
    /** 
     * Fonction en charge d'apporter une Réponse à la requête 
     * Pour y parvenir, elle va essayer d'abord de trouver l'action correspondante à la requête ; 
     * si la recherche est fructueuse alors elle va automatiquement appeler le Contrôleur rattaché à 
     * l'action qui se chargera de fournir une réponse à la tâche 
     */ 
    public function dispatch() 
    { 
        // reconstruction de l'uri complète 
        $scheme = 'http'.(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 's' : ''); 
        $uri    = $scheme.'://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; 
 
        // le parsage de l'url est automatique à l'instanciation (voir le code Request::__construct()) 
        $this->request = new Request($uri); 
 
        if ($this->request->isValid() === false) { 
            // BAD REQUEST : création de la réponse à la volée 
            $this->response = new Response('Invalid request', '400 Bad Request'); 
            return; // fin de traitement 
        } 
 
        /*********** 
         * ROUTAGE * 
         ***********/ 
 
        /** @param string $full_class_name  Nom pleinement qualifié de la classe en charge du concept */ 
        $search_action_controller = function(string $full_class_name) { 
            $obj = new $full_class_name; 
 
            foreach ($obj->actions() as $action) { 
                /** @var Action */ 
                $action->setTask($this); 
                if ($action->handleRequest($this->request)) { 
                    // l'action est en mesure de prendre en charge la requête 
                    // on renvoie le nom pleinement qualifié du contrôleur qui va s'en charger 
                    return $action->controller(); 
                } 
            } 
            return false; 
        }; 
 
        // on extrait la valeur du premier segment du path 
        // par principe on a défini que cela correspondait à 
        // l'identifiant du concept dans l'application 
        $concept    = $this->request->path()[0] ?? ''; 
        // on recherche une correspondance dans la table des concepts 
        $class      = CONCEPTS[$concept] ?? ''; 
        // on prépare un controller générique qui va prendre en charge la requête 
        $controller = false; 
 
        // si aucune correspondance directe trouvée 
        if ($class === '') { 
            // on parcourt les concepts de l'application à la recherche 
            // de l'action capable de prendre en charge la requête 
            foreach (CONCEPTS as $cls) { 
                $controller = $search_action_controller($cls); 
                if ($controller !== false) { 
                    break; 
                } 
            } 
        } else { 
            $controller = $search_action_controller($class); 
        } 
 
        if ($controller === false) { 
            $this->response = new Response('Unable to manage the request', '400 Bad Request'); 
            return; 
        } 
 
        // on passe le flux d'exécution au contrôleur qui 
        // lui doit obligatoirement fournir une réponse 
 
        /** @var Controller */ 
        $controller = new $controller(); 
        $controller->setTask($this); 
        $controller->invoke(); 
 
        // on s'assure que la réponse a bien été fournie par le contrôleur 
        // sinon on en fournit une remontant l'erreur 
        if ( ! ($this->response instanceof Response)) { 
            $this->response = new Response('Invalid response format', '500 Internal Server Error'); 
        } 
 
        // à la fin de cette fonction : on est ABSOLUMENT certain 
        // que le programme a fourni une réponse à la requête 
    } 
}


MODÉLISATION SOUS FORME OBJET DE LA PROBLÉMATIQUE D'EXEMPLE

Pour modéliser plus facilement une problématique, il faut diviser la totalité en morceaux plus petits et plus facilement gérables.
Personnellement, j'ai l'habitude de tout diviser en concepts et chaque concept est le plus autonome possible : il va contenir ses contrôleurs, ses vues, son modèle... (c'est comme si c'était un mini MVC)
Par exemple, la page d'accueil va correspondre au concept Home, la gestion d'un utilisateur au concept Utilisateur, etc.
De cette conception va découler naturellement une organisation des fichiers très simple :



N'oubliez pas que chaque fichier ne comporte qu'une seule et unique classe, ainsi on est capable de très rapidement savoir qui fait quoi. C'est beaucoup plus lisible, non ?

Comme nous l'avons vu précédemment, tout ce qui se passe sur le serveur correspond à une Action.
Et pour chaque Action devra correspondre une route et un contrôleur :



Ce qui nous donne :
Code php : Sélectionner tout
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
set($p); 
    } 
 
    /** 
     * @param  Task $p 
     * @return self 
     */ 
    public function setTask(Task $p): self 
    { 
        $this->task = $p; 
        if ($this->controller instanceof Controller) { 
            $this->controller->setTask($p); 
        } 
        return $this; 
    } 
 
    /** 
     * @return Task 
     */ 
    public function task(): ?Task 
    { 
        return $this->task; 
    } 
 
    /** 
     * @param  mixed $p     closure that return a strict boolean value | null 
     * @return self 
     */ 
    public function setRoute($p): self 
    { 
        if (is_callable($p) || ($p === null)) { 
            $this->route = $p; 
        } 
        return $this; 
    } 
 
    /** 
     * @return mixed Closure|null 
     */ 
    public function route() 
    { 
        return $this->route; 
    } 
 
    /** 
     * @param  mixed $p     Controller | closure that return a Controller object | null | string 
     * @return slef 
     */ 
    public function setController($p): self 
    { 
        if (is_string($p) || ($p instanceof Controller) || is_callable($p) || ($p === null)) { 
            $this->controller = $p; 
        } 
        return $this; 
    } 
 
    /** 
     * @return mixed Controller|\Closure|null|string 
     */ 
    public function controller() 
    { 
        return $this->controller; 
    } 
 
    /** 
     * @param  mixed $p 
     * @return self         Response | closure that return a Response object | null | string 
     */ 
    public function setResponse($p): self 
    { 
        if (is_string($p) || ($p instanceof Response) || is_callable($p) || ($p === null)) { 
            $this->response = $p; 
        } 
        return $this; 
    } 
 
    /** 
     * @return mixed    Response|closure|null|string 
     */ 
    public function response() 
    { 
        return $this->response; 
    } 
 
    /** 
     * Set many at once 
     * 
     * @param array $p  [acl, route, controller, response] 
     */ 
    public function set(array $p) 
    { 
        if (isset($p['route'])) { 
            $this->setRoute($p['route']); 
        } 
 
        if (isset($p['controller'])) { 
            $this->setController($p['controller']); 
        } 
 
        if (isset($p['response'])) { 
            $this->setResponse($p['response']); 
        } 
    } 
 
    /** 
     * @param  Request $p 
     * @return bool 
     * 
     * @throws BadFunctionCallException 
     */ 
    public function handleRequest(Request $p): bool 
    { 
        if (is_callable($this->route)) { 
            $func  = $this->route; 
            $match = $func($p); 
            if (is_bool($match)) { 
                return $match; 
            } else { 
                throw new BadFunctionCallException('Callable does not return a strict boolean'); 
            } 
        } 
        return false; 
    } 
}


CONCEPTS MÉTIER

Tout est concept dans l'organisation , attardons nous sur le concept Utilisateur, comme tout concept il est générique c'est-à-dire qu'il est capable de gérer n'importe quel utilisateur sans exception. Tous les cas de figure doivent être prévus, c'est le boulot d'un développeur aguerri.
Code php : Sélectionner tout
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
54
55
56
57
58
<?php 
  
declare(strict_types=1); 
  
namespace src\Utilisateur; 
  
use rawsrc\Action; 
use rawsrc\Request; 
  
use src\Utilisateur\Controller\{ Login, Connect, Dashboard }; 
use src\Utilisateur\Model\Data; 
  
/** 
 * Classe représentant le concept Utilisateur 
 */ 
class Utilisateur 
{ 
    /** 
     * Liste des actions possibles pour un utilisateur 
     * 
     * @return array [Action] 
     */ 
    public function actions(): array 
    { 
        // pour bien comprendre les concepts, je vais aller au plus simple 
        // il est tout à fait possible d'écrire son code autrement afin de gagner en souplesse et possibilités 
        return [ 
            // action 1 = /utilisateur/login    pour afficher le formulaire de connexion 
            new Action([ 
                // cette fonction (route) ne fait que vérifier si les composants de la requête en paramètre lui correspondent 
                // si la correspondance est totale alors cette action sera retenue et considérée comme l'action en cours 
                // donc c'est le controller rattaché à cette action qui héritera du flux de traitement 
                // route = /utilisateur/login 
                'route'      => function(Request $p) { return $p->path()[1] === 'login'; }, 
                'controller' => Login::class 
            ]), 
            // route = /utilisateur/connect  pour la soumission du formulaire de connexion 
            new Action([ 
                'route'      => function(Request $p) { return $p->path()[1] === 'connect'; }, 
                'controller' => Connect::class 
            ]), 
            // route = /utilisateur/dashboard       (tableau de bord) 
            new Action([ 
                'route'      => function(Request $p) { return $p->path()[1] === 'dashboard'; }, 
                'controller' => Dashboard::class 
            ]), 
        ]; 
    } 
  
    /** 
     * Pointeur vers la classe en charge de la gestion des données -> LE MODELE 
     * @return Data 
     */ 
    public function data() 
    { 
        return Model\Data::class; 
    } 
}
Comme à chaque fois, je vous invite à bien lire les commentaires disséminés un peu partout dans le code source. Vous allez trouver déjà beaucoup de réponses à vos interrogations.

Vu que l'organisation est générique et l'approche du code standardisée, il va être dorénavant beaucoup plus simple d'étendre les fonctionnalités du site sans se noyer. Il fat toujours s'efforcer de préserver la même architecture tout au long du développement. Le MVC vous permet assez facilement de le faire.

LES VUES

Vous allez voir immédiatement que même les vues dans mon code source sont des classes. Cette approche est tirée d'un framework personnel que j'ai nommé Rerender et qui est bâti autour de ce principe. Pour l'instant, il n'est pas open source, mais j'y réfléchis à le rendre ouvert.
Donc, chaque morceau de code de rendu peut être encapsulé dans un bloc vue.
Code php : Sélectionner tout
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php 
  
declare(strict_types=1); 
  
namespace rawsrc; 
  
/** 
 * TUTORIAL DVP SUR LE CONCEPT MVC : MODÈLE-VUE-CONTRÔLEUR 
 * @link https://www.developpez.net/forums/blogs/32058-rawsrc/b7786/developpement-selon-l-approche-mvc-modele-vue-controleur-retour-theorie/ 
 * @link https://www.developpez.net/forums/blogs/32058-rawsrc/b7804/developpement-selon-lapproche-mvc-modele-vue-controleur-cas-pratique/ 
 * 
 * Représentation d'un bloc de code HTML 
 */ 
class ViewBlock 
{ 
    /** 
     * @var string      code source HTML du bloc courant 
     */ 
    protected $code = ''; 
  
    public $vars = []; 
  
    public function setCode(string $p) 
    { 
        $this->code = $p; 
    } 
  
    public function render() 
    { 
        $this->code = ''; 
    } 
  
    /** 
     * Renvoie une valeur échappée du tableau $vars 
     * 
     * @param  mixed $key 
     * @return mixed        string|null 
     */ 
    public function esc($key) 
    { 
        if (isset($this->vars[$key])) { 
            return htmlspecialchars($this->vars[$key], ENT_QUOTES, 'utf-8'); 
        } 
  
        return null; 
    } 
  
    /** 
     * Renvoie le tableau de toutes les variables échappées 
     * @return array 
     */ 
    public function escVars(): array 
    { 
        $hsc = function($p) { return htmlspecialchars($p, ENT_QUOTES, 'utf-8'); }; 
  
        return array_map($hsc, $this->vars); 
    } 
  
    /** 
     * @return string 
     */ 
    public function code(): string 
    { 
        if ($this->code === '') { 
            $this->render(); 
        } 
  
        return $this->code; 
    } 
  
    public function __tostring() 
    { 
        return $this->code(); 
    } 
}
Par exemple le formulaire de connexion d'un utilisateur est codé ainsi :
Code php : Sélectionner tout
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
<?php 
  
declare(strict_types=1); 
  
namespace src\Utilisateur\View; 
  
use rawsrc\ViewBlock; 
  
class Login 
extends ViewBlock 
{ 
    public function render() 
    { 
        $error = isset($this->vars['error']) ? 'block' : 'none'; 
  
        $this->code = <<<html 
<p> Blog de <strong>rawsrc</strong> sur DVP </p> 
<p>Veuillez vous identifier</p> 
<form method=post action="{$this->vars['url_submit']}"> 
    <label>Identifiant</label> 
    <input type="text" name="login" value="{$this->esc('login')}"> 
    <label>Mot de passe</label> 
    <input type="password" name="pwd" value=""> 
    <input type="submit" name="submit" value="SE CONNECTER"> 
</form> 
 
<p style="display:{$error}"><strong>{$this->esc('error')}</strong></p> 
html; 
    } 
}
Et il s'insère dans le layout d'une page comme ceci :
Code php : Sélectionner tout
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
<?php 
  
declare(strict_types=1); 
  
namespace src\View; 
  
use rawsrc\ViewBlock; 
  
/** 
 * TUTORIAL DVP SUR LE CONCEPT MVC : MODÈLE-VUE-CONTRÔLEUR 
 * @link https://www.developpez.net/forums/blogs/32058-rawsrc/b7786/developpement-selon-l-approche-mvc-modele-vue-controleur-retour-theorie/ 
 * @link https://www.developpez.net/forums/blogs/32058-rawsrc/b7804/developpement-selon-lapproche-mvc-modele-vue-controleur-cas-pratique/ 
 * 
 * Représentation d'un modèle de page 
 * 
 * Les variables sont passées au modèle via le tableau $this->vars. 
 * Dans le code ci-dessous n'est attendu que la variable 'body' qui doit contenir le code de rendu du corps de la page 
 */ 
class Layout 
extends ViewBlock 
{ 
    public function render() 
    { 
        $url_home = URL_HOME; 
  
        $this->code = <<<html 
<!DOCTYPE html> 
<head> 
    <base href="{$url_home}" 
</head> 
<body> 
    {$this->vars['body']} 
</body> 
html; 
    } 
}
Le contrôleur s'occupe de paramétrer la vue, comme ceci :
Code php : Sélectionner tout
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
<?php 
  
declare(strict_types=1); 
  
namespace src\Utilisateur\Controller; 
  
use rawsrc\{ Controller, Response }; 
use src\View\Layout; 
use src\Utilisateur\View\Login as Block; 
  
class Login 
extends Controller 
{ 
    public function invoke() 
    { 
        // corps de la vue 
        $block = new Block(); 
        $block->vars['url_submit'] = URL_HOME.'/utilisateur/connect'; 
  
        // layout de la page principale 
        $page  = new Layout(); 
        $page->vars['body'] = $block; 
  
        $this->setResponse(new Response($page)); 
    } 
}
Cela permet de morceler à loisir les fonctions de génération de l'affichage et de profiter de toutes les possibilités offertes par la POO.

Il vous tout à fait possible d'opter pour n'importe quel autre moteur de génération de rendu. Le concept MVC s'accommode très facilement de toute extension ou nouvel ajout.

SCHÉMA GLOBAL DU PRINCIPE MVC

Compte tenu des problèmes de compréhension de certaines étapes, j'ai décidé de vous proposer un schéma global de l'approche MVC mettant à plat toute l'articulation de la logique sous-jacente.
Voici globalement le travail accompli par ces 4 lignes de code situées dans index.php:

Code php : Sélectionner tout
1
2
3
4
5
use rawsrc\Task; 
  
$task = new Task(); 
$task->dispatch(); 
$task->response()->send();



NOTION DE CONCEPT



FICHIERS DU PROJET ET MISE EN ROUTE

Pour que vous puissiez tester le projet en réel, il vous faudra une petite base de données MySQL avec qu'une seule table :
Code sql : Sélectionner tout
1
2
3
4
5
6
7
8
CREATE TABLE `t_user` ( 
  `user_id` INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, 
  `user_login` VARCHAR(255) COLLATE utf8_general_ci NOT NULL, 
  `user_pwd_hash` VARCHAR(255) COLLATE utf8_general_ci NOT NULL, 
  PRIMARY KEY USING BTREE (`user_id`), 
  UNIQUE KEY `user_login` USING BTREE (`user_login`) 
) ENGINE=InnoDB 
AUTO_INCREMENT=1 ROW_FORMAT=DYNAMIC CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

et juste une seule ligne de données :
Code sql : Sélectionner tout
1
2
INSERT INTO `t_user` (`user_login`, `user_pwd_hash`) VALUES 
  ('rawsrc', '$argon2i$v=19$m=1024,t=2,p=2$Uzdldm5iWTJMOGp3cXdsZQ$4ZE9jZ8YyH4SGXsjT/ED/XYQpf0oVJe4TRWQXXS04eQ');
Pour tester une identification réussie : login = rawsrc, mot de passe = 123456

REDIRECTION VERS index.php

Comme nous l'avons vu, un seul et unique point d'entrée de toutes les requêtes est nécessaire au bon fonctionnement du projet, pour cela il faudra faire une redirection au niveau du serveur web comme ceci pour apache :
RewriteEngine on
RewriteCond %{REQUEST_URI} /(index)(\..{3,4})?$ [NC]
RewriteRule ^.* http://dev.mvc.fr [R=301,L]
RewriteCond %{REQUEST_FILENAME} !^/css/.*$
RewriteCond %{REQUEST_FILENAME} !^/js/.*$
RewriteCond %{REQUEST_FILENAME} !^/img/.*$
RewriteRule . /index.php [QSA,L]
Paramétrage final

Il y a quelques constantes à adapter à votre environnement dans le fichier index.php
Code php : Sélectionner tout
1
2
3
4
5
6
7
8
9
// Gestion des constantes 
define('URL_HOME', 'http://dev.mvc.fr'); 
// Base de données 
define('DB_SCHEME', 'mysql'); 
define('DB_HOST', 'localhost'); 
define('DB_NAME', 'db_mvc'); 
define('DB_PORT', '3306'); 
define('DB_USER', 'root'); 
define('DB_PWD', '');
Rien d'extraordinaire que du standard.

Et enfin, le ficher ZIP qui contient tous les fichiers du projet
[ATTACH]496067d1/a/a/a" />

CONCLUSION

Nous sommes arrivés au terme de ce tutoriel qui j'espère vous aura été très utile.
Dites-vous bien que le passage à un codage MVC va nécessiter une nouvelle manière de penser (beaucoup mieux découplée) ainsi qu'un certain travail de reprise de vos codes existants, mais c'est pour du mieux alors !!!

Bon code à tous, bienvenue aux p'tits nouveaux dans le vaste monde de la POO.



rawsrc

Une erreur dans cette actualité ? Signalez-le nous !

Contacter le responsable de la rubrique PHP

Partenaire : Hébergement Web