Skip to content

Instantly share code, notes, and snippets.

@tomsihap
Last active June 21, 2019 14:49
Show Gist options
  • Select an option

  • Save tomsihap/450d5923b746b71091961f0d08f36ca1 to your computer and use it in GitHub Desktop.

Select an option

Save tomsihap/450d5923b746b71091961f0d08f36ca1 to your computer and use it in GitHub Desktop.
Exercices MVC sur le projet base-videoclub-wf3

EXERCICE : Créer des pages

Créez les routes suivantes, avec les méthodes du contrôleur et vues correspondantes :

- /
- /about
- /contactez-nous

CORRECTION

On créée les routes dans routes.php :

$router->get('/', 'PagesController@home' );
$router->get('/about', 'PagesController@about');
$router->get('/contactez-nous', 'PagesController@contact');

On créée les méthodes qui correspondent dans PagesController.php. On appelle une vue pour chaque méthode (en effet, ce sont 3 pages différentes donc 3 vues à créer).

class PagesController {

    // Route '/'
    public function home() {
        view('pages.home');

    }

    // Route '/about'
    public function about() {
        view('pages.about');
    }

    // Route '/contactez-nous'
    public function contact() {
        view('pages.contact');
    }
}

Dans le dossier public/views :*

Fichier pages/home.php :

<?php ob_start(); ?>
    <h1>Bienvenue !</h1>


<?php $content = ob_get_clean() ?>

<?php view('template', compact('content')); ?>

Fichier pages/about.php :

<?php ob_start(); ?>
    <h1>À propos de ce site</h1>


<?php $content = ob_get_clean() ?>

<?php view('template', compact('content')); ?>

Fichier pages/contact.php :

<?php ob_start(); ?>
    <h1>Contactez-nous</h1>


<?php $content = ob_get_clean() ?>

<?php view('template', compact('content')); ?>

EXERCICE : Gérer un formulaire

Créez les routes suivantes :

- GET ajout-article
- POST ajout-article

Vous créérez une vue pour GET ajout-article avec un formulaire qui pointera vers POST ajout-article. Vous ferez un dump de $_POST dans la méthode qui gère POST ajout-article.

AIDE : En configurant correctement la BASE_URL dans config/config.php, vous pourrez mettre l'action du formulaire ainsi :

<form method="post" action="<?= url('ajout-article') ?>">
</form>

CORRECTION

On créée les routes dans routes.php :

$router->get('ajout-article', 'ArticlesController@ajout' );
$router->post('ajout-article', 'ArticlesController@save');

On voit que l'URL est la même, mais la méthode HTTP change (get et post), et la méthode de la classe change aussi (GET est traité dans ajout(), POST est traité dans save()) :

Méthode HTTP URL Classe Méthode de la classe Notes
GET ajout-article ArticlesController ajout() Affiche le formulaire
POST ajout-article ArticlesController save() Acion du formulaire
class ArticlesController {

    // Route GET ajout-article
    public function ajout() {
        view('articles.ajout');

    }

    // Route POST ajout-article
    public function save() {
        dump($_POST);
    }
}

Dans le dossier public/views :*

Fichier articles/ajout.php :

<?php ob_start(); ?>
    <h1>Ajoutez un article :</h1>

    <form method="post" action="<?= url('ajout-article') ?>">

        <input type="text" name="content" placeholder="Contenu de l'article">

        <input type="submit" value="Envoyer l'article">
    </form>


<?php $content = ob_get_clean() ?>

<?php view('template', compact('content')); ?>

EXERCICE : Enregistrer les données d'un formulaire

Prérequis :

Soient les routes suivantes déjà créées :

- GET ajout-article     => ArticlesController@add
- POST ajout-article    => ArticlesController@save

Soit la table articles :

articles
---
id          INT PK AI
title       VARCHAR(255)
content     TEXT
author_id   INT
created_at  DATETIME DEFAULT=CURRENT_TIMESTAMP
updated_at  DATETIME ON_UPDATE=CURRENT_TIMESTAMP

