MVC et Framework

MVC

MVC c'est quoi ?

Le Model View Controller est un design pattern de conception architecturale.

Le MVC est basé sur la séparation des responsabilités au sein d'une application.

Le MVC entend donc séparer une application en 3 parties distincts, le Model, la View et le Controller.

À quoi ça sert ?

MVC permet de mieux concevoir votre application en la découpant clairement et en réunissant les actions similaires.

Cela vous permet de rendre votre code facilement maintenable, facilement testable, facilement modifiable.

Comment ça marche ?

L'application est découpée en 3 composants, et chaque composant ne peut discuter qu'avec son voisin.

Controller

Model

Template

Les différentes parties

Le Controller :

C'est le chef d'orchestre. Il contrôle la logique de l'application, assure la cohérence, choisi qui dois faire quoi.

Le Model :

C'est lui qui parle avec la base de données. Il effectue la requête et retourne les résultats.

La View :

C'est elle qui est chargée d'afficher les données.

Petits bonus dont

personne ne parle

Les "contrôleurs internes" :

Effectuent une forme de traitement qui pourrait être effectué dans un controller, mais qui sera effectué plusieurs fois. C'est une passerelle Controller <-> Model.

Le Router :

C'est lui qui est chargé d'interpréter la requête pour savoir quel controller sera appelé.

Framework

Un framework, c'est quoi ?

Le framework est un cadre de développement qui dois nous aider à concevoir un logiciel en :

1. Définissant un standard au sein de l'industrie.

2. Imposant une organisation du code.

3. Proposant des outils pour réaliser des tâches "classiques".

Les frameworks en PHP

Il existe des tas de frameworks PHP, mais certains sont plus connus et reconnus que d'autres.

Framework & MVC

Presque tous les frameworks web PHP sont orientés MVC, presque tous avec leurs particularités.

On retrouvera dans ces frameworks les points communs suivants :

1. Un système de Router.

2. Un système d'ORM.

3. Un système de Controller.

4. Un système de Templating.

Descartes

Dans ce cours

Nous allons utiliser Descartes, un micro-framework PHP maison orienté MVC.

Pourquoi ?

- Léger.

- Simple a maîtriser.

- Full PHP.

- Zéro magie, donc il faut tout comprendre.

- Beaucoup moins spécifique qu'un gros framework.

Installer Descartes

Première étape, nous allons installer Descartes.

Pour ça, on va directement télécharger Descartes depuis le Github et l'installer dans le dossier /var/www/html.

cd /var/www/html
wget https://github.com/RaspbianFrance/descartes/archive/master.zip
unzip ./master.zip
rm ./master.zip
mv descartes-master descartes
rm -rf descartes/.git

Utiliser Composer

Deuxième étape, nous allons installer les dépendances nécessaires avec Composer.

Composer est un gestionnaire de dépendances PHP. Descartes utilise Composer, nous devons initialiser le projet. Pour cela, on va dans le dossier et on fait un composer install.

apt update && apt install composer -y
composer install

Activer les .htaccess

Troisième étape, nous allons activer le support des .htaccess par Apache pour pouvoir rediriger toutes les requêtes vers index.php

Configurer Descartes

Première étape, nous allons configurer Descartes.

La configuration de Descartes est stockée en plusieurs fichiers sous la forme de tableaux PHP :

- env.php, contient la conf généraliste de l'application

- env.descartes.php, contient la conf généraliste pour override celle de Descartes

- env.<ENV>.php, ou <ENV> correspond à la constante PHP "ENV" (vous devez donc la définir dans env.php).

Configurer Descartes

Dans notre cas, nous allons donc remplir les deux fichiers suivants :

<?php
    $env = [ 
        'ENV' => 'dev',
        'SESSION_NAME' => 'descartes',
    ];

env.php

<?php
    $env = [ 
        'DATABASE_HOST' => 'your_host',
        'DATABASE_NAME' => 'your_db',
        'DATABASE_USER' => 'your_user',
        'DATABASE_PASSWORD' => 'your_password',
    ];  

env.dev.php

Configurer Descartes

Petit truc spécifique. Si votre application est accessible en ligne via un dossier, càd qu'on y accède via example.fr/dossier_app/ plutôt que example.fr

Dans ce cas vous devez modifier la variable $http_dir_path dans descartes/env.php pour y mettre "/dossier_app".

Les différentes parties

