Refonte admin : mise en place du socle (2/5)

Refonte admin : mise en place du socle (2/5)

Comment nous avons refondu Shop Application

De tous les actes, le plus complet est celui de construire – Paul Valéry

Maintenant qu'on a notre tout nouveau framework fraîchement recruté, il faut le brancher quelque part.

Shop Application c'est une solution qui n'est pas versionnée. Toutes les pages auxquelles ont accès nos clients sont les mêmes, pour qu'ils disposent tous des nouvelles fonctionnalités mais aussi de mises à jour de sécurité et de performance.

 

Installation

De cette hypothèse de départ on en déduit qu'il va falloir intégrer CodeIgniter de manière à être rétro-comptabile. On ne doit rien casser nulle part ni perturber nos clients dans leur façon de travailler.

Facile ! On a déjà intégré un framework au sein même du logiciel pour notre API, il s'agit donc de faire à peu près la même chose.

On initie donc un dossier tout neuf dans notre /wp-admin à nous et on y pose tout simplement un composer.json pour charger CodeIgniter4 ici.
Le dossier app contiendra nos contrôleurs et vues tandis que le dossier public servira à stocker les feuilles de style et fichiers javascript.
Inutile de s'étendre sur les détails de l'architecture du framework, on ne va pas vraiment y toucher.

 

Rétro-compatibilité

Pour le routing

Comme je le disais plus tôt, aucun lien ne doit changer.
Sauf que la structure des urls de CodeIgniter, de la forme /public/namespace/controller/action/, ne correspond pas du tout aux nôtres qui sont plutôt /admin/namespace-controller-action.html.

Mais quand on y réfléchit, on n'a finalement pas besoin de changer toute la structure du framework puisque tout ce dont on a besoin c'est de changer l'url d'accès à la page, et pas vraiment la route interne du framework.

On résume ça par une règle dans le .htaccess global :

RewriteRule ^admin/namespace-controller-action.html admin/codeigniter/public/namespace/controller/action [L]

Et pour faire la correspondance avec nos futurs contrôleurs, dans le fichier app/Routes.php on pourra écrire nos chemins de la forme :

$routes->add('namespace/controller/action', 'NamespaceController::action', ['as' => 'route_alias']);

Quid du MVC ?

En parlant de rétro-compatibilité, il faut savoir que dans Shop Application le découpage MVC est assez flou. Ayant hérité d'un OSCommerce de 2007, toutes les fonctionnalités n'ont pas pu bénéficier d'une mise à jour et d'un découpage clair.
Dans le pire des cas, une page de l'espace d'admin n'est générée que grâce à un seul fichier fourre-tout qui requête la base et génère du HTML. On est d'accord, ce n'est pas idéal. Heureusement, il n'en reste que très peu comme ça et ce sont ces pages-là qui vont nous demander le plus de travail.
Mais dans la plupart des cas, une page fait intervenir deux fichiers : un contrôleur qui s'occupe également de requêter la base, la plupart du temps grâce à des objets entités ou des collections, et une vue, qui peut utiliser des partials si besoin pour alléger.
Vous l'aurez compris, le gros du boulot sera donc de découper ce contrôleur made in Shop App pour en extraire la partie Modèle de notre système MVC et ainsi tenter de rester S.O.L.I.D. face à l'adversité.

Configuration optimale

Pour pouvoir accéder à notre base de données, définir les différents chemins de notre application, configurer les informations de notre site ou encore paramétrer nos différents environnements, un utilisateur de CodeIgniter est censé jouer avec les différents fichiers de configuration existants du framework comme le .env et tout ce qui se trouve dans app/Config. Sauf que tout ça est déjà bien défini, ancré et fonctionnel dans notre Shop Application et on n'a pas très envie de réinventer la roue.

On a donc surchargé la classe qui gère les requêtes dans CodeIgniter pour lui faire avaler le fichier de configuration globale existant.

<?php namespace App\Libraries;

use CodeIgniter\HTTP\IncomingRequest as BaseIncomingRequest;
use CodeIgniter\HTTP\URI;
use CodeIgniter\HTTP\UserAgent;

class IncomingRequest extends BaseIncomingRequest
{
    public function __construct($config, URI $uri = null, $body = 'php://input', UserAgent $userAgent = null) {
        // Lire les données du fichier de configuration
        include(ROOTPATH . '../chemin_fichier_configuration.php');

        parent::__construct($config, $uri, $body, $userAgent);
    }
}

Templétons

D'après la doc, une page peut générer un DOM HTML complet de deux manières :
 - soit en incluant tous les éléments de la page dans le template. C'est-à-dire le doctype, le header et le footer, en plus du contenu qui nous intéresse pour la page en cours.
 - soit en scindant ces différents éléments dans différents fichiers, qu'on appelle partiels, et en appelant ceux-ci dans le contrôleur.

On va d'office éliminer la première méthode, ça n'aurait pas de sens de dupliquer autant de code dans chaque vue.
La seconde méthode est mieux, mais c'est tout aussi bizarre de devoir appeler les partiels systématiquement dans chaque contrôleur. D'autant que la page ne fonctionnerait pas sans ceux-ci, donc finalement pourquoi rendre ces inclusions explicites ?

La fonction "view" a donc été remplacée par une méthode "render" de notre cru qui permet de s'affranchir du nom du template. Avec un peu de rigueur de nommage, on est capables de charger le template qui porte le nom de notre action dès lors que les deux sont dans une arborescence identique. Par exemple le contrôleur app/Controllers/Namespace/Controller.php::action() aura pour template automatique app/Views/namespace/controller/action.php.

function render(array $data = [], array $options = [], string $name = ''): string
{
    $controllerNameParts = explode('\\', Services::router()->controllerName());
    $name = strtolower($controllerNameParts[3]) . '/' . strtolower($controllerNameParts[4]) . '/' . strtolower(Services::router()->methodName());

    $renderer = Services::renderer();
    $saveData = config(View::class)->saveData;

    if (array_key_exists('saveData', $options)) {
        $saveData = (bool) $options['saveData'];
        unset($options['saveData']);
    }

    return $renderer
        ->setData($data, 'raw')
        ->render($name, $options, $saveData)
    ;
}

Mais ça ne résout pas encore notre problème d'inclusions redondantes. Et ça tombe bien puisque cette même doc qui propose de dupliquer des appels de vue propose aussi d'utiliser des layouts, et c'est très exactement ce qu'il nous faut !

Au final, notre dossier app/Views contient un fichier base.php dont tous les autres templates héritent (hormis les partiels) grâce au couple d'instructions PHP $this->renderSection('content') dans la base et $this->section('content') avec $this->endSection() dans le template enfant.

Petit bonus maison, une fonction qui permet d'appeler n'importe quel template depuis n'importe quel autre template tout en héritant des variables du parent :

function partial(string $template, array $parameters = [])
{
    return view($template, $parameters);
}

 

Et après ?

Maintenant qu'on a notre socle, il va falloir commencer à créer des pages dans nos templates. Mais ça c'est pour le prochain épisode...


Partager sur les réseaux