Voir le flux RSS

rawsrc

[Actualité] PHP 7+ // Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Cas pratique

Note : 2 votes pour une moyenne de 5,00.
par , 30/07/2019 à 01h41 (2388 Affichages)
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.

Nom : 2019-07-26_200729.jpg
Affichages : 3195
Taille : 28,7 Ko

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 - 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
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 - 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
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
<?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 requête générique
 * Anlalyse complète de l'URL
 */
class Request
{
    // composants d'une URL
    /**
     * @var string
     */
    private $scheme = '';
    /**
     * @var string
     */
    private $user = '';
    /**
     * @var string
     */
    private $pwd = '';
    /**
     * @var string
     */
    private $host = '';
    /**
     * @var string
     */
    private $port = '';
    /**
     * @var array
     */
    private $path = [];
    /**
     * @var array
     */
    private $query = [];
    /**
     * @var string
     */
    private $fragment = '';
    /**
     * @var bool
     */
    private $is_ajax = false;
    /**
     * @var bool
     */
    private $is_valid = false;

    /**
     * @param string $url
     */
    public function __construct(string $url)
    {
        $this->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 - 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
<?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(), '<br>';    // https
echo $request->host(), '<br>';      // www.developpez.net
echo $request->path()[0], '<br>';   // forums
echo $request->path()[1], '<br>';   // blogs
echo $request->path()[2], '<br>';   // 32058-rawsrc
echo $request->path()[3], '<br>';   // b7804
echo $request->path()[4], '<br>';   // 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 - 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
<?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 - Visualiser dans une fenêtre à part
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 :

Nom : 2019-07-30_003724.jpg
Affichages : 3179
Taille : 31,9 Ko

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 - 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
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
<?php

declare(strict_types=1);

namespace rawsrc;

use rawsrc\{ Controller, Request, 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/
 *
 * Représentation d'une tâche serveur générique
 *
 * Est considéré comme tâche serveur, une requête envoyée au serveur à laquelle une réponse devra être fournie
 *      (réponse au sens générique : succès ou erreur), à ce stade on en sait encore rien
 *
 * Le traitement correspond à :
 *  - l'analyse de la requête (étape du PARSAGE de l'URL => 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 :

Nom : 2019-10-14_185950.jpg
Affichages : 138
Taille : 191,6 Ko

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 :

Nom : 2019-07-30_010215.jpg
Affichages : 3203
Taille : 32,2 Ko

Ce qui nous donne :
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
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
<?php

declare(strict_types=1);

namespace rawsrc;

use rawsrc\{ Controller, Request, Response };
use BadFunctionCallException;

/**
 * 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'une action générique
 *
 * Une action va correspondre à un traitement sur le serveur web
 * Elle fournira tout ce qui est nécessaire à l'application pour être pilotée de manière la plus autonome possible
 * ainsi on pourra empiler des actions sans avoir pour autant à toucher au code de traitement d'une requête et d'une réponse
 *
 * Comme il été vu dans le tuto :
 * À CHAQUE ACTION DISPONIBLE DANS LE SITE WEB DEVRA CORRESPONDRE UNE SEULE ET UNIQUE URL/ROUTE/CONTRÔLEUR
 * QUI SERA DE FAIT L'UNIQUE POINT D'ENTRÉE DU SITE POUR CE TRAITEMENT
 */
class Action
{
    /**
     * Si l'action correspond à celle demandée par la requête alors une instance
     * de la tâche serveur en cours sera dynamiquement injectée au cas où l'action
     * aurait besoin de connaître son environnement d'exécution pour s'exécuter
     *
     * @var Task
     */
    private $task = null;
    /**
     * @var Route
     */
    private $route = null;
    /**
     * @var Controller
     */
    private $controller = null;
    /**
     * @var Response
     */
    private $response = null;

    /**
     * @param array $p [route, controller, response]
     */
    public function __construct(array $p)
    {
        $this->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 - 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
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

Les vues reposent sur un petit moteur de rendu de ma conception : PhpEcho. Ce moteur reposant sur une seule et unique classe est disponible et détaillé sur un de mes autres articles de blog DVP.

Par exemple le formulaire de connexion d'un utilisateur est codé ainsi :
Code html : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
<p> Blog de <strong>rawsrc</strong> sur DVP </p>
<p>Veuillez vous identifier</p>
<form method=post action="<?= $this['url_submit'] ?>">
    <label>Identifiant</label>
    <input type="text" name="login" value="<?= $this('login') ?>"><br>
    <label>Mot de passe</label>
    <input type="password" name="pwd" value=""><br>
    <input type="submit" name="submit" value="SE CONNECTER">
</form>
<br>
<p style="display:<?= $this['error'] ? 'block' : 'none' ?>"><strong><?= $this('error') ?></strong></p>

Le contrôleur s'occupe de paramétrer l'affichage du formulaire de connexion comme ceci :
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
<?php
 
declare(strict_types=1);
 
namespace src\Utilisateur\Controller;
 
use rawsrc\{ Controller, Response };
use rawsrc\PhpEcho\PhpEcho;
 
class Login
extends Controller
{
    public function invoke()
    {
        // corps de la vue
        $body = new PhpEcho([__DIR__, '.. View Login.php'], ['url_submit' => '/utilisateur/connect']);
 
        // layout de la page principale
        $page = new PhpEcho([DIR_ROOT, 'src View Layout.php'], ['body' => $body]);
 
        $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 - Visualiser dans une fenêtre à part
1
2
3
4
5
use rawsrc\Task;
 
$task = new Task();
$task->dispatch();
$task->response()->send();

Nom : 2019-08-05_231243.jpg
Affichages : 3148
Taille : 515,1 Ko


NOTION DE CONCEPT

Nom : 2019-10-14_190800.jpg
Affichages : 136
Taille : 220,6 Ko


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 - Visualiser dans une fenêtre à part
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 - Visualiser dans une fenêtre à part
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 - Visualiser dans une fenêtre à part
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 (fichiers mis à jour avec PhpEcho)
mvc.zip


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.

EDIT : Reprise complète des vues en utilisant PhpEcho, disponible à cette adresse.



rawsrc

Envoyer le billet « PHP 7+ // Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Cas pratique » dans le blog Viadeo Envoyer le billet « PHP 7+ // Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Cas pratique » dans le blog Twitter Envoyer le billet « PHP 7+ // Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Cas pratique » dans le blog Google Envoyer le billet « PHP 7+ // Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Cas pratique » dans le blog Facebook Envoyer le billet « PHP 7+ // Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Cas pratique » dans le blog Digg Envoyer le billet « PHP 7+ // Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Cas pratique » dans le blog Delicious Envoyer le billet « PHP 7+ // Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Cas pratique » dans le blog MySpace Envoyer le billet « PHP 7+ // Développement selon l'approche MVC : Modèle - Vue - Contrôleur - Cas pratique » dans le blog Yahoo

Mis à jour 15/10/2019 à 09h06 par rawsrc

Catégories
PHP , Développement Web

Commentaires

Page 1 sur 2 12 DernièreDernière
  1. Avatar de laurentSc
    • |
    • permalink
    Bonsoir,

    ton cas pratique s'adresse pas aux débutants...Je m'attendais à ce qu'il réponde à mes interrogations mais c'est à 100 lieux de ce que j'imaginais...et bien plus compliqué que ce que je pensais. Je vais encore passer du temps dessus, mais gros risque que je ne m'en sorte pas....

    Sinon, il faudrait préciser qu'il faut PHP7 car j'étais en 5.6 et ça marchait pas. Mais en PHP7, ça va mieux, mais ton appli marche pas encore : y a des fatal errors...
  2. Avatar de rawsrc
    • |
    • permalink
    Salut laurentSC,

    Merci, j'ai précisé la version du PHP dans le titre.
    Effectivement, ce n'est pas simple à aborder car il y a deux problématiques simultanées : le paradigme de la programmation orientée objet et le concept MVC à proprement parler.
    Forcément, il faut se lever de bonne heure pour ingurgiter le tout.

    J'ai réduit mon cas pratique au strict minimum, juste une page d'accueil et un formulaire de connexion, aucune fioriture pour bien distinguer les étapes et éléments de programmation.

    Apprendre à maîtriser ces 2 concepts est fort utile. Le mieux que je puisse te conseiller c'est de refaire des croquis pour voir et suivre le déroulé du code, histoire de voir comment tout s'emboîte et fonctionne de concert.

    Je vais essayer de poster un schéma récapitulatif
  3. Avatar de laurentSc
    • |
    • permalink
    Merci pour ta réponse rawsrc. Je vais continuer à essayer.

    Par contre, si j'essaye d'exécuter ton code, j'obtiens une bizarrerie : d'abord, il a râlé car dans une fonction il attendait un array et il recevait une string ; j'ai corrigé (vendor/rawsrc/Task.php ligne 139) :
    $this->response = new Response('Unable to manage the request', ['400 Bad Request']); à la place de

    $this->response = new Response('Unable to manage the request', '400 Bad Request'); mais au lieu de m'afficher le formulaire de login (probablement), il m'affiche
    Unable to manage the request
    Y a un problème, non ?
  4. Avatar de rawsrc
    • |
    • permalink
    Ouaip, je n'ai pas pris la bonne version, j'ai empaqueté une version avec ce bug de tableau qui traînait.
    Ça m'apprendra à le faire à plus d'une heure du mat. Je vais republier les sources.

    Pour ce qui est de ton problème, si tu atterris sur cette réponse c'est que le dispatch n'a pas trouvé une action correspondante à la requête envoyée au serveur.
    Il faut que tu regardes tes concepts et actions déclarées.
  5. Avatar de laurentSc
    • |
    • permalink
    Citation Envoyé par rawsrc
    Il faut que tu regardes tes concepts et actions déclarées.
    J'ai rien déclaré ; j'ai juste essayé d'exécuter ton code. Où ça se passe, ces déclarations ?
  6. Avatar de laurentSc
    • |
    • permalink
    autre souci ce matin : j'ai 3 fois l'erreur suivante :
    Undefined offset: 1 in C:\wamp64\www\mvc_rawsrc\src\Utilisateur\Utilisateur.php on line 34
    Cette ligne est 'route' => function(Request $p) { return $p->path()[1] === 'login'; },. Qu'en penses-tu ?
  7. Avatar de Dendrite
    • |
    • permalink
    [cancre]
    Laurent, t'es un warrior, moi je dis... J'ai fait défiler l'ascenseur, et j'ai vite vu que mes sites n'avaient pas besoin de tant de perfection et technicité... et heureusement, parce que sinon je ne pourrais pas faire ce métier...
    Je n'envoie pas des fusées sur la lune...
    En attendant, un grand bravo à toi Raw, pour le boulot... sincèrement.
    [/cancre]
  8. Avatar de laurentSc
    • |
    • permalink
    Ouaip...Ca fait des années que la POO m'attire ; le concept est intéressant et celui du MVC aussi. Le concept est facile à comprendre, mais l'implémentation de rawsrc est un peu moins accessible. J'espère encore réussir à m'en servir un jour...
  9. Avatar de rawsrc
    • |
    • permalink
    Salut les jeunes,

    le plus important c'est de bien comprendre et de bien se représenter l'idée derrière cette phrase :
    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).
    Le concept de la POO est partie d'une observation fine de notre environnement. C'est une des raison pour lesquelles l'informatique a connu un tel essor : il a été rendu possible de coller (informatiquement parlant) à la réalité assez précisément.

    Après, je dois reconnaître que ce n'est pas effectivement à la portée de tout le monde de se cogner l'apprentissage de tant de concepts seul avec un livre aussi complet soit-il.
    Bref, il faut s'accrocher car le jeu en vaut la chandelle.
  10. Avatar de laurentSc
    • |
    • permalink
    Citation Envoyé par rawsrc
    Salut les jeunes
    Autrement dit, je n'ai pas à tenir compte de ton commentaire
  11. Avatar de rawsrc
    • |
    • permalink
    @laurentSc

    rigolo va !
  12. Avatar de Dendrite
    • |
    • permalink
    Salut, c'est la gamine qui prend 54 ans dans 15 jours...
    Je transfère ton blog à mon mari, matheux devant l'éternel et administrateur de db...
    Avec lui, on va étudier ta page à la loupe, p'têt ben qu'à deux cerveaux, on arrivera à en tirer bénéfice.
    Je l'ai vendu à mon mari avec l'argument "Rawsrc, c'est une tronche qui développe pour le coeur de PHP"...
    Mon service est censé passer à Symfony, comment ça me gave... mais chuuuuuuuuuut, j'ai pas le droit de le dire.
  13. Avatar de Dendrite
    • |
    • permalink
    Rawsrc, j'aimerais ton opinion sur ma dernière évolution.
    Depuis longtemps, j'aime beaucoup les tutos d'emacs, et j'ai découvert cet été celui-ci :

    https://apprendre-php.com/tutoriels/...grant-pdo.html

    Du coup, j'ai revu ma librairie perso. Notamment dans les cas où je me connecte toujours à la même et seule db (ce qui n'est pas majoritaire d'ailleurs)... J'ai réécrit une classe SPDO selon ces conseils. Qu'en dis-tu ? bonne idée ? fausse bonne idée ?

    Ce qui me ravit à l'arrivée, c'est la brièveté du code des pages qui convoquent la db :

    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
    <?php
    include('../inc/autoloader.php');
    $last_id=SPDO::insert_into_table('insert ignore into strategie values (NULL, ?, ?, NULL)', array('titi','tata'));
    echo $last_id;
    $strategies=SPDO::return_array('select * from strategie order by id desc');
    echo '<pre>';
    print_r($strategies);
    echo '</pre>';
     
    $done=SPDO::write_to_db('update strategie set nom=? where nom=?', array('comique','titi'));
    echo $done;
    $strategies=SPDO::return_array('select * from strategie order by id desc');
    echo '<pre>';
    print_r($strategies);
    echo '</pre>';
    Oui, j'ai conscience d'être un peu hors sujet... mais bon... j'aime bien les hors sujets en fait.
  14. Avatar de rawsrc
    • |
    • permalink
    @Dendrite
    Désolé, mais j'ai beau être abonné à mon blog, les messages postés dans la suite des articles ne sortent pas dans mon tableau de bord. Du coup, je mets un certain temps à répondre car je ne passe pas tous les jours sur mon blog.
    Si tu veux un avis éclairé, il faudrait que tu postes le code source de ta classe d'accès à la base de données. D'ailleurs, vu que tu dis que c'est un singleton, je suis étonné de voir des appels de fonction au format statique.
  15. Avatar de Dendrite
    • |
    • permalink
    Pardon... c'est moi qui mets la grouille.
    Mais je voulais rester discrète en fait, un peu exprès.
    Je te mets ça ce soir.
  16. Avatar de Dendrite
    • |
    • permalink
    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
    54
    55
    56
    <?php
    class SPDO {
      private $PDO_instance = null;
      private $PDO_statement = null;//pour les requêtes préparées
      private static $instance = null;
      private function __construct() { 
        $this->PDO_instance = new PDO('mysql:host=localhost;port=3306;dbname=ma_base;charset=utf8','root', '', [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES   => false
        ]);  
      }  
      protected static function get_instance(){  
        if(is_null(self::$instance)){
          self::$instance = new SPDO();
        }
        return self::$instance;
      }
      //puis copie des méthodes PDO les plus fréquentes
      protected function prepare($sql){
        $this->PDO_statement = $this->PDO_instance->prepare($sql);
      }
      protected function execute(array $filters=NULL){
        $this->PDO_statement->execute($filters);
      }
      protected function fetch(){
        return $this->PDO_statement->fetch(PDO::FETCH_ASSOC);
      }
      protected function rowCount(){
        return $this->PDO_statement->rowCount();
      }
      protected function lastInsertId(){
        return $this->PDO_instance->lastInsertId();
      }
      //requête en lecture
      public static function return_array($sql, $filters=array()){
        self::get_instance()->prepare($sql);
        self::get_instance()->execute($filters);
        $return_array=array();
        while($row= self::get_instance()->fetch()){
          $return_array[]=$row;
        }
        return $return_array;
      }
      //requête en insertion, retourne l'id d'insertion
      public static function insert_into_table($sql, $filters=array()){
        self::get_instance()->prepare($sql);
        self::get_instance()->execute($filters);
        return  self::get_instance()->lastInsertId();
      }
      //n'importe quel code SQL, retourne fait ou pas
      public static function write_to_db($sql, $filters=array()){
        self::get_instance()->prepare($sql);
        self::get_instance()->execute($filters);
        return  self::get_instance()->rowCount();
      }
    }
  17. Avatar de rawsrc
    • |
    • permalink
    Salut Dendrite,

    ton approche n'est pas bonne, ta classe n'utilise que 2% de ce qu'offre la classe PDO. Tu limites à mort les fonctionnalités et tu ne te sers que pour ouvrir une connexion. C'est léger !
    Si tu veux profiter pleinement de PDO partout où tu en as besoin sans avoir à trimbaler les paramètres de connexion :
    Code php : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Cnx
    extends \PDO
    {
        private static $pdo;
     
        public function __construct()
        {
            if (self::$pdo === null) {
                self::$pdo = new \PDO('mysql:host=localhost;port=3306;dbname=nom_base_de_donnees;charset=utf8', 'root', 'mot_de_passe');
            }
            return self::$pdo;
        }
    }
    ensuite si tu as plein de connexions (un pool) tu peux très bien te servir d'une classe qui stocke, distribue et ouvre les connexions à la demande, pour cela il suffit que chaque connexion ait un identifiant unique.

    Voici une classe que j'utilise (je ne t'ai pas mis tout les dépendances mais tout le concept est là) :
    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
    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
    192
    193
    194
    <?php
    
    declare(strict_types=1);
    
    namespace Database;
    
    use Config\Configurator;
    use Error\EECollector;
    
    /**
     * Classe regroupant les services relatifs et spécifiques à une ou plusieurs connexions à des base de données
     */
    class Service
    {
        /**
         * Tableau des services de base de données
         * La clé pdo n'existe que si une connexion a été établie avec succès
         *
         * @var array [id => [pdo, config_group, params => [scheme, host, database, user, pwd, port, attemps, timeout]]]
         */
        private static $pool = [];
    
        /**
         * Ajoute un service de base de données identifié par un id unique
         *
         * @param string    $id             Identifiant unique du service
         * @param \PDO      $pdo
         */
        public static function addPdo(string $id, \PDO $pdo)
        {
            self::$pool[$id] = ['pdo' => $pdo];
        }
    
        /**
         * Ajoute un service de base de données dont les paramètres sont définis
         * dans le fichier principal de configuration de l'application
         *
         * @param string    $id                 Identifiant unique du service
         * @param string    $group              Identifiant du groupe de paramètres dans le fichier de configuration => ce qui est entre crochets dans le .ini
         * @param array     $others             [key => value] Paramètres supplémentaires à passer dans la chaine de connexion à la base de données
         */
        public static function addFromConfigFile(string $id, string $group, array $others = [])
        {
            self::$pool[$id] = ['config_group' => $group, 'others' => $others];
        }
    
        /**
         * Ajoute un service de base de données dont les paramètres sont définis manuellement
         *
         * @param string $id             Identifiant unique du service
         * @param string $scheme
         * @param string $host
         * @param string $database
         * @param string $user
         * @param string $pwd
         * @param string $port
         * @param string $attempts
         * @param string $timeout
         * @param array  $others         à passer dans la chaine de connexion à la base de données
         */
        public static function addManualParams(
            string $id, string $scheme, string $host, string $database, string $user, string $pwd, string $port,
            string $attempts = '3', string $timeout = '5', array $others = [])
        {
            self::$pool[$id] =  [
                'others'    => $others,
                'params'    => [
                    'scheme'    => $scheme,
                    'host'      => $host,
                    'database'  => $database,
                    'user'      => $user,
                    'pwd'       => $pwd,
                    'port'      => $port,
                    'attempts'  => $attempts,
                    'timeout'   => $timeout
                ]
            ];
        }
    
        /**
         * Renvoie une connexion PDO
         * Si connexion inexistante, elle sera établie automatiquement en fonction du paramétrage
         *
         * @param  string           $id Identifiant unique du service
         * @return \PDO |EECollector
         */
        public static function pdo(string $id)
        {
            if (isset(self::$pool[$id])) {
                return self::$pool[$id]['pdo'] ?? self::open($id);
            } else {
                return new EECollector();
            }
        }
    
        /**
         * Ouvre une connexion vers une base de données
         *
         * @param string $id Identifiant unique du service
         * @return \PDO |EECollector
         */
        private static function open(string $id)
        {
            $service       = self::$pool[$id];
            $driver_params = $service['others'] ?? [];
    
            if (isset($service['config_group'])) {
                // extraction des paramètres du fichier de configuration
                $params = Configurator::group($service['config_group']);
    
                if ($params instanceof EECollector) {
                    return $params;
                }
    
                // driver_params provenant du fichier de configuration : clés différentes des clés communes
                $common_keys    = array_flip(['scheme', 'host', 'database', 'user', 'pwd', 'port', 'timeout', 'attempts']);
                $driver_params += array_diff_key($params, $common_keys);
    
                return self::connect(
                    $id,
                    $params['scheme'],
                    $params['host'],
                    $params['database'],
                    $params['user'],
                    $params['pwd'],
                    $params['port'],
                    $params['timeout'],
                    $params['attempts'],
                    $driver_params
                );
            } elseif (isset($service['params'])) {
                // paramètres manuels
                return self::connect(
                    $id,
                    $service['params']['scheme'],
                    $service['params']['host'],
                    $service['params']['database'],
                    $service['params']['user'],
                    $service['params']['pwd'],
                    $service['params']['port'],
                    $service['params']['timeout'],
                    $service['params']['attempts'],
                    $driver_params
                );
            }
        }
    
        /**
         * @param  string $id             Identifiant unique du service
         * @param  string scheme          Ex: mysql pgsql...
         * @param  string $host           Adresse IP du serveur
         * @param  string $database       Nom de la base de données
         * @param  string $user           Nom de l'utilisateur
         * @param  string $pwd            Mot de passe de la connexion
         * @param  string $port           Numéro du port pour la connexion
         * @param  string $timeout        Délai d'attente réponse serveur
         * @param  string $attempts       Nombre d'essais de reconnexion
         * @param  array  $driver_params  Array(key => value) Paramètres additionnels à passer à l'ouverture de la connexion
         * @return \PDO |EECollector
         */
        private static function connect(string $id, string $scheme, string $host, string $database, string $user,
                                        string $pwd, string $port, string $timeout, string $attempts, array $driver_params)
        {
            $dsn = "{$scheme}:host={$host};dbname={$database};";
    
            if ((int)($port)) {
                $dsn .= "port={$port};";
            }
    
            $dsn .= "connect_timeout={$timeout};";
    
            if ( ! empty($driver_params)) {
                $dsn .= implode(';', $driver_params).';';
            }
    
            $eec = new EECollector();
    
            // essais d'ouverture de connexion
            for ($i = 0; $i < $attempts; ++$i) {
                try {
                    $dbh = new \PDO($dsn, $user, $pwd, [
                        \PDO::ATTR_ERRMODE            => \PDO::ERRMODE_EXCEPTION,
                        \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
                        \PDO::ATTR_EMULATE_PREPARES   => false
                    ]);
                    self::$pool[$id]['pdo'] = $dbh;
                    return $dbh;
                } catch (\PDOException $e) {
                    $eec->addException($e);
                }
            }
            return $eec;
        }
    }
  18. Avatar de Dendrite
    • |
    • permalink
    Youpi, merciiiiiiiiiiiii !
    Je travaille tout ça au boulot, et hop, je reviens vers toi pour te dire si ça tourne comme j'aime.
    Fin du hors sujet !
  19. Avatar de jibus19
    • |
    • permalink
    Bonjour,

    Tout d'abord merci pour le partage de ce travail, étant en formation de dév web, cette approche encore jamais rencontrée me permet de pousser encore plus loin mon apprentissage.

    Par contre, je rencontre des petits problèmes en consultant votre article : certains morceaux de codes sont coupés (manque le début parfois) et concernant la mise à disposition des fichiers, le lien apparaît comme ceci :

    Et enfin, le ficher ZIP qui contient tous les fichiers du projet (fichiers mis à jour avec PhpEcho)
    [ATTACH]509914d1/a/a/a" />

    Ce n'est pas grand chose mais cela m'empêche de pleinement étudier votre travail.

    Merci

    JB
  20. Avatar de rawsrc
    • |
    • permalink
    Salut jibus19

    Je pense que tu devrais vider le cache de ton navigateur. J'ai essayé de mon côté en étant connecté et même déconnecté, je n'ai pas les problèmes que tu décris.
    Deux navigateurs : Chrome et Firefox, aucun souci. Tout fonctionne parfaitement.
Page 1 sur 2 12 DernièreDernière