L'organisation de Descartes

├── console.php    ------> L'entrée vers les scripts console du framework
├── controllers    ------> Le dossier des controlleurs
│   ├── internals  ------> Le dossier des controlleurs internes (code métier ré-utilisé) (hérite de \InternalController)
│   └── publics    ------> Le dossier des controlleurs publiques (qui contiennent les fonctions correspondants aux pages de l'appli)(hérite de \Controller)
├── descartes      ------> Le dossier qui contient les sources du framework. Pas toucher !
├── env.php        ------> Le fichier de conf généraliste
├── index.php      ------> L'entrée vers les scripts web du framework
├── models         ------> Le dossier des modèles (code qui interagit avec la bdd) (hérite de \Model)
├── routes.php     ------> Le fichier qui contient les routes de l'appli, càd les url du site
├── templates      ------> Le dossier des templates
├── tests          ------> Le dossier des tests PHPUnit
│   ├── bootstrap.php
│   └── controllers
│       ├── internals
│       └── publics

Comment ça marche ?

Avec un Framework MVC, chaque page de votre application va correspondre dans le code à une méthode d'un objet.

Pour exemple, si je veux créer deux page, une pour me connecter et une pour m'inscrire, je pourrais avoir un objet Registration, avec une méthode login et une méthode register.

Comment sait-on quelle page appeler ?

Tout le code étant séparé en fonctions, et plus en pages .php toutes simples, comment le serveur sait-il quelle fonction appeler ?

En effet, sur une application classique, chaque page du site est un fichier différent, et dans l'url on demande directement ce fichier (ex : index.php, admin.php, login.php, etc.).

Dans notre cas, c'est plus compliqué, le code est réparti entre plusieurs fichiers. Alors comment fait-on ?

Comment sait-on quelle page appeler ?

En fait, c'est le Framework qui s'en occupe à la place du serveur.

Quand on reçoit une requête (sauf ressource .js, .css, .jpg, etc.), quelle que soit la page, on dit au serveur de renvoyer tout le trafic vers le fichier index.php. Cela est fait par le fichier .htaccess à la racine du site.

De son coté, le fichier index.php va pouvoir savoir quelle adresse a été demandée à l'origine. Il va alors la transmettre au Routeur.

Comment sait-on quelle page appeler ?

De son coté le Routeur va analyser cette adresse, et chercher dans son tableau de routes si une adresse correspondant existe.

Une route, c'est assez simple, ça dit "l'URL machin, elle correspond au Controleur Truc et à sa méthode chose".

Quand le Routeur trouve une route qui correspond à l'URL demandé, il va construire le contrôleur lié et appeler la méthode qu'il faut, éventuellement en lui passant des arguments si la route utilisait des arguments.

Découper son code

Le principe de base d'un framework MVC, c'est de découper notre code. Chaque page est une fonction, mais allons plus loin !

On va séparer notre code en 4 parties :

- Le code "publique", il vérifie qu'on a bien les données qu'il faut, et il dit ce qui va se passer.

- Le code "interne", il sait comment ça va se passer.

- Le code "modèle", il s'occupe de discuter avec la base de données.

- Le code "template", il va afficher le résultat à l'utilisateur.

Découper son code

Imaginons une page avec un formulaire de connexion.

Découper son code

Comment notre requête a-t-elle parcouru notre application ?

Créer une application simple : un raccourcisseur d'URL

Ajouter une bdd

Maintenant, nous allons créer une base pour notre projet.

Pour cela, nous allons créer un fichier create_database.sql qui contiendra le script de création de notre base.

CREATE DATABASE IF NOT EXISTS short;
use short;

-- Create table of exemple
CREATE TABLE IF NOT EXISTS short
(
    id INT NOT NULL AUTO_INCREMENT,
    uid VARCHAR(255) NOT NULL,
    url VARCHAR(1000) NOT NULL,
    PRIMARY KEY (id)
);

Puis on créer la base avec la commande suivante :

mysql -u root -p < create_database.sql

Créer notre première route

Pour créer une page avec un framework MVC, il faut au minimum : une route, un contrôleur, et un template.

La première étape sera donc de créer une route. Pour cela, on l'ajoute au tableau $routes dans routes.php.

Une route c'est : un contrôleur, une méthode, une url, possiblement des arguments.

<?php
    $routes = array(
        'Short' => [
            'home' => '/',
        ],
    );  

    define('ROUTES', $routes);

Créer notre Controller

Maintenant que nous avons une route, nous allons créer le contrôleur correspondant /controllers/publics/Short.php.

<?php
namespace controllers\publics;

class Short extends \Controller
{
    /** 
     * Home Page
     */ 
    public function home()
    {   
        return $this->render("short/home");
    }   
}

Dans un contrôleur, chaque méthode représente une page et retourne une chaîne HTML.

Notre fonction va juste retourner un template via render.

Créer notre template

Il ne nous reste plus qu'à créer nos différents templates.

<?php include(PWD_TEMPLATES . '/incs/head.php'); ?>

<div class="container">
    <div class="row" style="margin-top: 35vh">
        <h1>Make it Short !</h1>
    </div>
    <div class="row mt-2">
        <form action="" method="GET" class="col-12">
            <div class="form-group row">
                <input type="url" class="form-control form-control-lg col-10 mr-2" id="url" name="url" placeholder="L'URL que vous voulez réduire." />
                <button class="btn btn-info btn-lg">Réduire l'URL</button>
            </div>
        </form>
    </div>
</div>

<?php include(PWD_TEMPLATES . '/incs/footer.php'); ?>
<!DOCTYPE html>
<html>
    <head>
        <title>Short URL</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
        <link rel="stylesheet" href="https://bootswatch.com/4/darkly/bootstrap.min.css">
    </head>
    <body>
    </body>
</html>

templates/incs/head.php

templates/short/home.php

templates/incs/footer.php

Ajouter le traitement du lien

Première étape, on va créer une url /minify et sa route, ainsi que sa méthode dans le controller Short.

<?php

namespace controllers\publics;

class Short extends \Controller
{
   ...

    public function minify ()
    {
        echo 'Minify';
    }
}
<?php
    $routes = array(
        'Short' => [
            'home' => '/',
            'minify' => '/minify/',
        ],

    );  

    define('ROUTES', $routes);

Ajouter le traitement du lien

Deuxième étape, on va créer un modèle Short qui permet d'insérer un raccourci, d'en récupérer un par son url, ou par son uid.

<?php

namespace models;

class Short extends \Model
{
    public function get_one_by_url ($url)
    {   
        return $this->select_one('short', ['url' => $url]);
    }   
    
    public function get_one_by_uid ($uid)
    {   
        return $this->select_one('short', ['uid' => $uid]);
    }   

    public function create ($url, $uid)
    {   
        return $this->insert('short', ['url' => $url, 'uid' => $uid]);
    }   
}

models/Short.php

Ajouter le traitement du lien

Troisième étape, on va créer un controlleur interne Short avec une méthode qui permette minifier une url, càd générer un uid et insérer via le model Short.

namespace controllers\internals;

use \models\Short as ModelShort;

class Short extends \InternalController
{
    public function __construct(\PDO $pdo)
    {   
        $this->model_short = new ModelShort($pdo);
    }   

    public function minify ($url)
    {   
        $short = $this->model_short->get_one_by_url($url);

        if ($short)
        {
            return $short['uid'];
        }

        $uid = str_replace('=', '', strtr(base64_encode(random_bytes(4)), '+/', '-_'));

        $this->model_short->create($url, $uid);
        return $uid;
    }
}

controllers/internals/Short.php

Note : on utilise le constructeur pour récupéré le paramètre $pdo, que l'on utilise pour construire le Model Short.

Ajouter le traitement du lien

On modifie le controlleur publique pour récupérer la connexion à la base, instancier le controlleur interne, et effectuer le traitement sur l'url transmise en POST à minify.

<?php

namespace controllers\publics;
use \controllers\internals\Short as InternalShort;

class Short extends \Controller
{

    public function __construct()
    {
        $pdo = \Model::connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
        $this->internal_short = new InternalShort($pdo);
    }

    //...

    public function minify ()
    {
        $url = $_POST['url'] ?? false;

        if (!$url || !filter_var($url, FILTER_VALIDATE_URL))
        {
            return $this->render('short/minify', ['success' => false]);
        }

        $uid = $this->internal_short->minify($url);

        if (!$uid)
        {
            return $this->render('short/minify', ['success' => false]);
        }

        return $this->render('short/minify', ['success' => true, 'url' => $url, 'uid' => $uid]);
    }

controllers/publics/Short.php

Note : par défaut le framework passe la connexion à la base en argument lors de la construction d'un controleur publique.

Ajouter le traitement du lien

On met à jour les templates et on rajoute un template pour minify.

<?php include(PWD_TEMPLATES . '/incs/head.php'); ?>

<div class="container">
    <?php if (!$success) { ?>
        <h1 style="margin-top: 35vh;" class="mb-3">Impossible de minifier cette URL !</h1>
    <?php } else { ?>
            <h1 style="margin-top: 35vh;" class="mb-3">L'url a bien été minifiée !</h1>
            <h3>
                <a href="<?php $this->s(Router::url('Short', 'develop', ['uid' => $uid])); ?>">
                    <?php $this->s(Router::url('Short', 'develop', ['uid' => $uid])); ?>
                </a>
            </h3>
        </div>
    <?php } ?>
</div>

<?php include(PWD_TEMPLATES . '/incs/footer.php'); ?>

templates/short/minify.php

<?php include(PWD_TEMPLATES . '/incs/head.php'); ?>

<div class="container">
    <div class="row" style="margin-top: 35vh">
        <h1>Make it Short !</h1>
    </div>
    <div class="row mt-2">
        <form action="<?= Router::url('Short', 'minify'); ?>" method="POST" class="col-12">
            <div class="form-group row">
                <input type="url" class="form-control form-control-lg col-10 mr-2" id="url" name="url" placeholder="L'URL que vous voulez réduire." />
                <button class="btn btn-info btn-lg">Réduire l'URL</button>
            </div>
        </form>
    </div>
</div>

<?php include(PWD_TEMPLATES . '/incs/footer.php'); ?>

templates/short/home.php

Ajouter la redirection

Première étape, on va créer une route qui reçoit en argument dans l'url l'uid d'un raccourci, ainsi que la méthode associée dans Short qui redirige vers le raccourci.

Pour passer un argument, il suffit de mettre son nom en "{}" dans la route, et d'ajouter un argument dans la signature de la méthode.

PS : On peut utiliser des arguments optionnels et multiplier les routes en omettant des arguments si besoin.

Ajouter la redirection

routes.php

controllers/publics/Short.php

<?php
    $routes = array(
        'Short' => [
            'home' => '/',
            'minify' => '/minify',
            'develop' => '/r/{uid}',
        ],

    );  

    define('ROUTES', $routes);
namespace controllers\publics;
use \controllers\internals\Short as InternalShort;
use \models\Short as ModelShort;

class Short extends \Controller
{
    ...
    
    public function develop (string $uid)
    {   
        $url = $this->internal_short->develop($uid);

        if (!$url)
        {
            return $this->render('short/develop', ['success' => false]);
        }

        header('Location: ' . $url);
        return false;
    }
}

Ajouter la redirection

On ajoute une fonction develop dans le controleur interne Short, qui récupère l'URL de redirection depuis un uid.

controllers/internals/Short.php

<?php
namespace controllers\internals;

use \models\Short as ModelShort;

class Short extends \InternalController
{
    ...
    
    public function develop ($uid)
    {   
        $short = $this->model_short->get_one_by_uid($uid);

        if (!$short)
        {
            return false;
        }

        return $short['url'];
    }   
}

Ajouter la redirection

On ajoute le template qui affiche une erreur sur un mauvais raccourci.

templates/short/develop.php

<?php include(PWD_TEMPLATES . '/incs/head.php'); ?>

<div class="container">
    <?php if (!$success) { ?>
        <h1 style="margin-top: 35vh;" class="mb-3">Désolé, cette page n'existe pas !</h1>
    <?php } ?>
</div>

<?php include(PWD_TEMPLATES . '/incs/footer.php'); ?>

Aller plus loin

Utiliser un script console

Descartes vous permet d’exécuter n'importe quelle méthode public d'un contrôleur interne ou public, en lui passant potentiellement des arguments.

Pour cela, on utilise le script console.php, comme ci-dessous.

php console.php controllers/internals/Example method_to_cal --arg1="some value to pass"

Utiliser un script console

Par exemple, nous pouvons créer une méthode delete_old_shorts dans le contrôleur interne Short, qui va supprimer tous les liens trop vieux.

php console.php controllers/internals/Short delete_olds_shorts

Pour appeler cette méthode, il nous suffira alors de lancer la commande suivante :

Le code du projet Short

Question ?

MVC

By plebweb

MVC

  • 1,010