Amélioration du TP blog en respectant l'architecture MVC



Je vais maintenant vous montrer comment on pourrait découper le code précédent selon une architecture MVC.
Je vais vous proposer une façon de faire, mais n'allez pas croire que c'est la seule méthode qui existe. On peut respecter l'architecture MVC de différentes manières ; tout dépend de la façon dont on l'interprète. D'ailleurs, la théorie « pure » de MVC est bien souvent inapplicable en pratique. Il faut faire des concessions, savoir être pragmatique : on prend les bonnes idées et on met de côté celles qui se révèlent trop contraignantes.


À la racine de votre site, je vous propose de créer trois répertoires :

  • modele ;
  • vue ;
  • controleur.


Dans chacun d'eux, vous pouvez créer un sous-répertoire pour chaque « module » de votre site : forums,blogminichat, etc. Pour le moment, créez un répertoire blog dans chacun de ces dossiers. On aura ainsi l'architecture suivante :

  • modele/blog : contient les fichiers gérant l'accès à la base de données du blog ;
  • vue/blog : contient les fichiers gérant l'affichage du blog ;
  • controleur/blog : contient les fichiers contrôlant le fonctionnement global du blog.


On peut commencer par travailler sur l'élément que l'on veut ; il n'y a pas d'ordre particulier à suivre (comme je vous le disais, on peut travailler à trois en parallèle sur chacun de ces éléments). Je vous propose de commencer par le modèle, puis de voir le contrôleur et enfin la vue.


Le modèle

Créez un fichier get_billets.php dans modele/blog. Ce fichier contiendra une fonction dont le rôle sera de retourner un certain nombre de billets depuis la base de données. C'est tout ce qu'elle fera.

<?php

function get_billets($offset, $limit)

{

    global $bdd;

    $offset = (int) $offset;

    $limit = (int) $limit;

         

    $req = $bdd->prepare('SELECT id, titre, contenu, DATE_FORMAT(date_creation, \'%d/%m/%Y à %Hh%imin%ss\') AS date_creation_fr FROM billets ORDER BY date_creation DESC LIMIT :offset, :limit');

    $req->bindParam(':offset', $offset, PDO::PARAM_INT);

    $req->bindParam(':limit', $limit, PDO::PARAM_INT);

    $req->execute();

    $billets = $req->fetchAll();

     

     

    return $billets;

}


Ce code source ne contient pas de réelles nouveautés. Il s'agit d'une fonction qui prend en paramètre un offset et une limite. Elle retourne le nombre de billets demandés à partir du billet no offset. Ces paramètres sont transmis à MySQL via une requête préparée.


Je n'ai pas utilisé la méthode traditionnelle à laquelle vous avez été habitués pour transmettre les paramètres à la requête SQL. En effet, j'ai utilisé ici la fonction bindParam qui me permet de préciser que le paramètre est un entier (PDO$\colon\colon$PARAM_INT). Cette méthode alternative est obligatoire dans le cas où les paramètres sont situés dans la clause LIMIT car il faut préciser qu'il s'agit d'entiers.


La connexion à la base de données aura été faite précédemment. On récupère l'objet $bdd global représentant la connexion à la base et on l'utilise pour effectuer notre requête SQL. Cela nous évite d'avoir à recréer une connexion à la base de données dans chaque fonction, ce qui serait très mauvais pour les performances du site (et très laid dans le code !).


Il existe une meilleure méthode pour récupérer l'objet $bdd et qui est basée sur le design pattern singleton. Elle consiste à créer une classe qui retourne toujours le même objet. Nous ne détaillerons pas cette méthode ici, sensiblement plus complexe, mais je vous invite à vous renseigner sur le sujet.


Plutôt que de faire une boucle de fetch(), j'appelle ici la fonction fetchAll() qui assemble toutes les données dans un grand array. La fonction retourne donc un array contenant les billets demandés.

Vous noterez que ce fichier PHP ne contient pas la balise de fermeture ?>. Celle-ci n'est en effet pas obligatoire, comme je vous l'ai dit plus tôt dans le cours. Je vous recommande de ne pas l'écrire surtout dans le modèle et le contrôleur d'une architecture MVC. Cela permet d'éviter de fâcheux problèmes liés à l'envoi de HTML avant l'utilisation de fonctions comme setCookie qui nécessitent d'être appelées avant tout code HTML.


Le contrôleur


Le contrôleur est le chef d'orchestre de notre application. Il demande au modèle les données, les traite et appelle la vue qui utilisera ces données pour afficher la page.

Dans controleur/blog, créez un fichier index.php qui représentera la page d'accueil du blog.

<?php

 

// On demande les 5 derniers billets (modèle)

include_once('modele/blog/get_billets.php');

$billets = get_billets(0, 5);

 

// On effectue du traitement sur les données (contrôleur)

// Ici, on doit surtout sécuriser l'affichage

foreach($billets as $cle => $billet)

{

    $billets[$cle]['titre'] = htmlspecialchars($billet['titre']);

    $billets[$cle]['contenu'] = nl2br(htmlspecialchars($billet['contenu']));

}

 

// On affiche la page (vue)

include_once('vue/blog/index.php');


On retrouve le cheminement simple que je vous avais décrit. Le rôle de la page d'accueil du blog est d'afficher les cinq derniers billets. On appelle donc la fonction get_billets() du modèle, on récupère la liste « brute » de ces billets que l'on traite dans un foreach pour protéger l'affichage avec htmlspecialchars() et créer les retours à la ligne du contenu avec nl2br().


S'il y avait d'autres opérations à faire avant l'appel de la vue, comme la gestion des droits d'accès, ce serait le bon moment. En l'occurrence, il n'est pas nécessaire d'effectuer d'autres opérations dans le cas présent.

