Skip to content

MVC - activité 1

  • Vous allez récupérer un projet contenant un fichier index.php
  • Ce script se connecte à une base de données SQLite, récupère des articles dans la base de données, les affiche sous format HTML, et offre un formulaire pour ajouter de nouveaux articles.
  • Il affiche aussi des informations sur les variables super globales de PHP $_GET, $_POST, $_SESSION
  • Premièrement, vous devez étudier le fichier index.php pour comprendre son fonctionnement
  • Deuxièmement, vous allez être guidé pour modifier ce projet et tendre vers une architecture MVC (Model-View-Controller) simplifiée. Cette partie est fondamentale pour bien comprendre la suite du cours et utiliser ensuite le framework Laravel.
  • Troisièmement, vous devrez réaliser des exercices pour vous familiariser avec PHP
  1. Suivez ce lien pour récupérer / cloner / forker la base du projet. (Optionel: suivre ce guide pour utiliser github en ssh)

  2. Ajoutez le répertoire du projet dans VSCode ou votre éditeur.

  3. Pour accéder au projet dans un navigateur, ouvrez le dossier du projet dans un terminal, lancez la commande :

    Terminal window
    php -S localhost:2023

    et ouvrez l’url http://localhost:2023/

    Pour les utilisateurs de Windows : Si vous avez des erreurs ou que le serveur ne fonctionne pas, essayez de lancer la commande en pointant directement vers le PHP de XAMPP :

    Terminal window
    C:\XAMPP\php\php.exe -S localhost:2023

    Si cela fonctionne vous devez configurer votre PATH. Voir le guide en annexe

  4. Ouvrez le fichier index.php dans votre éditeur et étudiez le.

    Lisez bien les commentaires dans le code, posez des questions aux professeurs, discutez en entre vous

  5. Testez l’ajout d’article et lien avec des paramètres et essayez de comprendre les variables PHP $_POST, $_GET et $_SESSION

Exercice 2 - Modification de la structure en MVC

Section titled “Exercice 2 - Modification de la structure en MVC”

Le projet dans son organisation actuelle ne sera pas pratique à faire évoluer :

  • Si on veut ajouter de nouvelles pages, blocs de contenus et fonctionnalités, le fichier index.php va devenir illisible et impossible à maintenir
  • Il faut segmenter le fichier index.php en plusieurs fichiers, et les placer dans différents répertoires
  • On veut aussi utiliser la programmation objet
  • Le design pattern MVC propose une solution viable pour réaliser des applications complexes

Exercice 2.1 - Création des dossiers et fichiers

Section titled “Exercice 2.1 - Création des dossiers et fichiers”

Vous allez maintenant créer des dossiers et des fichiers nécessaires à l’implémentation d’une architecture MVC.

  1. Créez les répertoires suivants dans le projet : (voir commande ci-dessous)

    • Directoryapp
      • DirectoryControllers/
      • DirectoryModels/
    • Directoryresources
      • Directoryviews
        • Directoryarticles/

    En ligne de commande en étant dans le répertoire du projet, vous pouvez utiliser la commande suivante:

    Windows Powershell
    mkdir app/Controllers/, app/Models/, resources/views/articles/
    Linux
    mkdir -p app/Controllers/ app/Models/ resources/views/articles/
  2. Créez les fichiers suivants dans le projet :

    • Directoryapp
      • DirectoryControllers
        • Home.php
      • DirectoryModels
        • Article.php
      • bootstrap.php
    • Directoryresources
      • Directoryviews
        • Directoryarticles
          • list.php
          • form.php
      • aside.php
      • footer.php
      • header.php
      • template.php
    • routes.php

    En ligne de commande en étant dans le répertoire du projet, vous pouvez utiliser la commande suivante:

    Windows Powershell
    ni app/bootstrap.php, app/Controllers/Home.php, app/Models/Article.php,
    resources/views/articles/list.php, resources/views/articles/form.php,
    resources/views/header.php, resources/views/footer.php,
    resources/views/aside.php, resources/views/template.php, routes.php
    Linux
    touch app/bootstrap.php app/Controllers/Home.php app/Models/Article.php resources/views/articles/list.php resources/views/articles/form.php resources/views/header.php resources/views/footer.php resources/views/aside.php resources/views/template.php routes.php

