Pensez MVC avec PHP

P

Vous avez dit MVC ? C’est quoi ce truc ? Ca se mange ?

Oh là tout doux n’allons pas en besogne. J’essaie de vous expliquer dans cet billet ce que s’est pas à pas. Commençons par définir le concept.

MVC (Model-View-Controller ou Modèle-Vue-Contrôleur) est un modèle dans la conception de logiciels. Il met l’accent sur la séparation entre la logique métier et l’affichage du logiciel. Cette «séparation des préoccupations» permet une meilleure répartition du travail et une maintenance améliorée.

Allez plus loin dans la définition avec Wikipédia.

Le stric minimum

C’est parti, concrètement qu’est-ce que s’est ? J’essaie de vous expliquer le plus simplement possible 😎 avec création d’un blog en PHP.

Considérons que nous avons dans une base de données MySQL blog une table post avec les champs idtitlecontent.

Pour commencer, créons une page unique index.php qui affiche les billets du blog qui ont été enregistré dans la base de données. L’écriture en PHP classique est rapide et sale :

<?php
// index.php
$connection = new PDO("mysql:host=localhost;dbname=blog_db", 'myuser', 'mypassword');

$result = $connection->query('SELECT id, title FROM post');
?>

<!DOCTYPE html>
<html>
    <head>
        <title>List of Posts</title>
    </head>
    <body>
        <h1>List of Posts</h1>
        <ul>
            <?php while ($row = $result->fetch(PDO::FETCH_ASSOC)): ?>
            <li>
                <a href="/show.php?id=<?= $row['id'] ?>">
                    <?= $row['title'] ?>
                </a>
            </li>
            <?php endwhile ?>
        </ul>
    </body>
</html>

<?php
$connection = null;
?>
index.php

Ce code est rapide à écrire en plus d’être fonctionnel, facile à déployer et à exécuter mais, on se rend vite à l’évidence. A mesure que le code se développe, il devient difficilement “maintenable” voire impossible à maintenir.

Ci-après les quelques limites que comporte se le code ci-dessus :

  • Pas de controleur d’erreur : Pas de contrôle d’erreur : Que se passe-t-il si la connexion à la base de données échoue ?
  • Mauvaise organisation : Si l’application se développe, ce fichier unique deviendra de plus en plus difficile à maintenir. Où placer le code pour gérer la soumission d’un formulaire ? Comment pouvez-vous valider les données ? Où doit aller le code pour l’envoi de courriels ?
  • Difficile de réutiliser le code : Comme tout est dans un seul fichier, il n’y a aucun moyen de réutiliser une partie de l’application pour d’autres “pages” du blog.

Alors, ça vous dit qu’on essaie d’améliorer le code 😆 ?

Ca vous dit un template de présentation pour notre blog ?

On essaie de faire les choses bien. Nous allons séparer la logique de notre code de celui de l’affichage HTML.

// index.php
$connection = new PDO("mysql:host=localhost;dbname=blog_db", 'myuser', 'mypassword');

$result = $connection->query('SELECT id, title FROM post');

$posts = [];
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
    $posts[] = $row;
}

$connection = null;

// include the HTML presentation code
require 'templates/list.php';
index.php - Logique applicative

Le code HTML est désormais stocké dans un fichier distinct, templates/list.php, qui est avant tout un fichier HTML utilisant une syntaxe PHP de type modèle :

<!-- templates/list.php -->
<!DOCTYPE html>
<html>
    <head>
        <title>List of Posts</title>
    </head>
    <body>
        <h1>List of Posts</h1>
        <ul>
            <?php foreach ($posts as $post): ?>
            <li>
                <a href="/show.php?id=<?= $post['id'] ?>">
                    <?= $post['title'] ?>
                </a>
            </li>
            <?php endforeach ?>
        </ul>
    </body>
</html>
templates/list.php

Par convention, le fichier qui contient toute la logique de l’application (ici index.php) est appelé contrôleur. Ce terme est un mot que vous entendrez souvent, quel que soit le langage ou le framework que vous utilisez. Il fait référence à la zone de votre code qui traite les entrées de l’utilisateur et prépare la réponse. C’est la signification C dans l’acronyme MVC.

Dans ce cas, le contrôleur prépare les données à partir de la base de données et inclut ensuite un modèle pour présenter ces données. Avec le contrôleur isolé, vous pouvez modifier uniquement le fichier de modèle si vous avez besoin de présenter les entrées du blog dans un autre format (par exemple, list.json.php pour le format JSON).

Je sais, tu te dis que c’est cool, mais t’inquiète, en vrai c’est vraiment cool 😎

Isolons maintenant la logique de notre blog

Jusqu’à présent, l’application ne contient qu’une seule page. Mais que se passerait-il si d’autres pages devaient utiliser la même connexion à la base de données 😯 ? On serait tenté de refaire le code de sorte que le comportement de base et les fonctions d’accès aux données de l’application soient isolés dans un nouveau fichier appelé model.php :

// model.php
function open_database_connection()
{
    $connection = new PDO("mysql:host=localhost;dbname=blog_db", 'myuser', 'mypassword');

    return $connection;
}

