Développement de ce site : Étape 2 - Le rendu des pages avec le micro-framework Plates PHP

Dans ce second billet, j'aborde la mécanique générale du site à savoir la gestion des pages à l'aide du micro-framework Plates PHP et plus précisemment la génération du code HTML ainsi que son organisation.

Ce billet est le second opus d'une serie, retrouvez ici le billet précédent.

Inspiré par Twig et développé par la célèbre team thephpleague, Plates PHP est un micro-framework (une seule dépendance) qui se concentre essentiellement sur le processus de rendu en PHP natif. Cependant, il ne gère aucune mécanique de Dispatching/Routing ou plutôt laisse libre le développeur de choisir comment gérer ses routes dans le cas d'un site avec plusieurs pages. On abordera ce point dans un autre billet.

Architecture du site Internet

Pour rappel, le site s'articule autour de quelques pages et d'une partie "blog" que l'on pourrait résumer ainsi, j'indique déjà les routes que j'imagine pour mes différentes pages :

Le fonctionnement serveur d'une requête peut donc se résumer aux quatres actions suivantes :

Structure du projet

Coté code, la structure du projet est assez simple et peut-être adapté en fonction du projet.

Installation, initialisation & 1er test

L'installation du framework se fait par l'incontournable outil de gestion des dépendances pour PHP : Composer.
Après avoir initialisé le projet à l'aide de la commande composer init, on indique la dépendance à installer à l'aide de la commande composer require league/plates.

On se retrouve avec un fichier de configuration à peu près comme ci-dessous auquel j'ai ajouté l'autoload sur le dossier includes afin d'étendre facilement le framework en créeant des helpers ou d'autres codes réutilisables.

Fichier composer.json
{
    "name": "PersoWebSite",
    "description": "Site Web Perso",
    "authors": [
        {
            "name": "Ohayon",
            "email": "adresse@mail.fr"
        }
    ],
    "require": {
        "league/plates": "3.*"
    },
    "autoload": {
        "psr-4": {
            "PersoWebSite\\": "includes"
        }
    },
}

Ensuite, il suffit de charger l'autoload dans le fichier index.php qui sert de point d'entrée au site internet et d'instancier un objet Engine.
Le premier test que je souhaite faire est assez simple : afficher une vue rendue par le moteur de template avec passage de la variablehello.

Fichier index.php
<?php 
    require 'vendor/autoload.php';
	
    setlocale(LC_ALL, "fr_FR.utf8");

    $template = new League\Plates\Engine();

    $template->addFolder('views', 'templates/views');

    $datas = [
        'hello' => 'world'
    ];

    $template->addData($datas);

    echo $template->render("views::test");
?>
Fichier de vue templates/view/test.php
<html lang="fr">
    <body>
        <span><?= $hello ?></span>
    </body>
</html>

Pour tester, il suffit d'appeler la page index.php avec son navigateur et normalement la page affiche bien le contenu de la variable hello: "world".

La mécanique de rendu : le layout, les vues et les élements

Le principe est simple et se retrouve chez tous les frameworks de templating moderne.
Une page Web ou un document au sens large se compose souvent d'une entête / corps / pied de page et plus généralement d'éléments que l'on souhaite "moduler" pour maximiser la réutilisation de code ou plutôt minimiser la redondance de code similaire.

Le layout c'est le contour de la page web, généralement les entêtes au sens HTML (head) et les éléments graphiques qui sont communs dans le site comme par exemple le logo, les menus, le pied de page... Dans le cas d'une application avec un front office et un back office, cela peut se gérer avec deux layouts qui sont chargés en fonction du contexte.

La vue c'est précisement une page, elle est incluse dans le layout par la mécanique de rendu HTML de Plates.

Les éléments ce sont des petits morceaux de HTML que l'on se sert plusieurs fois dans le site. Ils peuvent être appelés directement depuis le layout ou la vue et permettent également de bien structurer son code.

Un petit schéma pour résumer l'imbrication des différentes parties qui composent une page web classique.

Remarques :

Côté codes

Coté Plates, on va tout d'abord déclarer les dossiers pour les layouts et les élements, j'ai rajouté le passage d'une variable siteTitle qui sera utilisée dans la vue (titre h1) et dans le layout (head title).

Fichier index.php
<?php 
    // ...
    $template->addFolder('views', 'templates/views');
    $template->addFolder('layouts', 'templates/layouts');
    $template->addFolder('elements', 'templates/elements');

    $datas = [
		'hello' => 'world',
		'siteTitle' => 'Site Perso',
        'mail' => 'contact@mail.fr'
	];

    echo $template->render("views::test");