Votre projet devrait maintenant avoir cette structure :

  • DirectoryPWS-TP1
    • Directoryapp
      • DirectoryControllers
        • Home.php
      • DirectoryModels
        • Article.php
      • bootstrap.php
    • Directoryresources
      • Directoryviews
        • Directoryarticles
          • list.php
          • form.php
      • aside.php
      • footer.php
      • header.php
      • template.php
  • database.sqlite
  • index.php
  • README.md
  • routes.php
  • style.css

Vérifiez bien le nommage des répertoires et des fichiers !

Exercice 2.2 - Réorganisation du code - index.php

Section titled “Exercice 2.2 - Réorganisation du code - index.php”

Nous allons maintenant déplacer le code du fichier index.php dans les différents répertoires et fichiers créés dans l’exercice 2.1.

Lorsque vous aurez terminé cet exercice, le projet fonctionnera de manière similaire à la première version, mais le code sera organisé en structure MVC :

  1. Coupez le premier bloc de PHP (ligne 1 à 57) du index.php et collez le dans le fichier :

    app/bootstrap.php

  2. Coupez tout le code restant et placez le dans le fichier :

    resources/views/template.php

  3. Dans le fichier index.php, qui doit être maintenant vide, placez le code suivant :

    <?php
    require 'app/bootstrap.php';
    require 'resources/views/template.php';
    ?>

La fonction PHP require permet d’inclure du code d’un script PHP dans un autre script. Si vous rafraîchissez la page dans votre navigateur, le site doit fonctionner de la même manière qu’auparavant.

À ce stade, nous avons simplement déplacer le code de index.php dans 2 autres fichiers, puis nous avons inclus ces 2 fichiers dans index.php

Dans le fichier resources/views/template.php :

  1. Déplacer le code d’en-tête (de la ligne 1 à la ligne 18, soit du début jusqu’à la balise </header>) dans :

    resources/views/header.php

  2. Déplacer le code de la balise <section class="articles"> dans :

    resources/views/articles/list.php

  3. Déplacer le code de la balise <aside> dans :

    resources/views/aside.php

  4. Déplacer le code restant dans :

    resources/views/footer.php

Puis, dans le fichier template.php, qui doit maintenant être vide, ajoutez des require vers ces fichiers :

<?php
require 'header.php';
require 'articles/list.php';
require 'aside.php';
require 'footer.php';

Si vous rafraîchissez la page de votre navigateur, le site doit toujours fonctionner de la même manière.

Sortez le code HTML du formulaire d’ajout d’article, placez le dans le fichier resources/views/articles/form.php puis ajoutez un require vers ce fichier dans aside.php

Vérifiez que le site fonctionne toujours correctement.

À ce stade, nous avons simplement segmenté le fichier index.php original en plusieurs fichiers. Le fichier template.php est directement chargé dans index.php, et les autres fichiers Views sont à leur tour chargés depuis template.php.

L’idée du MVC est de contrôler les requêtes HTTP entrantes et de déclencher certaines actions en conséquence.

Les controllers doivent être chargés depuis l’index.php, et ils se chargeront à leur tour d’appeler les Models et Views.

Créons notre premier Controller. Nous voulons utiliser de la programmation objet.

Dans le fichier app/Controllers/Home.php, ajoutez le code suivant :

<?php
class Home
{
function index()
{
require "resources/views/template.php";
}
}

C’est une classe d’objet très simple, nommée Home et qui contient pour l’instant une seule fonction index. La fonction index charge le fichier template.php comme précédemment dans index.php

Puis nous devons modifier le fichier index.php, en remplaçant la ligne:

require 'resources/views/template.php';

par le code :

require_once 'app/Controllers/Home.php';
$controller = new Home();
$controller->index();

Ici, on inclut le fichier du Controller Home pour que sa classe d’objet soit disponible. Puis on instancie un objet de la classe Home que l’on stocke dans la variable $controller. Enfin, on appelle la fonction index() qui va à son tour inclure le fichier template.php

Si vous rafraîchissez la page dans votre navigateur, le site devrait “en partie” fonctionner : La liste des articles ne s’affiche plus correctement !

La liste des articles utilise une variable $articles, qui jusqu’à maintenant, était initialisée dans le fichier app/boostrap.php. Bien que cette variable soit toujours initialisée dans le fichier bootstrap, elle n’est plus disponible, car elle “n’existe pas” dans la classe d’objet Home. En effet, en programmation objet, les variables non globales ne sont pas accessibles à l’intérieur des classes d’objet.

Nous allons maintenant devoir modifier le fichier bootstrap et créer notre premier Model. Nous appellerons ensuite ce modèle dans le Controller Home pour pouvoir passer les articles à la View..

Ouvrez le fichier app/Models/Article.php, puis ajoutez ce code :

<?php
class Article
{
protected $pdo;
function __construct()
{
}
function list()
{
}
function add($data)
{
}
}

Nous allons maintenant extraire et adapter le code relatif à la base de données et aux articles du bootstrap.php.

La fonction __construct() est une fonction spéciale en PHP objet. C’est la fonction constructeur. Elle est automatiquement appelée lorsqu’un objet de la classe est instanciée.

Nous allons placer dans cette fonction la connexion à la base de données, qui sera stockée dans la variable $pdo (PDO signifie PHP Data Object, c’est la librairie PHP permettant d’interagir avec des sources de données).

Voici le code de la fonction __construct() :

function __construct()
{
try {
$this->pdo = new \PDO('sqlite:' . DB_PATH);
} catch (Exception $e) {
print "Erreur de connexion à la base de données : " . $e->getMessage() . "<br/>";
die();
}
}

Ce code utilise une constante DB_PATH qui va contenir le chemin vers la base de données. Cette constant doit être définie dans le fichier bootstrap.php en ajoutant ce code :

DEFINE('DB_PATH', realpath("./database.sqlite") );

En effet, le chemin vers la base de données va pouvoir varier en fonction de la machine où est installée le projet. On considérera que ce chemin est une variable de configuration, et les variables de configuration sont à placer dans bootstrap.php.

function list()
{
try {
$query = $this->pdo->prepare('SELECT * FROM articles');
$query->execute();
return $query->fetchAll(PDO::FETCH_CLASS);
} catch (Exception $e) {
print "Erreur fonction list() dans le modèle Article : " . $e->getMessage() . "<br/>";
die();
}
}

Par rapport à la version dans bootstrap.php, on a remplacé $pdo par $this->pdo. On a retourné le résultat de la requête plutôt que de le stocker dans une variable et on a englobé le tout dans un bloc try / catch pour capturer une potentielle erreur.

function add($data)
{
try {
if (isset($data['article_title']) && isset($data['article_content'])) {
$query = $this->pdo->prepare('INSERT INTO articles ("article_title", "article_content") VALUES (?,?)');
$query->execute([$data['article_title'], $data['article_content']]);
return true;
}
return false;
} catch (Exception $e) {
print "Erreur fonction add($data) dans le modèle Article : " . $e->getMessage() . "<br/>";
die();
}
}

Par rapport à la version dans bootstrap.php, on a remplacé $_POST par $data, ce qui nous permet de ne plus être dépendant de la variable superglobale PHP $_POST.

Nous avons aussi testé que $data contenait bien des valeurs pour l’article à ajouter, et nous avons englobé le tout dans un bloc try / catch pour capturer une erreur le cas échéant.

Notre modèle est prêt. Vous pouvez supprimer la variable $articles et tout le bloc try / catch du fichier bootstrap.php