function close_database_connection(&$connection)
{
    $connection = null;
}

function get_all_posts()
{
    $connection = open_database_connection();

    $result = $connection->query('SELECT id, title FROM post');

    $posts = [];
    while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
        $posts[] = $row;
    }
    close_database_connection($connection);

    return $posts;
}
model.php

Le fichier model.php est utilisé parce que la logique et l’accès aux données d’une application sont traditionnellement connus comme la couche “modèle”. Dans une application bien organisée, la majorité du code représentant votre “logique métier” devrait se trouver dans le modèle (par opposition à un contrôleur). Et contrairement à cet exemple, seule une partie (ou aucune) du modèle est réellement concernée par l’accès à une base de données.

Le contrôleur (index.php) ne compte plus que quelques lignes de code. C’est super avoue-le 😆 :

// index.php
require_once 'model.php';

$posts = get_all_posts();

require 'templates/list.php';
index.php

Maintenant, la seule tâche du contrôleur est de récupérer les données de la couche modèle de l’application (le modèle) et d’appeler un modèle pour rendre ces données. Ceci est un exemple très concis du pattern modèle-vue-contrôleur — MVC.

right now, Attaquons-nous aux vues !

À ce stade, l’application a été remaniée en trois parties distinctes offrant divers avantages et la possibilité de réutiliser presque tout sur différentes pages.

A présent, la seule partie du code qui ne peut pas être réutilisée est le template de mise en page. Corrigez-la en créant un nouveau fichier templates/layout.php :

<!-- templates/layout.php -->
<!DOCTYPE html>
<html>
    <head>
        <title><?= $title ?></title>
    </head>
    <body>
        <?= $content ?>
    </body>
</html>
templates/layout.php

Le fichier templates/list.php peut maintenant être simplifié pour “étendre” (ou hériter du) le fichier templates/layout.php :

<!-- templates/list.php -->
<?php $title = 'List of Posts' ?>

<?php ob_start() ?>
    <h1>List of Posts</h1>
    <ul>
        <?php foreach ($posts as $post): ?>
        <li>
            <a href="/show.php?id=<?= $post['id'] ?>">
                <?= $post['title'] ?>
            </a>
        </li>
        <?php endforeach ?>
    </ul>
<?php $content = ob_get_clean() ?>

<?php include 'layout.php' ?>
templates/list.php

Vous avez maintenant une configuration qui vous permettra de réutiliser l’affichage. Malheureusement, pour y parvenir, vous êtes obligé d’utiliser quelques fonctions PHP (ob_start(), ob_get_clean()) dans la vue. L’utilisation des frameworks, résout ce problème en utilisant un composant Templating. On en reparlera sûrement dans un autre billet.

Affichons le contenu d’un billet de notre super blog

La page “liste” du blog a été remaniée de manière à ce que le code soit mieux organisé et réutilisable. Pour le prouver, ajoutez une page de blog “show”, qui affiche un article de blog individuel identifié par un paramètre de requête id.

Pour commencer, créez une nouvelle fonction dans le fichier model.php qui récupère un résultat de blog individuel en fonction d’un identifiant donné :

// model.php
function get_post_by_id($id)
{
    $connection = open_database_connection();

    $query = 'SELECT created_at, title, body FROM post WHERE id=:id';
    $statement = $connection->prepare($query);
    $statement->bindValue(':id', $id, PDO::PARAM_INT);
    $statement->execute();

    $row = $statement->fetch(PDO::FETCH_ASSOC);

    close_database_connection($connection);

    return $row;
}
model.php

Ensuite, créez un nouveau fichier appelé show.php – le contrôleur de cette nouvelle page :

// show.php
require_once 'model.php';

$post = get_post_by_id($_GET['id']);

require 'templates/show.php';
show.php

Enfin, créez une nouvelle vue – templates/show.php – pour rendre l’article de blog individuel :

<!-- templates/show.php -->
<?php $title = $post['title'] ?>

<?php ob_start() ?>
    <h1><?= $post['title'] ?></h1>

    <div class="date"><?= $post['created_at'] ?></div>
    <div class="body">
        <?= $post['body'] ?>
    </div>
<?php $content = ob_get_clean() ?>

<?php include 'layout.php' ?>
templates/show.php

La création de la deuxième page ne nécessite plus que très peu de travail et aucun code n’est dupliqué : on parle de la philosophie du DRY — Don’t Repeat Yourself. Pourtant, cette page présente encore plus de problèmes persistants. Par exemple, un paramètre de requête manquant ou invalide provoquera un crash de la page. Ce serait mieux si cela provoquait l’affichage d’une page 404, mais ce n’est pas encore possible.

Un autre problème majeur est que chaque fichier contrôleur individuel doit inclure le fichier model.php. Que se passerait-il si chaque fichier de contrôleur devait soudainement inclure un fichier supplémentaire ou effectuer une autre tâche globale (par exemple, renforcer la sécurité) ? Dans l’état actuel des choses, ce code devrait être ajouté à chaque fichier de contrôleur. Si vous oubliez d’inclure quelque chose dans un fichier, espérons que cela ne concerne pas la sécurité…

Allons plus loin…

