1 PHP Première application WEB

Dans ce TP nous allons créer une application web Utilisant le modèle MVC sans utiliser de framework. Néanmoins, nous allons sélectionner quelques packages disponibles afin de gérer la partie ‘frontend’ plus facilement.

1.1 Architecture du projet

Nous allons mettre en oeuvre une architecture de notre projet qui facilite la recherche des classes de notre application et qui permette l’intégration de classes de librairies tiers.

Dans le modèle MVC on retrouve :

  • Le modèle qui donne une représentation des données utilisées dans notre application. Les données sont généralement stockées dans une base de données. Dans notre TP nous allons utiliser le système de gestion de base de données MySQL et la classe PDO pour communiquer entre notre application et la base de données.

  • Les vues qui affichent dans le navigateur les réponses aux requêtes. La librairie BladeOne sera utilisée comme librairie tiers qui génère des pages HTML à partir de template. Cela permet de séparer l’aspect visuel et le code pour le traitement. BladeOne est une version équivalente du moteur de templare Blade utilisé dans Laravel.

  • Le contrôleur analyse les requêtes, aiguille vers le code (Contrôleur spécifique) pour traiter la requête. Le contrôleur récupère les données nécessaires pour répondre à la requête et construit la vue qui sera renvoyée au navigateur. Dans notre cas le contrôleur utilisera le moteur de template BladeOne. L’acteur qui permet facilement d’aiguiller la requête vers le bon contrôleur s’appelle un routeur. La librairie miladrahimi/phprouter va nous permettre de mettre un routeur autonome et disposant de nombreuses possibilités.

A l’aide de PhpStorm, créez un nouveau projet ProjetTache et créez à la racine un répertoire src.

Créez à la racine de votre projet un fichier composer.json à l’aide de la commande composer init.

Ajoutez au fichier composer.json une référence au code source de votre projet (choisir un préfixe) qui se trouvera dans le répertoire src.

Vous devez fixer un préfixe (vendor) pour votre code. Par exemple \IutLens\Tache

Ajouter à votre projet les dépendances à l’aide de la commande composer require :

  • D’une part :
    • Le moteur de templates : eftec/bladeone
    • Le routeur : miladrahimi/phprouter
  • D’autre part :
    • Le framework de formatage : twbs/bootstrap,
    • La librairie javascript : components/jquery,
    • Une police de caractères : components/font-awesome et
    • L’interpréteur de texte markdown : michelf/php-markdown

Dans le répertoire src créez un fichier Config.php. Dans ce fichier définir les constantes :

  • DB_TYPE qui contient le type de driver (mysql, pgsql , sqlite, …)
  • DB_HOST qui contient le nom du serveur de base de données.
  • DB_NAME qui contient le nom de votre base de données.
  • DB_USER qui contient votre identité.
  • DB_PASS qui contient votre mot de passe.

Créez à la racine de votre projet un fichier index.php qui utilise l’auto-loader créé par la commande composer et qui charge en plus le fichier Config.php.

En effet le fichier Config.php n’est pas une classe, il ne peut pas être chargé à l’aide de l’auto-loader. On utilisera la fonction require_once.

Tester votre script index.php en affichant les valeurs des constantes définies dans le fichier Config.php.

1.2 Le modèle

Dans cette section nous allons mettre en place la couche modèle. Notre application assurera toutes les opérations de base d’une application CRUD (Create, Read, Update, Delete).

Notre modèle est essentiellement composé de la classe Tache dont voici le listing :

namespace IutLens\Tache\Modele;


class Tache {
    /** @var  int */
    public $id;
    /** @var  string */
    public $expiration;
    /** @var  string */
    public $categorie;
    /** @var  string */
    public $description;
    /** @var  string */
    public $accomplie;

    public function __construct(
        $expiration = "",
        $categorie = "",
        $description = "",
        $accomplie = "N",
        $id = false
    ) {
        $this->id = $id;
        $this->expiration = $expiration;
        $this->categorie = $categorie;
        $this->description = $description;
        $this->accomplie = $accomplie;
    }

    /**
     * @return int
     */
    public function getId(): int {
        return $this->id;
    }

    /**
     * @param int $id
     */
    public function setId(int $id) {
        $this->id = $id;
    }

    /**
     * @return string
     */
    public function getExpiration(): string {
        return $this->expiration;
    }

    /**
     * @param string $expiration
     */
    public function setExpiration(string $expiration) {
        $this->expiration = $expiration;
    }

    /**
     * @return string
     */
    public function getCategorie(): string {
        return $this->categorie;
    }

    /**
     * @param string $categorie
     */
    public function setCategorie(string $categorie) {
        $this->categorie = $categorie;
    }

    /**
     * @return string
     */
    public function getDescription(): string {
        return $this->description;
    }