Maintenant que le Model Article est prêt, nous allons pouvoir l’utiliser dans le Controller Home :

<?php
require_once 'app/Models/Article.php';
class Home
{
function index()
{
$article_model = new Article();
$articles = $article_model->list();
require "resources/views/template.php";
}
}

On inclut la classe Article pour qu’elle soit disponible.

On instancie la classe Article et on stocke l’objet dans une variable $article_model.

On appelle la fonction list() sur l’objet Article et on stocke le résultat dans la variable $articles.

On inclut le fichier template.php, désormais la variable $articles est de nouveau disponible.

Si vous rafraîchissez votre navigateur, le site devrait fonctionner à nouveau !

La liste des articles est de nouveau fonctionnelle, mais nous n’avons pas encore traité le cas d’ajout d’un nouvel article.

Côté Controller Home, c’est assez simple, il suffit d’ajouter la fonction suivante à la classe dans Home.php :

function add()
{
if(isset($_POST['ajout_article'])) {
$article_model = new Article();
$article_model->add($_POST);
}
$articles = $article_model->list();
require "resources/views/template.php";
}

On teste simplement si la variable $_POST contient des données puis on appelle la fonction add du Model Article.

Maintenant se pose la question de comment va-t-on appeler cette fonction ?

Actuellement, dans index.php, on instancie directement le Controller Home et on appelle la fonction index().

Nous devons faire évoluer ce code pour déterminer quelle fonction (ou action) doit être appelée. De plus, notre application comprendra surement plus d’un Controller. Nous devons donc définir quels Controller et actions doivent être appelés en fonction de l’url demandée.

C’est le but du fichier routes.php : Déterminer les routes à emprunter en fonction de l’url et de la requête HTTP reçues.

Ajoutez le code suivant au fichier routes.php :

<?php
//On doit inclure tous les Controllers dont on veut disposer
require_once 'app/Controllers/Home.php';
//On définit nos routes
$routes = [
'/' => ['Home','index'],
'/articles/add' => ['Home','add']
];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); //on récupère l'URL
if(key_exists($path, $routes)) { //On regarde si l'url existe dans le tableau $routes
$controller_name = $routes[$path][0]; //on récupère le nom du Controller
$action_name = $routes[$path][1]; //on récupère le nom de l'action
$controller = new $controller_name; //on instancie le Controller
$controller->$action_name(); //on appelle la fonction correspondante
}
else {
http_response_code(404);
echo 'page non trouvée';
}

Puis modifier le fichier index.php :

<?php
require 'app/bootstrap.php';
require 'routes.php';

Et enfin, le formulaire doit pointer vers la bonne route. Modifier la balise form dans le fichier resources/views/articles/form.php :

<form action="<?= BASE_URL ?>articles/add" method="post">

Et voilà ! L’ajout d’un article fonctionne à nouveau.

À ce stade, l’application s’approche d’une architecture MVC même s’il reste de nombreuses améliorations à effectuer :

  • Le passage du système de Views en objet
  • Modifier le template.php pour pouvoir charger différent contenus de différents Controllers
  • Le chargement automatique des classes et l’utilisation des namespaces, pour éviter d’avoir à inclure les classes d’objet dont on a pas besoin
  • Mettre en place le design pattern Repository
  • et bien d’autres… qui seront disponible dans Laravel

Le but de ce TP est de vous initier à l’architecture MVC, pour comprendre comment sont organisées les applications qui implémentent ce design pattern.

Le framework Laravel, que vous allez utiliser par la suite, est bien plus évolué que le code de cette activité.

L’organisation des fichiers, la syntaxe des MVC, et la logique seront légèrement différents, cependant, cette activité devrait vous avoir apporté une base de connaissance pour mieux appréhender Laravel et le design pattern MVC.

Pour aller plus loin avec ce projet et vous exercer au PHP, vous pouvez passer à l’activité suivante