Notez que la présence des fonctions de sécurisation des données dans le contrôleur est discutable. On pourrait laisser cette tâche à la vue, qui ferait donc les htmlspecialchars, ou bien à une couche intermédiaire entre le contrôleur et la vue (c'est d'ailleurs ce que proposent certains frameworks dont on parlera plus loin). Comme vous le voyez, il n'y a pas une seule bonne approche mais plusieurs, chacune ayant ses avantages et inconvénients.


Vous noterez qu'on opère sur les clés du tableau plutôt que sur $billet (sans s) directement. En effet, $billet est une copie du tableau $billets créée par le foreach$billet n'existe qu'à l'intérieur du foreach, il est ensuite supprimé. Pour éviter les failles XSS, il faut agir sur le tableau utilisé à l'affichage, c'est-à-dire $billets.


Ce qui est intéressant, c'est que la fonction get_billets() pourra être réutilisée à d'autres occasions. Ici, on l'appelle toujours avec les mêmes paramètres, mais on pourrait en avoir besoin dans d'autres contrôleurs. On pourrait aussi améliorer ce contrôleur-ci pour gérer la pagination des billets du blog et afficher un nombre différent de billets par page en fonction des préférences du visiteur.


La vue


Il ne nous reste plus qu'à créer la vue correspondant à la page d'accueil des derniers billets du blog. Ce sera très simple : le fichier de la vue devra simplement afficher le contenu de l'array $billets sans se soucier de la sécurité ou des requêtes SQL. Tout aura été préparé avant.


Vous pouvez créer un fichier index.php dans vue/blog et y insérer le code suivant :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">

    <head>

        <title>Mon blog</title>

        <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />

    <link href="vue/blog/style.css" rel="stylesheet" type="text/css" /> 

    </head>

         

    <body>

        <h1>Mon super blog !</h1>

        <p>Derniers billets du blog :</p>

  

<?php

foreach($billets as $billet)

{

?>

<div class="news">

    <h3>

        <?php echo $billet['titre']; ?>

        <em>le <?php echo $billet['date_creation_fr']; ?></em>

    </h3>

     

    <p>

    <?php echo $billet['contenu']; ?>

    <br />

    <em><a href="commentaires.php?billet=<?php echo $billet['id']; ?>">Commentaires</a></em>

    </p>

</div>

<?php

}

?>

</body>

</html>


Ce code source se passe réellement de commentaires. Il contient essentiellement du HTML et quelques morceaux de PHP pour afficher le contenu des variables et effectuer la boucle nécessaire.


L'intérêt est que ce fichier est débarrassé de toute « logique » du code : vous pouvez aisément le donner à un spécialiste de la mise en page web pour qu'il améliore la présentation. Celui-ci n'aura pas besoin de connaître le PHP pour travailler sur la mise en page du blog. Il suffit de lui expliquer le principe de la boucle et comment on affiche une variable, c'est vraiment peu de choses.


Le contrôleur global du blog


Bien qu'en théorie ce ne soit pas obligatoire, je vous recommande de créer un fichier contrôleur « global » par module à la racine de votre site. Son rôle sera essentiellement de traiter les paramètres $_GET et d'appeler le contrôleur correspondant en fonction de la page demandée. On peut aussi profiter de ce point « central » pour créer la connexion à la base de données.


J'ai ici choisi d'inclure un fichier connexion_sql.php qui crée l'objet $bdd. Ce fichier pourra ainsi être partagé entre les différents modules de mon site.

Vous pouvez donc créer ce fichier blog.php à la racine de votre site :

<?php

 

include_once('modele/connexion_sql.php');

 

if (!isset($_GET['section']) OR $_GET['section'] == 'index')

{

    include_once('controleur/blog/index.php');

}

Pour accéder à la page d'accueil du blog, il suffit maintenant d'ouvrir la page blog.php !


L'avantage de cette page est aussi qu'elle permet de masquer l'architecture de votre site au visiteur. Sans elle, ce dernier aurait dû aller sur la page controleur/blog/index.php pour afficher votre blog. Cela aurait pu marcher, mais aurait probablement créé de la confusion chez vos visiteurs, en plus de complexifier inutilement l'URL.


Résumé des fichiers créés


Je pense qu'un schéma synthétisant l'architecture des fichiers que nous venons de créer ne sera pas de refus ! La figure suivante devrait vous permettre de mieux vous repérer.



L'architecture des fichiers créés pour adapter notre blog en MVC



Cela fait beaucoup de fichiers alors qu'auparavant tout tenait dans un seul fichier ! La construction de son site selon une architecture MVC est à ce prix. Cela multiplie les fichiers, mais clarifie dans le même temps leur rôle. Si vous les regroupez intelligemment dans des dossiers, il n'y aura pas de problème. ;-)


Désormais, si vous avez un problème de requête SQL, vous savez précisément quel fichier ouvrir : le modèle correspondant. Si c'est un problème d'affichage, vous ouvrirez la vue. Tout cela rend votre site plus facile à maintenir et à faire évoluer !


Je vous invite à télécharger les fichiers d'exemple de ce chapitre pour que vous puissiez vous familiariser avec l'architecture MVC.

Profitez-en pour améliorer le code ! Je vous proposais un peu plus tôt de gérer par exemple la pagination du blog sur la page d'accueil. Vous pouvez aussi porter le reste des pages du blog selon cette même architecture : ce sera le meilleur exercice pour vous former que vous trouverez !

Créé avec HelpNDoc Personal Edition: Transformez votre processus de documentation d'aide avec un outil de création d'aide