From Controller to a « Front Controller » to the rescue 😀

La solution consiste à utiliser un contrôleur frontal (ou Front Controller, c’est plus classe et ça donne un genre dans les réunions tech 😉 ): un seul fichier PHP par lequel toutes les demandes sont traitées. Avec un contrôleur frontal, les URI de l’application changent légèrement, mais commencent à devenir plus flexibles :

Sans le front controller
/index.php          => Blog post list page (index.php executed)
/show.php           => Blog post show page (show.php executed)

Avec index.php comme le front controller
/index.php          => Blog post list page (index.php executed)
/index.php/show     => Blog post show page (index.php executed)

Attention : En utilisant des règles de réécriture dans la configuration de votre serveur web, le fichier index.php ne sera pas nécessaire et vous aurez des URL belles et propres (par exemple, /show).

Lorsqu’on utilise un contrôleur frontal, un seul fichier PHP (index.php dans ce cas) rend chaque requête. Pour la page d’affichage de l’article de blog, /index.php/show exécutera en fait le fichier index.php, qui est maintenant chargé d’acheminer les demandes en interne sur la base de l’URI complet. Comme vous le verrez, un contrôleur frontal est un outil très puissant.

Alors ce fameux Front Controller, on le crée quand ?

Vous êtes sur le point de faire un grand pas avec l’application. Avec un seul fichier gérant toutes les requêtes, vous pouvez centraliser des éléments tels que la gestion de la sécurité, le chargement de la configuration et le routage. Dans cette application, index.php doit maintenant être assez intelligent pour rendre la page de la liste des articles du blog ou la page d’affichage des articles du blog en fonction de l’URI demandé :

// index.php

// load and initialize any global libraries
require_once 'model.php';
require_once 'controllers.php';

// route the request internally
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ('/index.php' === $uri) {
    list_action();
} elseif ('/index.php/show' === $uri && isset($_GET['id'])) {
    show_action($_GET['id']);
} else {
    header('HTTP/1.1 404 Not Found');
    echo '<html><body><h1>Page Not Found</h1></body></html>';
}
index.php

Pour l’organisation, les deux contrôleurs (anciennement /index.php et /index.php/show) sont maintenant des fonctions PHP et chacun a été déplacé dans un fichier séparé nommé controllers.php :

// controllers.php
function list_action()
{
    $posts = get_all_posts();
    require 'templates/list.php';
}

function show_action($id)
{
    $post = get_post_by_id($id);
    require 'templates/show.php';
}
controllers.php

En tant que contrôleur frontal, index.php a pris un rôle entièrement nouveau, qui inclut le chargement des bibliothèques de base et le routage de l’application afin que l’un des deux contrôleurs (les fonctions list_action() et show_action()) soit appelé.

Note : Mais attention à ne pas confondre les termes contrôleur frontal et contrôleur. Votre application n’aura généralement qu’un seul contrôleur frontal, qui lance votre code. Vous aurez de nombreuses fonctions de contrôleur : une pour chaque page.

Un autre avantage d’un contrôleur frontal est la flexibilité des URL. Remarquez que l’URL de la page d’affichage des billets de blog a pu être modifiée de /show à /read en changeant le code à un seul endroit. Auparavant, il fallait renommer un fichier entier.

À présent, l’application est passée d’un simple fichier PHP à une structure organisée et permettant la réutilisation du code. Vous devriez être plus heureux, mais loin d’être satisfait. Par exemple, le système de routage est capricieux, et ne reconnaîtrait pas que la page de liste – /index.php – devrait être accessible également via / (si des règles de réécriture Apache étaient ajoutées). En outre, au lieu de développer le blog, on passe beaucoup de temps à travailler sur l’architecture du code (par exemple, le routage, l’appel aux contrôleurs, les modèles, etc.) Il faudra consacrer plus de temps à la gestion des soumissions de formulaires, à la validation des entrées, à la journalisation et à la sécurité. Pourquoi devriez-vous réinventer des solutions à tous ces problèmes de routine ? Dans d’autres billet, je vous parlerai des Framework.

 

A propos de l'auteur

François KOBON

Trois mots pour me décrire : Code - Build - Hack. Je réponds au nom de François KOBON. Partisan passionné de technologies/innovations libre et ouverte, d'Algorithme, de Datas, des Mathématiques, tous types de sciences... et surtout d'apprentissage non conventionnel parce je suis auto-didacte. Je suis membre de la communauté Ayiyikoh que vous pouvez retrouver en ligne sur ayiyikoh.org

Ajouter un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

François KOBON

Trois mots pour me décrire : Code - Build - Hack. Je réponds au nom de François KOBON. Partisan passionné de technologies/innovations libre et ouverte, d'Algorithme, de Datas, des Mathématiques, tous types de sciences... et surtout d'apprentissage non conventionnel parce je suis auto-didacte. Je suis membre de la communauté Ayiyikoh que vous pouvez retrouver en ligne sur ayiyikoh.org

Suivez-moi

Insert math as
Block
Inline
Additional settings
Formula color
Text color
#333333
Type math using LaTeX
Preview
\({}\)
Nothing to preview
Insert