La séparation UI/Rules en PHP pur.

Dans ce bonus, je vais vous montrer comment j’ai réussi à réorganiser un spaghetti code PHP de première classe en un projet bien huilé grâce à la séparation UI/Rules.

Mais avant ça, contexte :

Le but est de faire une application qui permet de passer une commande sur une chaîne de fast-food fictive (appelons la Burgies & Sam). L’application sera divisée en 2 pages : la page de la commande où on pourra choisir nos casse-croûtes (order.php), et une page où on pourra voir tout ce qu’on a commandé (payement.php).

order.php
payement.php

Architecture

Comme toute bonne expérience, il me fallait d’abord un sujet témoin afin de pouvoir comparer avec le résultat final. J’ai donc fais cette application PHP de la manière la plus simple possible, sans aucune séparation UI/Rules. Vous pourrez la retrouver dans le répertoire /without du code source téléchargeable en bas de page. Dans cette version de l’application, tout le code de order.php tient dans un seul fichier, et idem pour payement.php.

Maintenant voilà à quoi ça ressemble sur la version avec séparation UI/Rules :

order.php

Vous remarquerez que ça fait beaucoup de fichiers, mais l’intérêt, c’est qu’à présent, toutes les petites choses sont à leur place : on a une partie dédiée au design, aux modèles, aux contrôleurs, aux scripts métiers, et même aux classes diverses. Bref, c’est un modèle qui est très bien rangé et facilement extensible.

Ca peut vous sembler overkill pour une petite application de 2 pages comme celle-ci (et ça l’est). Mais imaginez maintenant que Burgies & Sam soit une immense multinationale, et que leur application fasse 140 pages. J’estime qu’en plus d’avoir un code beaucoup plus propre, vous allez mettre en moyenne 3 fois moins de temps pour tout programmer en utilisant ma méthode plutôt que de “faire au plus simple” (ce qui n’est plus vrai sur une si grosse application).

N’oubliez pas que plus votre application aura de fonctionnalités, plus la séparation UI/Rules va vous faire gagner en temps et en rigueur.

Maintenant, voyons voir comment ça se présente de plus prés.

Répartition des fichiers

Une bonne architecture, ça commence déjà par une bonne organisation des fichiers dans l’arborescence. Dans mon modèle, j’ai séparé mes fichiers dans plusieurs répertoires :

  • / : contiens tous les contrôleurs, qui sont les seuls fichiers que vous allez appeler depuis votre navigateur. On aurait également pu les placer dans un répertoire /public pour faire plus propre).
  • /assets : les ressources à inclure dans vos vues : CSS, images, JS…
  • /src : c’est ici que l’on va ranger toutes les classes qui relèvent du cœur de l’application : modèle, règles métiers, classes système...
  • /templates : le répertoire qui va contenir toutes nos vues.

Donc grâce à cette architecture, on va avoir nos règles dans /src, notre interface dans /templates, et l’utilisateur accédera uniquement aux fichiers à la racine.

Les plus cultivés d’entre vous reconnaîtront le système de fichiers de Symfony, qui est à peu prés le même et que j’aime beaucoup.

Template

Une chose que l’on voit souvent sur les frameworks de séparation UI/Rules, c’est l’utilisation d’un système de templates (aussi appelés layouts), qui va nous servir à diviser notre code HTML et à recycler les parties identiques comme des fonctions !

Le principe est que l’on va faire un modèle de page, et toutes nos pages seront en fait des variations de ce modèle.

Pour faire ça en PHP pur, j’ai créé une classe appelée TemplateEngine, et un modèle de page /template/default.php. Celui-ci va venir exploiter une instance de TemplateEngine, appelée $template, pour récupérer du HTML venant d’autres vues pour pouvoir composer la page finale comme un puzzle.

Je pense qu’une image vaut mieux qu’un long discours.

On créer une variable TemplateEngine $template qui va être utilisée dans default.php pour appeler les autres vues.

Événements

Pour les événements, je ne me suis pas embêté à faire un système d’API REST. C’est pas bien, mais ça ne m’a pas empêché de séparer ce qui relève de la règle et ce qui relève de l’événement.

Ainsi, la vraie règle est déléguée dans le script /src/Scripts/Order.js sous la forme d’une classe métier, et notre événement ne fait que récupérer des données et modifier l’interface.

// Classe métier JavaScript
function Order(money) 
{
    this.money = money;
    this.items = [];

    this.orderItem = function(item, price) 
    {
        if (price <= this.money) 
        {
            this.items.push(item);
            this.money -= price;
            return true;
        } 
        else 
        {
            return false;
        }
    }
}
Classe métier JavaScript
let order = new Order(2000);

function placeOrder(item, price) 
{
    if(order.orderItem(item, price)) 
    {
        document.querySelector('#order').value = JSON.stringify(order.items);
        document.querySelector('#money-left').textContent = order.money / 100;
        document.querySelector('#no-money-left').textContent = "";
    } 
    else 
    {
        document.querySelector('#no-money-left').textContent = "Pas assez d'argent !";
    }
}
Événements JavaScript

Contrôleur

Sans séparation UI/Rules, on se retrouve avec un fichier qui contient un peu tout et n’importe quoi. Ici, notre contrôleur (le fichier principal) ne contient plus que du PHP très trivial, qui va juste servir à relier les différents éléments de notre architecture, et ainsi boucler la boucle.

<?php
// Importation des classes
include "src/Classes/TemplateEngine.php";
include "src/Entities/OrderItem.php";

// Initialisation des données
$orderItems = 
[
    new OrderItem(1, 'Baby Burger', 'baby-burger.png', 450),
    new OrderItem(2, 'Maxi Burger', 'maxi-burger.png', 700),
    new OrderItem(3, 'Good\'old French Fries', 'fries.png', 250),
    new OrderItem(4, 'Tacos Deluxe', 'tacos.png', 550),
    new OrderItem(5, 'Burritozilla', 'burrito.png', 1600),
    new OrderItem(6, 'Salad', 'salad.png', 450)
];

// Création du template
$template = new TemplateEngine();
$template->title = "Big Food";
$template->body = "templates/order/body.php";
$template->scripts = "templates/order/scripts.php";

// Appel du layout
include "templates/default.php";
?>
Le contrôleur order.php

Il y a encore quelques petites choses que je pourrais raconter sur cette application, mais honnêtement, le mieux est que vous regardiez par vous-même.

Les sources sont disponibles ici, mais d’abord, lisez bien les consignes d’installation ci-dessous.

L’archive contient 2 répertoires :

  • /without qui contient l’application sans séparation UI/Rules.
  • /with qui contient l’application telle que je vous l’ai décrite ci-dessus.

Bien sûr, les deux sont indépendants et produisent exactement le même résultat.

IMPORTANT : dans la version /with, TemplateEngine.php possède deux define() qui contiennent le chemin absolut de l’application. Il faut que vous changiez ces deux lignes pour qu’elles correspondent à votre configuration.

Exemples :

define("ROOT_PATH", "D:\\xampp\\htdocs\\itexpert-separation-ui-rules\\with\\");
define("EMPTY_PATH", "D:\\xampp\\htdocs\\itexpert-separation-ui-rules\\with\\templates\\empty.php");
L’application est installée dans D:\xampp\htdocs\itexpert-separation-ui-rules-with\
define("ROOT_PATH", "C:\\wamp\\www\\test\\");
define("EMPTY_PATH", "C:\\wamp\\www\\test\\templates\\empty.php");
L’application est installée dans C:\wamp\www\test\