    /**
     * @param string $description
     */
    public function setDescription(string $description) {
        $this->description = $description;
    }

    /**
     * @return string
     */
    public function getAccomplie(): string {
        return $this->accomplie;
    }

    /**
     * @param string $accomplie
     */
    public function setAccomplie(string $accomplie) {
        $this->accomplie = $accomplie;
    }

    function __toString() {
        return sprintf(
            "%d: %s %s %s %",
            $this->id,
            $this->expiration,
            $this->categorie,
            $this->description,
            $this->accomplie
        );
    }
}

Qui se traduit dans la base de données comme un enregistrement dans la table tache.

Dans ce TP, nous allons réutiliser le travail réalisé dans le TP météo.

Nous allons reprendre la classe AccesBD qui fait le lien entre l’application et la base de données. Ensuite nous allons définir une interface IBDMapper qui déclare l’ensemble des fonctions nécessaires à notre application pour dialoguer avec la base de données.

Le listing suivant donne le contenu de l’interface IBDMapper.

Ajouter la table tache dans votre base de données.

Ajouter la classe Tache dans un répertoire Modele sous le répertoire src.

Ajouter la classe AccesBD IBDMapper dans un répertoire BD sous le répertoire src.

Ecrire le code de la classe MyBDMapper dans un répertoire BD qui implémente l’interface IBDMapper.

Dans la classe MyBDMapper on créera une fonction __construct() avec un paramètre de type PDO qui aura par défaut une valeur nulle. Dans cette fonction __construct() on pourra aussi définir les requêtes préparées comme par exemple celle qui permet de récupérer l’ensemble des enregistrements de la table tache.

Les requêtes dans la base de données doivent nous renvoyer des instances de la classe \IutLens\Tache\Modele\Tache, pour cela nous devons utiliser la fonction setFetchMode() avec les paramètres :

  • PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE pour indiquer que l’on instancie une classe et appelle du constructeur avant de définir les propriétés,

  • \IutLens\Tache\Modele\Tache, le nom de la classe à instancier.