N'oubliez pas de modifier config.php par rapport à votre configuration de base de données et de projet !

** NOTE IMPORTANTE** : Vous allez créer de nombreux éléments. À chaque étape, n'oubliez pas de tester ce que vous faites. En particulier, vous allez créer un Model dans un fichier Article.php qui vous permettra de faire certaines actions, mais c'est bien dans le controller ArticlesController.php que les tests (dump()) seront à faire ! En effet, le controller appelle le Model.

Exercice :

On va gérer l'enregistrement des données de formulaire dans la méthode ArticlesController@save.

Pour pouvoir créer un article, le controller va faire appel au Model. Le rôle du Model est d'être un modèle d'une table de la base de données, c'est à dire qu'il sera une représentation des données au sein de notre code PHP.

Par exemple, le type de données array est une représentation d'un tableau, avec ses règles (des clés, valeurs, des obligations de format...). Le model va nous permettre de créer un type de données correspondant à une table de la base de données.

Par exemple, une donnée de type Article doit être un élément contenant un titre, un contenu, un auteur existant, une date de création...

Le Model nous permet d'établir toutes ces règles.

Description du model Article

Dans le dossier src/model, créez le fichier Article.php.

Attention : les Model sont nommés en PascalCase et au singulier !

Fichier Article.php:

class Article {

}

Le Model est une classe. Une classe est une structure de code qui peut contenir des fonctions qui lui sont propres (les méthodes), et des variables qui lui sont propres, les attributs.

De la même manière qu'un élément de type array a des sortes d'attributs (une taille, des données, des clés, des valeurs...) et des sortes de méthodes (fonctions de tri, fonctions d'ajout, fonctions de suppression, fonctions de recherche...), un élément de type Article a en effet des fonctions et des attributs !

Les attributs peuvent être par exemple :

  • un id
  • un titre
  • un contenu
  • une date de création
  • ...

Il a aussi des méthodes :

  • une fonction pour s'enregistrer
  • une fonction pour se supprimer
  • une fonction pour s'éditer
  • ...

Un Model n'est donc ni plus ni moins qu'un type de données que nous créons.

Liste des attributs

Listons les attributs possibles de Article :

class Article {

    /**
     * LISTE DES ATTRIBUTS
     */

    // Il s'agit du nom de la table dans la base de données
    const TABLE_NAME = "articles";

    // il s'agit de la liste des champs de la table, mis en attributs :
    protected $id;
    protected $title;
    protected $content;
    protected $authorId;
    protected $createdAt;
    protected $updatedAt;
}

Comme prévu, le type Article a comme attributs la liste des champs de la table. En effet, un Article est en fait un élément qui contient un id, un titre, un contenu.... etc.

Qu'est-ce que protected ? En programmation orientée objet, il s'agit de la portée de l'attribut. Cela veut dire que ces attributs peuvent être connus au sein de la classe Article ou de ses classes enfants - on en reparlera -, mais pas d'ailleurs. Comment donner un titre à l'article ou y accéder alors ? Voir ci-dessous les setters et getters !

Liste des méthodes : setters

Parmi les méthodes du Model, on a des setters et des getters. En POO, on parle de mutateurs pour les setters, et d'accesseurs pour les getters.

Les setters servent à établir la valeur d'un attribut. Les getters servent à accéder à la valeur d'un attribut.

Pourquoi passer par des méthodes plutôt que de leur donner une valeur immédiatement ?

On pourrait en effet accéder aux données du Model directement, par exemple :

$article = new Article; // On créée un nouvel objet Article
$article->title = "Nouvel article"; // On donne une valeur à $article->title
echo $article->title; // On accède à la valeur de $article->title

Mais ce n'est pas une bonne façon de faire : en faisant comme ça, on n'a aucune assurance que les données que l'on met dans le titre soient valides (par exemple pas le bon nombre de caractères, pas de majuscule au début...), et on n'a aucune assurance que la donnée du titre soit celle que l'on souhaite (pas exemple, dans le cas d'un système de traduction où le titre doit être traduit avant).