Dans la vue, on indique le layout à utiliser, dans notre cas, il n'y en a qu'un seul que l'on a appelé default. Il est possible de passer des paramètres lors de la définition du layout, voir de surcharger ceux qui existent déjà ; comme par exemple ci dessous la variable siteTitle est dérivée pour le title et la méta-description du layout.

Fichier de vue templates/view/test.php
<?php 
    $this->layout('layouts::default', [
        'siteTitle' => "{$siteTitle} - Accueil",
        'metaDescription' => "{$siteTitle}. Réalisation de projets de développement web..."
    ]);
?>

<h1><?= $siteTitle ?></h1>
<p>
    <?= $hello ?>
</p>

Dans le layout, on utilise les différentes fonctions du framework pour écrire/inclure :

Fichier de layout templates/layout/default.php
<!doctype html>
<html lang="fr">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta http-equiv="content-language" content="fr">
        <title><?= $this->e($siteTitle) ?></title>
    </head>
    <body>
        <div id="header">

        </div>
        <div id="mainContainer" class="container">
            <?= $this->section('content') ?>
        </div>

        <div id="footer">
            <?= $this->insert('elements::mail') ?>
        </div>
    </body>
</html>

Et enfin, l'élement mail qui certes n'est pas très pertinent, permet pour l'exemple de comprendre comment il s'intègre. Il est possible de surcharger la variable $mail à l'insertion de l'élement à travers un tableau de paramètres.

Fichier d'élément mail templates/elements/mail.php
<span><?= $mail ?></span>

Il est également prévu par le framework des concepts plus avancés pour la gestion du contenus commes les sections permettant de gérer un peu à la manière d'un flux le code à écrire. C'est d'ailleurs comme ça que la vue principale est incluse dans le layout.

Limites & Helper à la rescousse

Cependant, on atteint vite les limites du framework en terme de fonctions dans les vues pour produire du HTML. Je n'ai rien trouvé de très convainquant pour gérer des problématiques simples comme : fabriquer des liens pour les images, pour les pages entres elles ou encore les ressources statiques (CSS/JS/FONT).

On va donc se créer un "helper" maison dans le dossier includes qui va se charger pour le moment de faire les liens pour les fichiers CSS et les images placés respectivement dans les dossiers assets/css et assets/img.
On notera l'utilisation d'une variable de classe $baseURL calculée à la construction à partir des paramètres de la requête.

Fichier de helper includes/HtmlHelper.php
namespace PersoWebSite;

class HtmlHelper {
    protected $baseURL;

    public function  __construct() {
        $this->baseURL = $_SERVER["REQUEST_SCHEME"]."://".$_SERVER['SERVER_NAME'];
    }

    public function cssURL($cssFile) {
        return $this->baseURL."/assets/css/".$filename;
    }

    public function imgURL($filename) {
        return $this->baseURL."/assets/img/".$filename;
    }
    
}
Ensuite on instancie un objet HtmlHelper dans le index.php que l'on passera à la vue.
Fichier index.php
// après le require au tout début.
use PersoWebSite\TestHelper;

// déclaration des variables que l'on passe à la vue.
$datas = [
    //..
    'helper' => new HtmlHelper()
];
Pour terminer, on peux appeler l'objet helper depuis la vue, les templates ou les éléments, par exemple pour l'insertion d'une image.
Dans un fichier de type template/layout/element
<img src="<?= $helper->imgURL('image.png'); " />

Conclusion

En quelques lignes de code, Plates permet rapidement et efficacement de structurer le code qui concerne le rendu d'un site internet. Bien que plus adapté pour de petits projets, il reprend les principaux concepts des frameworks plus complets (et complexe) et peut être un excellent premier choix technique en vue d'une transition futur.

Toutefois, ce billet ne se limite finalement qu'à l'affichage d'une seule vue et donc d'une seule page. Pour un site comportant plusieurs pages, il n'est pas question de dupliquer le fichier index.php qui sert de point d'entrée mais de développer une mécanique de routing analysant l'URL pour charger la bonne page. Ce point plus que central fera l'objet d'un prochain billet.

Retour aux billets Billet plus ancien : Gitlab-ce, mise à jour par versions intermédiaires sous Debian. Billet plus récent : Prestashop 1.5 & PHP 7