Vous les utilisez probablement de façon naturelle sans même vous en rendre compte, les bibliothèques "Illuminate" nous rendent bien des services.
Requêter la base de données, faire une nouvelle route, authentifier un utilisateur ... elles sont les composantes applicatives de notre Framework favori ... mais saviez-vous que vous pouvez les utiliser en dehors de Laravel ?
Petit rappel sur la base de code Laravel avant d'aller plus loin :
-
laravel/laravel est un starter kit pour se mettre rapidement au travail. Le Framework y est une dépendance qui est mise en service notamment via les fichiers
index.php
,artisan
&bootstrap/app.php
- laravel/framework contient l'Application et les services applicatifs, c'est la pierre angulaire qui ordonne et instruit les services sur leur façon de fonctionner.
-
illuminate/* sont les services de
laravel/framework
, distribué hors du Framework et de façon individuel.
Mais ... si le Framework contient les services applicatifs, pourquoi les rendre disponibles individuellement ?
Vous envisagiez peut-être de développer votre propre framework en tirant parti des fonctionnalités existantes, de créer un package Composer
qui devra embarquer une dépendance forte avec une fonctionnalité existante, ou bien alors d'optimiser un service sans nécessiter toutes ces dépendances, les possibilités sont vastes et variées.
Je vous partage cette ressource de Matt Stauffer qui vous propose bon nombre d'exemples pour utiliser les composants Illuminate hors de Laravel.
Passons à l'action !
J'ai récemment été confronté à la refonte d'une API de génération de QR Code dont les enjeux ont été multiples et pour lesquels sans Illuminate, je ne suis pas sûr que cette refonte aurait pu être aussi aboutie.
L'API est composée d'un endpoint "get", paramétrable via plusieurs arguments.
Laravel 9.x sous le capot et intégrant le package simplesoftwareio/simple-qrcode pour la génération des QR Codes.
L'infrastructure, un serveur tout à fait classique avec des contraintes classiques : CPU, RAM et bande passante limitée ... le tout cumulé limitant le nombre de requêtes possible par minute.
Les objectifs de la refonte étaient multiples :
- Être toujours disponible, s'abstraire des limitations serveurs en migrant sur une fonction Serverless
- Avoir le minimum de packages composer et supprimer ceux inutilisés
- Optimiser le poids du projet et ainsi optimiser le temps de déploiement
- Si possible, pouvoir poursuivre l'utilisation du package
simplesoftwareio/simple-qrcode
À l'exception du contexte serveur, pour lequel je ne rentrerais pas plus dans les détails, cette application pourrait se résumer aux actions suivantes :
- Valider des paramètres, on sait le faire avec Laravel et
Illuminate\Validation
- Utiliser le service de QR Code, Laravel s'occupe de le charger pour nous
On s'aidera également de composer pour se faciliter la récupération des packages et librairies.
Allez ! Ça valide !
On commence par initialiser le projet avec composer.
1mkdir -p flash2cd flash3composer init
On va maintenant analyser la première dépendance que l'on va utiliser, Illuminate\Validation
, en commençant par installer le package.
1composer require illuminate/validation
Les détails techniques du package sont disponibles sur packagist.org, on s'y intéressera un peu plus bas dans l'article.
Une fois installé, explorons le répertoire vendor/illuminate/validation
.
Habituellement, la mécanique de Laravel (avec l'aide de composer) permet de charger automatiquement le "Service Provider" de la librairie, cela vous permet d'utiliser très facilement la "Facade" Validator
.
Les deux classes suivantes sont habituellement chargées automatiquement :
-
vendor/illuminate/validation/ValidationServiceProvider.php
-
vendor/illuminate/support/Facades/Validator.php
Alors, tout ça est bien beau, mais nous, on est hors de Laravel, hors du Framework, il n'y a plus de chargement automatique des classes !
Le modèle de conception "Service Provider" permet de créer une instance d'une ou plusieurs classes et de la/les mettre à disposition sous forme d'un singleton. La Facade, quant à elle, facilite l'accès à cette instance.
Voici comment Laravel instancie le validator dans le framework :
1protected function registerValidationFactory() 2{ 3 $this->app->singleton('validator', function ($app) { 4 $validator = new Factory($app['translator'], $app); 5 6 // The validation presence verifier is responsible for determining the existence of 7 // values in a given data collection which is typically a relational database or 8 // other persistent data stores. It is used to check for "uniqueness" as well. 9 if (isset($app['db'], $app['validation.presence'])) {10 $validator->setPresenceVerifier($app['validation.presence']);11 }12 13 return $validator;14 });15}
Portons notre attention à cette ligne qui crée l'instance et qui est ensuite partagée à la Facade.
1$validator = new Factory($app['translator'], $app);
Nous allons donc créer une instance du Validator de la même façon en faisant très attention aux paramètres.
1// le constructeur de la class `Illuminate\Validation\Factory`2public function __construct(3 Translator $translator,4 Container $container = null5)
On voit ici que seul le premier paramètre est obligatoire et doit être une instance de la classe Illuminate\Translation\Translator
.
Sur packagist.org, sans surprise, on peut voir que le package illuminate/translation
est une dépendance du package illuminate/validation
.
Commençons à écrire nos premières lignes de code en ajoutant un fichier index.php
à la racine du projet, on lie ensuite l'autoloader de composer :
1<?php declare(strict_types=1);2 3require __DIR__ . '/vendor/autoload.php';
Le Serverless impose de servir une fonction, la forme du index.php
ne sera pas la même que sur Laravel.
1<?php declare(strict_types=1);2 3require __DIR__ . '/vendor/autoload.php';4 5return function ($event = []) {6 7};
On peut alors coder la validation des arguments de cette fonction.
1<?php declare(strict_types=1); 2 3require __DIR__ . '/vendor/autoload.php'; 4 5use Illuminate\Translation\{ArrayLoader, Translator}; 6use Illuminate\Validation\Factory; 7 8return function ($event = []) { 9 10 /*11 * Pour instancier l'objet des traductions,12 * il faut générer un tableau de traduction13 */14 $validationTranslations = require __DIR__ . '/vendor/illuminate/translation/lang/en/validation.php';15 $translations = (new ArrayLoader())16 ->addMessages('en', 'validation', $validationTranslations);17 18 // On crée l'instance, l'objet du Translator19 $translator = new Translator($translations, 'en');20 21 // On peut maintenant créer l'objet du Validator22 $validator = new Factory($translator);23 24 /*25 * C'est parti ! On peut valider un array, ici `$event`,26 * le tableau qui contient les arguments de la fonction Serverless27 */28 $validator29 ->make($event, [30 'correction' => 'required|string|in:L,M,Q,H',31 'format' => 'required|string|in:png,svg,eps',32 'size' => 'required|integer',33 'text' => 'required|string',34 'image' => 'url',35 ])36 ->validate();37 38 unset($validator);39 40 // ici, on va générer le QR Code41 42 return // et là, on va retourner le QR Code;43};
Top ! On vient d'utiliser illuminate\validation
hors de Laravel !
Ça flashe pour moi, la méthode générale
Ok, on vient d'intégrer illuminate en dehors de Laravel, maintenant comment ça fonctionne pour un package Laravel Open Source ? Eh bien, de la même façon :
- On trouve la classe qui est instanciée par le "Service Provider"
- On s'approprie les paramètres et on liste les dépendances de la classe
- On ajoute les dépendances à composer
Avant de lire le code qui suit, essayez de faire l'exercice avec le package simplesoftwareio/simple-qrcode
et si vous avez besoin de support n'hésitez à nous rejoindre sur discord
Voilà ce que ça donne :
1<?php declare(strict_types=1); 2 3require __DIR__ . '/vendor/autoload.php'; 4 5use Illuminate\Translation\{ArrayLoader, Translator}; 6use Illuminate\Validation\Factory; 7use SimpleSoftwareIO\QrCode\Generator; 8 9return function ($event) {10 11 // Ici, il y a le code du Validator12 13 /*14 * Il n'y a pas de paramètre pour le générateur de QR Code15 * On crée une nouvelle instance, et on l'utilise directement16 */17 $qrGenerator = new Generator();18 19 $qrCode = $qrGenerator20 ->format($event['format'])21 ->errorCorrection($event['correction'])22 ->size($event['size']);23 24 unset($qrGenerator);25 26 if (isset($event['image'])) {27 $qrCode->merge($event['image'], .2, true);28 }29 30 return base64_encode($qrCode->generate($event['text'])->toHtml());31};
La fonction Serverless est prête, nous allons l'exécuter pour nous assurer que tout fonctionne correctement.
Créez un fichier test.php
et collez-y le script ci-dessous.
1<?php declare(strict_types=1); 2 3$event = [ 4 "correction" => "L", 5 "format" => "png", 6 "size" => 100, 7 "text" => "1234", 8]; 9 10$qrCodeFunction = require('./index.php');11 12$base64QrCode = call_user_func($qrCodeFunction, $event);13 14echo $base64QrCode;
Pour lancer l'exécution, dans un terminal, exécutez php test.php
, votre QR Code apparaitra sous la forme d'une chaine de caractère encodée en base64.
Service Provider ou Instance de classe ?
Dans le chapitre précédent, le contexte de la refonte de l'API de QR Code a dicté le besoin d'investiguer dans les différentes librairies que nous avons utilisées, pour trouver les bonnes classes à instancier et à utiliser. On est revenu au b.a.-ba, nous avons fait du PHP.
Dans le projet de Matt Stauffer, il présente un contexte beaucoup plus orienté application. Rappelez-vous de mon commentaire plus haut
Le modèle de conception "Service Provider" permet de faire une instance d'une ou plusieurs classes et la/les mettre à disposition sous forme d'un singleton
Dans les projets plus conséquents avec plusieurs fichiers, des classes applicatives et/ou metiers, vous allez certainement avoir besoin d'appeler plusieurs fois une même classe pour l'utiliser à de multiple reprise au cours du cycle de vie d'une de vos requêtes.
Pour ce faire, nous allons utiliser illuminate/container
, à l'instar de Laravel, nous allons créer une "Application".
Il n'est pas rare que les Frameworks aient un objet central qui orchestre l'ensemble de ses services et que cette Application soit nommée "Container".
Regardons le code du package illuminate/container
, nous remarquons qu'il n'y a pas de "Service Provider" ni de mécanique associée à Laravel, ici, on va directement utiliser la classe Container
.
À l'exemple des Services Providers, nous allons maintenant enregistrer des instances de classes dans le Container pour pouvoir les solliciter à notre guise.
1use Illuminate\Container\Container; 2use Illuminate\Translation\{ArrayLoader, Translator}; 3use Illuminate\Validation\Factory; 4 5$app = new Container(); 6 7// On assigne le nom `validator` à notre instance 8$app->singleton('validator', function () { 9 $validationTranslations = require __DIR__ . '/vendor/illuminate/translation/lang/en/validation.php';10 $translations = (new ArrayLoader())->addMessages('en', 'validation', $validationTranslations);11 12 return new Factory(new Translator($translations, 'en'));13});
Une instance doit être nommée pour qu'on puisse facilement la retrouver par la suite, dans notre cas, nous appellerons ce singleton : validator
.
Grâce au Container, nous pouvons désormais facilement récupérer une instance du validator :
1// On utilise notre instance2$app->get('validator'); // Illuminate\Validation\Factory
Notre tour d'horizon s'achève ici, je vous laisse maintenant explorer ces librairies pour Illuminez votre PHP.
A lire
Autres articles de la même catégorie
Des dépendances stables pour une architecture de qualité
Appliquons le Principe des Dépendances Stable (SDP) dans une application Laravel
Le design pattern Repository dans Laravel
Explorons une implémentation du pattern Repository au sein d’une application Laravel.
Les failles RCE dans Laravel
Une faille RCE consiste à injecter puis exécuter arbitrairement du code dans une application, voyons comment exploiter l’une d’entre elles dans un Laravel 9 !