Les setters et getters sont donc des garde-fou qui nous permettent de nous assurer que les données soient bien telles que l'on souhaite qu'elles soient.

On a donc des setters pour chaque attribut, par exemple :

class Article {

    // ...

    /**
     * LISTE DES MÉTHODES : SETTERS
     */

    public function setTitle($title) {
        $this->title = $title;
    }
}

Décrivons ce setter :

// La méthode prend en argument le titre que l'on souhaite donner : $title

// Le setter ne fait qu'une chose pour l'instant : il donne la valeur $title à l'attribut de l'objet actuel.

// Pour parler d'un attribut d'un objet, dans la classe elle même, on utilise $this->attribut. Ici, $this->title.
public function setTitle($title) {
    $this->title = $title;
}

À l'utilisation, dans le controller par exemple :

$article = new Article; // On crée une instance de Article, un nouvel objet Article
$article->setTitle("Hello world");

Mais pour l'instant rien ne change, au final l'attribut prend la valeur que je lui donne sans rien contrôler ! En effet, mais c'est dans le setter que nous allons gérer les validations. Améliorons notre setter.

public function setTitle($title) {

    if (strlen($title) < 3) {
        throw new Exception ("Le titre est trop court.");
    }

    if (strlen($title) > 255) {
        throw new Exception ("Le titre est trop long.");
    }

    // Enfin, si tous les tests sont passés...
    $this->title = $title;
}

D'accord, mais je tappe où tout ce code ? Maintenant votre model commencé, vous pouvez l'appeler depuis le controller :

Dans le fichier ArticlesController.php :

// Méthode de la route POST ajouter-articles
public function save() {

    // On créée une instance de notre model Article
    $article = new Article;
    $article->setTitle( $_POST['title'] );
}

Attention : Comme on parle de $_POST['title'], on admet ici que dans votre formulaire, il y ait un champ title bien sûr ! Sinon, adaptez ce code au votre ;)

Testez ce code en mettant trop ou pas assez de caractères pour voir si cela lève une erreur.

Exercice : Faites des setters cohérents pour tous les attributs du Model ! Il y a parfois des setters qui ne contiendront pas de validations, comme pour setId($id) qui ne doit contenir que $this->id = $id : comme cette donnée ne peut pas venir de nous ou de l'utilisateur, aucune raison de faire des validations dessus.

Liste des méthodes : getters

Là où les setters nous permettent d'enregistrer une valeur pour notre objet, le getter permet de récupérer une valeur de notre objet.

À quoi servent les getters, plutôt que de récupérer la donnée directement ? Les getters nous permettent de modifier la donnée brute de la base de données afin de l'adapter à nos besoins. Par exemple :

echo $article->created_at; // Affiche "2019-06-20 09:04:40"

Sans getter, la donnée brute ne nous conviendrait pas.

On peut créer dans le Model un getter pour created_at :

class Article {

    // ...

    /**
     * LISTE DES MÉTHODES : GETTERS
     */

    public function getCreatedAt() {

        $valeurBrute = $this->createdAt;
        $valeurTimestamp = strtotime($valeurBrute);

        $dateFr = date('d/m/Y', $valeurTimestamp);

        return $dateFr;
    }
} 

À l'usage :

echo $article->getCreatedAt(); // Affiche "20/06/2019"

Le getter nous permet donc d'adapter la donnée à nos besoins.

Comme les setters, nous allons faire des getters pour tous les attributs, bien que certains getters n'aient pas besoin de modifications ! Par exemple, l'id :

public function getId() {
    return $this->id;
}

Exercice : Faites des getters cohérents pour tous les attributs du Model.

Liste des méthodes : CRUD

à suivre

CORRECTION

La correction contient le model Article.php ainsi que les tests effectués dans la méthode save() de ArticlesController ( => donc pour tester, il faut aller dans le formulaire ajout-articles et le poster)

Fichier src/model/Article.php :

class Article {

    /**
     * LISTE DES ATTRIBUTS
     */