1.2.1 Environnement de tests

  1. Récupérations des librairies pour faire les tests. Les librairies à récupérer sont
    • “phpunit/phpunit”, et
    • “phpunit/dbunit”
  2. Ajouter le fichier de configuration de phpunit phpunit.xml à la racine de votre projet

  3. Créer un répertoire pour les tests (tests par exemple)

  4. Ajouter l’entrée autoload-dev dans le fichier composer.json

  5. Dans un répertoire data sous le répertoire tests créer une base de données sqlite maBaseTest.sqlite contenant la table tache.

    Il est pratique et peu coûteux en ressource d’utiliser pour les tests une base de données sqlite. Le tutoriel sur le site Le Portail Des Programmeurs Francophones explique comment créer une base de données à partir de la ligne de commande :

  6. Utiliser un jeu de tests Dans le répertoire data du répertoire tests on téléchargera
    • Le fichier de données pour la table tache taches.csv.
  7. Ecrire un programme de tests

    Le chapitre Tester des bases de données de la documentation de PHPUnit propose une explication pour créer un programme de tests qui utilise une base de données. Le listing suivant donne un exemple de base pour :

    • ouvrir une connexion avec la base de données (getConnection()),
    • charger les données dans la base de données (getDataSet()),
    • tester les accès à la base de données (testFetchAllStations() par exemple),
    • vider la base de données (pour pouvoir reproduire les tests, getTearDownOperation()).
      <?php
    
    use IutLens\Meteo\BD\MyBDMapper;
    use PHPUnit\DbUnit\DataSet\CsvDataSet;
    use PHPUnit\DbUnit\TestCaseTrait;
    use PHPUnit\Framework\TestCase;
    
    class MyDBMapperTest extends TestCase {
        use TestCaseTrait;
        static private $pdo = null;
        private $conn = null;
    
    
        /**
         * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection
         */
        public function getConnection() {
            if (!defined('USER'))
              define('USER', null);
            if (!defined('PASS'))
              define('PASS', null);
            if (!defined('NAME'))
              define('NAME', __DIR__.'/data/maBaseTest.sqlite');
            if (!defined('OPTIONS'))
            define('OPTIONS', [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_CLASS, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
            if (!defined('DSN'))
              define('DSN', 'sqlite:'.NAME);
    
            if ($this->conn === null) {
                if (self::$pdo == null) {
                    try {
                        self::$pdo = new PDO(DSN,USER, PASS, OPTIONS);
                    } catch (PDOException $e) {
                        echo "Erreur :".$e->getMessage().PHP_EOL;
                    }
                }
                $this->conn = $this->createDefaultDBConnection(self::$pdo, NAME);
            }
    
            return $this->conn;
        }
    
        /**
         * @return PHPUnit_Extensions_Database_DataSet_IDataSet
         */
        public function getDataSet() {
          $dataSet = new CsvDataSet(";");
          $dataSet->addTable('tache', dirname(__FILE__)."/data/taches.csv");
    
          return $dataSet;
        }
    
        public function testToutesLesTaches(): void {
            $mapper = new MyBDMapper(self::$pdo);
            $allTasks = $mapper->fetchAllTask();
            $this->assertCount(20, $allTasks);
        }
    
        /**
         * @return PHPUnit_Extensions_Database_DataSet_IDataSet
         */
        protected function getTearDownOperation() {
            return \PHPUnit\DbUnit\Operation\Factory::TRUNCATE();
        }
    }

Tester que les fonctions de la classe MyBDMapper sont correctes.

1.2.2 Première page web

L’interpréteur PHP dispose d’un serveur web de développement, nous allons l’utiliser dans la suite du TP pour développer notre application web. Ce serveur web s’exécute sur votre machine (localhost) et il attente des requêtes sur le port 8080 par défaut. C’est une version simple d’un serveur d’applications comme apache ou encore nginx.

Pour lancer ce serveur de développement, utilisez la commande suivante :

ou

Lance un serveur web dont la racine est le répertoire courant (le symbole ‘.’ après l’option -t ) qui écoute sur le port 8080 ici. L’Url http://localhost:8080 va exécuter le code présent dans le fichier index.php.

Ecrire un script listeTaches.php qui affiche l’ensemble des tâches contenues dans la base.

Tester votre script http://localhost:8080/listeTaches.php.

1.3 Mise en oeuvre du moteur de templates

La réalisation des vues représente un travail important lors du développement d’une application web. Ce travail se décompose en deux parties

  • Le style, l’organisation des données, ce que les spécialistes appellent la charte graphique, qui est réalisée par des graphistes. Une connaissance du langage CSS associée au langage de balises HTML est requise.
  • Insertion des données qui répondent à la requête. C’est le travail de l’informaticien de retrouver les informations, il utilise pour cela un langage de programmation (en particulier PHP).

Un site web utilise, en général, une charte, un style qui est partagé par l’ensemble des pages. Il existe des librairies qui mettent en oeuvre ce qu’on appelle un moteur de templates. L’idée est de définir une charte dans un template (un modèle) et de permettre l’ajout de paramètres (similaire au format de la fonction sprintf() déjà utilisée, mais ici pour une page HTML).

1.3.1 Utilisation du moteur de templates BladeOne

Parmi les moteurs de templates nous avons retenu BladeOne car c’est celui que sera utilisé dans le framework Laravel.

  1. Il faut ajouter le moteur BladeOne dans votre projet

  2. Configurer l’exécution du moteur de templates. Comme cette opération ne doit être faite qu’une seule fois, nous allons comme pour l’accès à la base de données créer une classe Blade qui met en oeuvre le patron de conception Singleton. Voir le listing suivant pour un exemple d’implémentation de la classe Blade.

    Dans le code on précise que les vues (templates) seront placées dans le répertoire views (ici comme la classe se trouve dans le répertoire src/Singleton pour respecter le namespace, les vues seront placées dans le répertoire views à la racine du projet).

  3. Créer un template.

    Un template est une page HTML classique contenant des directives Blade qui seront interprétées par le moteur de templates. Pour avoir la liste compléte des directives, voir la documentation BladeOne.

    Voici un exemple de template contacts.blade.php qui affiche une liste de contacts.

    Dans la page on utilise la directive @foreach qui fonctionne comme l’instruction foreach en PHP et la syntaxe {{ }} qui permet d’afficher des données PHP dans notre template.

  4. Utilisation du template.

    Pour utiliser le template, on appelle la méthode run avec deux paramètres :

    1. le nom du template
    2. un tableau associatif des données utilisées dans le template.

    Voici un exemple qui utilise le template contacts.blade.php

Il est possible de décomposer un template en plusieurs fichiers. Une page se décompose en

  • une en-tête
  • un corps variable en fonction de la requête
  • un pied de page

Chaque partie peut se trouver dans un fichier différent qui sera inclus dans un fichier principal. Voici un exemple

Mettre à jour les dépendances dans votre projet pour permettre l’utilisation de BladeOne, bootstrap, ….

Voir les librairies qui sont indiquées en introduction du TP en laissant de côté pour le moment les librairies miladrahimi/phprouter et zendframework/zend-diactoros.

Créez les fichiers :

  • master.blade.php
  • header.blade.php
  • footer.blade.php

Dans le répertoire views à la racine de votre projet.

Le fichier master.blade.php devra contenir une partie variable.

Vous pouvez partir des fichiers exemples donnés plus haut dans le texte et les adapter à votre goût

En utilisant les fichiers templates que vous venez de créer répondre aux questions suivantes :

Ecrire le script listeTaches.php qui liste les tâches présentent dans la base de données

Ecrire le script creeTache.php qui ajoute une tâche dans la base de données

1.4 Mise en oeuvre du routeur

Une application web qui met en oeuvre un modèle MVC utilise une entrée unique qui dispatche les requêtes vers la bonne partie du code à exécuter. Pour dispatcher les requêtes, un mécanisme de routage est utilisé. La encore, il existe plusieurs librairies qui propose une implémentation d’un routeur. Dans la suite du TP nous allons utiliser la librairie miladrahimi/phprouter.

Un mécanisme de communication normalisée entre le routeur et le code à exécuter permettra de gérer les paramètres et le contenu des requêtes. La librairie zendframework/zend-diactoros implémente la norme PSR-7 qui propose une interface pour représenter les messages (requêtes et réponses) HTTP.

1.4.1 Utilisation d’un routeur dans notre application

Nous allons créer un script index.php dans le répertoire racine du projet qui sera le point d’entrée de notre application.

Dans ce script nous allons créer une instance de la classe Router proposée par la librairie miladrahimi/phprouter. L’instance de la classe Router nous permettra de créer des routes (aiguillages) entre une URL et une action. Par exemple :

Dans l’exemple deux routes ont été déclarées :

  • / qui exécutera la fonction index de la classe Welcome.
  • /welcome qui exécutera la fonction welcome de la classe Welcome.

Les fonctions exécutées ont pour rôle de produire la réponse à la requête, en général une page HTML. Voici le code de ces fonctions :

Dans le code donné, on utilise le moteur de templates Blade pour produire le code HTML.

  1. La fonction index utilise la vue hello.blade.php qui utilise une variable visiteur.

    Voici le code de la vue hello.blade.php qui utilise les fichiers master, header, footer utilisés dans la section précédente :

  2. La fonction welcome utilise la vue info.blade.php qui utilise une variable content.

    Ici on utilise un fichier au format markdown dans le répertoire public à la racine du projet. Le contenu du fichier welcome.md peut être récupéré ici. On utilise la librairie michelf/php-markdown et la classe MarkdownExtra pour transformer le fichier welcome.md en code HTML.

    Le code du script info.blade.php est le suivant :

La directive {!!$content or ''!!} est utilisée pour autoriser le code HTML comme valeur du paramètre.

1.4.2 Utilisation de paramètre dans la route

Il est possible de donner un paramètre dans la route. Ce paramètre pourra devenir un argument de la fonction associée à la route. Par exemple :

Utilise le paramètre id qui pourra être un argument de la fonction show

1.4.3 Utilisation du contenu de la requête

Il est possible de transmettre le contenu de la requête comme argument de la fonction associée à une route. Par exemple :

Utilise le paramètre id qui pourra être un argument de la fonction show

Ici l’argument $request contient les données de la requête. La fonction affiche une réponse au format Json.

Mettre à jour les dépendances dans votre projet pour permettre l’utilisation du routeur, de l’interface des messages HTTP, ….

Créer une classe Taches dans le fichier Taches.php du répertoire src/Controleur

Dans la classe Taches créer un constructeur qui fait le lien entre la base de données et la classe

Dans la classe Taches créer les fonctions :

  • index() qui liste les tâches de la base de données.
  • show($id) qui affiche les détails d’une tâche. La fonction sera utilisée pour valider une demande de suppression d’une tâche.
  • create() qui affiche le formulaire de création d’une tache.
  • store($request) qui vérifie la saisie du formulaire (à l’aide de la fonction valideTache), ajoute la tâche dans la base de données et renvoie vers l’affichage de la liste des tâches si la saisie est correcte ou renvoie vers la création d’une tâche sinon.
  • edit($id) qui affiche le formulaire de modification d’une tâche.
  • update($request, $id) qui vérifie la saisie du formulaire (à l’aide de la fonction valideTache), modifie la tâche dans la base de données, renvoie vers l’affichage de la liste des tâches si la saisie est correcte ou renvoie vers la modification d’une tâche sinon.
  • destroy($id) qui supprime une tâche de la base de données.
  • valideTache($request, &$tache) qui vérifie que les champs du formulaire ont été saisis correctement (format de la date, présence obligatoire de la catégorie et de la description) et affecte les valeurs saisies dans le formulaire à la variable $tache donnée en argument de la fonction.

Dans le répertoire views créer les scripts PHP :

  • create.blade.php qui affiche un formulaire de saisie d’une tâche.
  • edit.blade.php qui affiche un formulaire de modification d’une tâche.
  • show.blade.php qui affiche une tâche.
  • taches.blade.php qui affiche la liste des tâches dans la base de données.

 

IUT de Lens Département Informatique

2018