    // Il s'agit du nom de la table dans la base de données
    const TABLE_NAME = "articles";

    // il s'agit de la liste des champs de la table, mis en attributs :
    protected $id;
    protected $title;
    protected $content;
    protected $authorId;
    protected $createdAt;
    protected $updatedAt;

    public function setId($id) {
        $this->id = $id;
    }
    public function setTitle($title) {
        /**
         * Tests :
         */

        if (strlen($title) < 5) {
            throw new Exception('Le titre est trop court.');
        }

        if (strlen($title) > 255) {
            throw new Exception('Le titre est trop long.');
        }

        // Si les tests ne lancent pas d'erreurs, alors :
        $this->title = $title;
    }

    public function setContent($content) {
        /**
         * Tests :
         */

        if (strlen($content) < 5) {
            throw new Exception('Le contenu est trop court.');
        }

        // Si les tests ne lancent pas d'erreurs, alors :
        $this->content = $content;
    }

    public function setAuthorId($authorId) {
        /**
         * Tests :
         */

        if (!is_numeric($authorId)) {
            throw new Exception('\'author_id doit être un entier.');
        }
        
        // Si les tests ne lancent pas d'erreurs, alors :
        $this->authorId = $authorId;
    }

    public function setCreatedAt($createdAt) {

        /**
         * Tests :
         */

        // 1. On essaie de créer une date avec $createdAt
        $date = DateTime::createFromFormat('Y-m-d', $createdAt);

        // Si on n'y arrive pas (donc la $createdAt est à un mauvais format), alors $date retourne false.
        if (!$date) {
            throw new Exception('La date n\'est pas au format YYYY-MM-DD.');
        }

        // Si les tests ne lancent pas d'erreurs, alors :
        $this->createdAt = $createdAt;
    }

    public function setUpdatedAt($updatedAt) {
        /**
         * Tests :
         */

        // 1. On essaie de créer une date avec $createdAt
        $date = DateTime::createFromFormat('Y-m-d', $updatedAt);

        // Si on n'y arrive pas (donc la $createdAt est à un mauvais format), alors $date retourne false.
        if (!$date) {
            throw new Exception('La date n\'est pas au format YYYY-MM-DD.');
        }

        // Si les tests ne lancent pas d'erreurs, alors :
        $this->updatedAt = $updatedAt;
    }


    public function getId() {
        return $this->id;
    }

    /**
     * Getter pour retourner le titre avec la première lettre en majuscules systématiquement
     */
    public function getTitle() {
        $titleUppercase = ucfirst($this->title);
        return $titleUppercase;
    }

    public function getContent() {
        return $this->content;
    }

    public function getAuthorId() {
        return $this->authorId;
    }

    public function getCreatedAt() {
        return $this->createdAt;
    }

    /**
     * Getter pour retourner la date de création en français
     */
    public function getCreatedAtFr() {
        
        $timestamp = strtotime($this->createdAt);
        $dateFr = date('d/m/Y', $timestamp);

        return $dateFr;
    }

    public function getUpdatedAt() {
        return $this->updatedAt;
    }

    /**
     * Getter pour retourner la date de mise à jour en français
     */
    public function getUpdatedAtFr() {
        
        $timestamp = strtotime($this->updatedAt);
        $dateFr = date('d/m/Y', $timestamp);

        return $dateFr;
    }
}

Fichier ArticlesController.php :

<?php


class ArticlesController {

    public function add() {

        view('articles.add');
    }

    public function save() {

        $article = new Article;

        /*********************
         * TEST DES SETTERS
         * Décommentez les tests pour voir le résultat !
         ********************/

        // Test du setter setTitle() avec un nom trop court
        //$article->setTitle('a');

        // Test du setter setTitle() avec un nom trop long
        //$article->setTitle( 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Molestiae ratione, fugiat laboriosam tenetur tempora dolorem. Eos nihil, ipsam sequi iusto accusantium nostrum culpa natus eligendi quae, provident et quis odio.'); // Test avec un nom trop court

        // Test du setter setTitle() avec un nom correct
        $article->setTitle('Nouvel article de blog !');

        // Test du setter setContent() avec un contenu trop court
        //$article->setContent('a');

        // Test du setter setContent() avec un contenu correct
        $article->setContent( 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Molestiae ratione, fugiat laboriosam tenetur tempora dolorem. Eos nihil, ipsam sequi iusto accusantium nostrum culpa natus eligendi quae, provident et quis odio.');

        // Test du setter setAuthorId() avec un caractère string
        //$article->setAuthorId('hello');

        // Test du setter setAuthorId() avec un entier
        $article->setAuthorId(23);

        // Test du setter setCreatedAt() avec une date au mauvais format
        // $article->setCreatedAt('23 juillet 2019');
        // $article->setCreatedAt('23/07/2019');
        // $article->setCreatedAt('23-07-2019');

        // Test du settter setCreatedAt() avec une date au bon format
        $article->setCreatedAt('2019-07-23');

        // Test du setter setUpdatedAt() avec une date au mauvais format
        // $article->setUpdatedAt('23 juillet 2019');
        // $article->setUpdatedAt('23/07/2019');
        // $article->setUpdatedAt('23-07-2019');

        // Test du settter setUpdatedAt() avec une date au bon format
        $article->setUpdatedAt('2019-07-23');

        // On visualise à quoi ressemble $article :
        dump($article);

        /********************
         * TEST DES GETTERS
         ********************/

        echo "Test de getId() : ";
        dump($article->getId());

        echo "Test de getTitle() : ";
        dump($article->getTitle());

        echo "Test de getContent() : ";
        dump($article->getContent());

        echo "Test de getAuthorId() : ";
        dump($article->getAuthorId());

        echo "Test de getCreatedAt() : ";
        dump($article->getCreatedAt());

        echo "Test de getCreatedAtFr() : ";
        dump($article->getCreatedAtFr());

        echo "Test de getUpdatedAt() : ";
        dump($article->getUpdatedAt());

        echo "Test de getUpdatedAtFr() : ";
        dump($article->getUpdatedAtFr());


    }

    public function show() {

        echo "Affichage de l'article";
    }
}

EXERCICE : Ajouter des méthodes CRUD dans le Model

Ajout de save()

En plus des setters et getters qui nous permettent de nous assurer du format de donnée (à l'enregistrment et à la récupération), le Model possède des fonctions communes à tous les Models, qui nous permettent de bien enregistrer l'objet en base de données : save(), update(), delete().

On pourrait faire quelque chose comme cela :

class Article {

    // ...

    public function save() {

        $bdd = new PDO('...');

        $request = 'INSERT INTO Articles (title, content, author_id)
                    VALUES (:title, :content, :author_id)';

        $response = $bdd->prepare($request);

        $response->execute([
            'title'     => $this->title,
            'content'   => $this->content,
            'author_id' => $this->authorId
        ]);
    }
}

Le problème de cette méthode, c'est que nous devons rapeller $bdd, saisir une requête SQL à chaque fois que nous avons un nouveau Model.

Nous avons déjà les informations variables pour le modèle: le nom de la table (la constante TABLE_NAME) et la liste des attributs.

On peut utiliser une librairie qui nous permettra de générer des requêtes automatiquement grâce à ces données : la classe Db qui se trouve dans le dossier config. Cette classe contient déjà toutes les requêtes déjà prêtes à l'usage pour communiquer avec la base de données.

Pour que notre Model puisse utiliser les méthodes de la classe Db, nous allons utiliser le concept d'héritage : notre classe Article va hériter de la classe Db, et pouvoir utiliser les méthodes qui y sont présentes.

Héritage

Pour faire un héritage, on utilise le mot clé extends :

Fichier Article.php :

class Article extends Db {

}

Utilisation des méthodes de Db dans Article

Vous pouvez jeter un oeil à Db.php afin de voir comment les méthodes sont écrites. Il n'est pas nécessaire de comprendre tout le fichier pour utiliser la classe !

Créons la méthode save() pour faire en sorte que l'article s'enregistre en base de données. Cette méthode va utiliser la méthode dbCreate() venant de la classe Db dont on a hérité.

Fichier Article.php :

class Article extends Db {

    // ...

    /**
     * LISTE DES MÉTHODES : CRUD
     */

    public function save() {

        /** 1. On prépare notre tableau de données à enregistrer :
         * en clé, les champs de la table en base de données,
         * en valeur, les valeurs de l'objet lui même (donc avec... les getters !)
         */

        $data = [
            "title"     => $this->getTitle(),
            "content"   => $this->getContent(),
            "author_id" => $this->getAuthorId(),
        ];

        /**
         *  2. On utilise la méthode dbCreate() de la classe parente Db.
         * Comme on en hérite, on peut l'utiliser comme une méthode de Article,
         * c'est à dire avec $this->...
         * La méthode dbCreate() retourne l'ID de l'élément créé.
         */

        $id = $this->dbCreate(self::TABLE_NAME, $data);

        /**
         * 3. L'élément étant créé, on enregistre son ID dans l'objet :
         */

        $this->id = $id;

        // Enfin, on retourne l'objet lui même (quitte à retourner quelque chose, on peut retourner l'objet lui même si on ne sait pas quoi retourner).
        return $this;
    }
}

Si tout s'est bien passé : refaites l'exercice avec une table Commentaire par exemple !

CORRECTION :

class Article extends Db {

    const TABLE_NAME = "articles";

    protected $id;
    protected $title;
    protected $content;
    protected $authorId;
    protected $createdAt;
    protected $updatedAt;

    public function setId($id) {
        $this->id = $id;
    }
    public function setTitle($title) {
        if (strlen($title) < 5) {
            throw new Exception('Le titre est trop court.');
        }

        if (strlen($title) > 255) {
            throw new Exception('Le titre est trop long.');
        }

        $this->title = $title;
    }

    public function setContent($content) {
        if (strlen($content) < 5) {
            throw new Exception('Le contenu est trop court.');
        }

        $this->content = $content;
    }

    public function setAuthorId($authorId) {
        if (!is_numeric($authorId)) {
            throw new Exception('\'author_id doit être un entier.');
        }

        $this->authorId = $authorId;
    }

    public function setCreatedAt($createdAt) {
        $date = DateTime::createFromFormat('Y-m-d', $createdAt);

        if (!$date) {
            throw new Exception('La date n\'est pas au format YYYY-MM-DD.');
        }

        $this->createdAt = $createdAt;
    }

    public function setUpdatedAt($updatedAt) {
        $date = DateTime::createFromFormat('Y-m-d', $updatedAt);

        if (!$date) {
            throw new Exception('La date n\'est pas au format YYYY-MM-DD.');
        }

        $this->updatedAt = $updatedAt;
    }


    public function getId() {
        return $this->id;
    }

    public function getTitle() {
        $titleUppercase = ucfirst($this->title);
        return $titleUppercase;
    }

    public function getContent() {
        return $this->content;
    }

    public function getAuthorId() {
        return $this->authorId;
    }

    public function getCreatedAt() {
        return $this->createdAt;
    }

    public function getCreatedAtFr() {
        $timestamp = strtotime($this->createdAt);
        $dateFr = date('d/m/Y', $timestamp);

        return $dateFr;
    }

    public function getUpdatedAt() {
        return $this->updatedAt;
    }

    public function getUpdatedAtFr() {

        $timestamp = strtotime($this->updatedAt);
        $dateFr = date('d/m/Y', $timestamp);

        return $dateFr;
    }

    /**
     * LISTE DES SETTERS : METHODES CRUD
     */

    public function save() {

        $data = [
            "title"     => $this->getTitle(),
            "content"   => $this->getContent(),
            "author_id" => $this->getAuthorId(),
        ];

        $id = $this->dbCreate(self::TABLE_NAME, $data);
        $this->id = $id;

        return $this;
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment