Jouer avec Composer

Pourquoi utiliser tout un Framework comme Laravel lorsqu’on a une tâche toute simple à effectuer ? On peut évidemment utiliser la version allégée Lumen. Mais on peut aussi tout simplement faire ses courses dans Packagist. Je vais montrer dans cet article à quel point Composer est simple et efficace et la richesse des composants qu’on a à notre disposition. Mais comme tout ça peut paraître très théorique ou alors perdu dans les méandres de Laravel par exemple, je vous propose de considérer un cas pratique qui va servir d’illustration, en l’occurrence on va construire un convertisseur Markdown en ligne.

Composer

Mais d’abord qu’est-ce que Composer ? C’est tout simplement un gestionnaire de dépendances pour PHP. On lui dit quels composants on désire et lui se fait un plaisir de les installer pour nous. En plus il met en place un chargement automatique des classes concernées. Et pour clôturer la chose il va aussi assurer les mises à jours.

Si vous utilisez Laravel vous connaissez déjà Composer et il est installé sur votre ordinateur. Si ce n’est pas le cas alors c’est le moment de le faire, que ce soit pour Linux ou pour Windows. Il existe aussi une version phar si vous ne voulez pas l’installer globalement.

Packagist

Allons faire notre marché sur Packagist. On va avoir besoin de :

  • un routeur
  • un convertisseur Markdown

Voyons un peu ce que nous trouvons…

Une recherche avec « route » donne :

img02

On voit que le plus utilisé est nikic/fast-route, mais on voit aussi que league/route est basé sur ce package. Comme j’aime bien les packages de league j’opte pour celui-ci.

Voyons maintenant pour Markdown :

img03

Là sans hésiter je choisis michelf/php-markdown.

Installation

Maintenant que les packages sont choisis comment les installer ?

Commençons par préparer un dossier sur un serveur pour accueillir notre application. Autant l’appeler markdown. Avec la console on se place dans ce dossier et on commence par entrer :

composer require league/route

On attend un petit peu et on obtient ce genre de chose :

Using version ^1.1 for league/route
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing symfony/http-foundation (v2.7.0)
    Loading from cache

  - Installing nikic/fast-route (v0.5.0)
    Loading from cache

  - Installing league/container (1.3.2)
    Loading from cache

  - Installing league/route (1.1.0)
    Loading from cache

Writing lock file
Generating autoload files

Voyons le résultat :

img04

On se retrouve avec pas mal de choses !

Quand on installe un composant il peut avoir des dépendances, alors Composer les installe également. Ici on voit qu’on a nikic/fast-route, mais ça on s’en doutait. Mais on a également symfony/http-foundation qui est un peu l’incontournable pour le http, et qui est d’ailleurs aussi utilisé par Laravel. On trouve aussi un autre composant de league : league/container.

On voit que les composants sont bien rangés dans des sous-dossiers de vendor :

img05

On trouve aussi un fichier composer.json avec ce code :

{
    "require": {
        "league/route": "^1.1"
    }
}

C’est ici qu’on définit les composants qu’on veut, ici Composer a mis celui que nous avons désigné. On peut très bien maintenant ajouter des noms de composants ici et utiliser composer update pour les installer.

On a aussi un fichier composer.lock qui sert à la maintenance interne de Composer. C’est là qu’il référence tout ce qu’il a installé. On n’a normalement pas à changer ce code à moins de savoir très précisément ce qu’on fait !

On a enfin un dossier composer :

img06

Ce dossier est destiné au chargement automatique des classes (mais aussi des fichiers éventuels). Je ne vais pas entrer dans leur description parce qu’ici aussi c’est Composer qui gère tout ça pour nous. Par exemple voilà le code pour le chargement automatique à la sauce PSR-4 :

<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
    'League\\Route\\' => array($vendorDir . '/league/route/src'),
    'League\\Container\\' => array($vendorDir . '/league/container/src'),
    'FastRoute\\' => array($vendorDir . '/nikic/fast-route/src'),
);

On retrouve les composants avec leurs emplacements précisés ainsi que leurs espaces de noms. On n’aura ainsi pas à se soucier de savoir où sont placés nos classes, il suffira de préciser leur espace de nom !

On va aussi installer le convertisseur Markdown :

composer require michelf/php-markdown

On obtient ça :

Using version ^1.5 for michelf/php-markdown
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing michelf/php-markdown (1.5.0)
    Loading from cache

Writing lock file
Generating autoload files

On nous dit que composer.json a été mis à jour :

{
    "require": {
        "league/route": "^1.1",
        "michelf/php-markdown": "^1.5"
    }
}

Et évidemment on trouve un nouveau dossier pour ce composant :

img07

On voit aussi que ce composant n’a aucune dépendance.

Des urls propres

Comme on va utiliser un fichier index.php pour récupérer toutes les urls on va avoir besoin d’un fichier .htaccess. Il faudra réécrire les urls, donc avoir le module mod_rewrite activé. Pour le code du fichier .htaccess je me suis contenté de reprendre celui de Laravel :

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>

    RewriteEngine On

    # Redirect Trailing Slashes...
    RewriteRule ^(.*)/$ /$1 [L,R=301]

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

L’application

Maintenant que l’intendance est en place il faut écrire le code de l’application. J’ai fait simple parce que le but est surtout de montrer comment Composer nous aide, pas pour aboutir à une application impeccable. Voici le code du fichier index.php :

<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use League\Route\RouteCollection;
use Michelf\Markdown;

// Autoload
require 'vendor/autoload.php';

// Création du routeur
$router = new RouteCollection;

// Création routes
$router->addRoute('GET', '/convert', function (Request $request, Response $response) {

    $html = '<form action="convert" method="POST">
     <textarea rows="4" cols="50" name="content"></textarea>
     <p><input type="submit" value="OK"></p></form>';
    $response->setContent($html);
    $response->setStatusCode(200);
    return $response;

});
$router->addRoute('POST', '/convert', function (Request $request, Response $response) {

    $content = $_POST['content'];
    $content = '<h1>Conversion obtenue :</h1>' . Markdown::defaultTransform($content);
    $response->setContent($content);
    $response->setStatusCode(200);
    return $response;

});

// On traite la requête
$dispatcher = $router->getDispatcher();
$request = Request::createFromGlobals();
$response = $dispatcher->dispatch($request->getMethod(), $request->getPathInfo());

// On renvoie la réponse
$response->send();

Voyons un peu ça. Pour que le chargement automatique des classes fonctionne il faut charger le fichier vendor/autoload.php :

require 'vendor/autoload.php';

Du coup avec quelques use on peut référencer les classes :

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use League\Route\RouteCollection;
use Michelf\Markdown;

Il est ensuite facile de créer le routeur :

$router = new RouteCollection;

On ajoute ensuite 2 routes :

$router->addRoute('GET', '/convert', function (Request $request, Response $response) {

    $html = '<form action="convert" method="POST">
     <textarea rows="4" cols="50" name="content"></textarea>
     <p><input type="submit" value="OK"></p></form>';
    $response->setContent($html);
    $response->setStatusCode(200);
    return $response;

});
$router->addRoute('POST', '/convert', function (Request $request, Response $response) {

    $content = $_POST['content'];
    $content = '<h1>Conversion obtenue :</h1>' . Markdown::defaultTransform($content);
    $response->setContent($content);
    $response->setStatusCode(200);
    return $response;

});

Pour le détail de cette syntaxe vous pouvez vous reporter à la documentation du composant.

On a ainsi 2 routes :

  • convert avec le verbe GET pour aller chercher le formulaire
  • convert avec le verbe POST pour soumettre le formulaire

La requête est traitée avec ce code :

$dispatcher = $router->getDispatcher();
$request = Request::createFromGlobals();
$response = $dispatcher->dispatch($request->getMethod(), $request->getPathInfo());

On obtient ainsi la réponse qu’il n’y a plus qu’à envoyer :

$response->send();

Le code est sommaire mais suffisant pour que l’application fonctionne.

Fonctionnement

Donc avec l’url convert on obtient le formulaire :

img08

Ce n’est pas du grand art mais on s’en contentera. On a une zone de texte et un bouton de soumission. On va entrer du code au format markdown. Par exemple :

# Titre

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

  - Type some Markdown on the left
  - See HTML in the right
  - Magic

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum :

    Duis aute irure dolor in reprehenderit
    occaecat cupidatat non proident
    consectetur adipiscing elit
    sed do eiusmod tempor incididunt

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

A la soumission on obtient :

img09

On obtient bien la conversion !

Un template

Poursuivons cet exemple en se disant qu’il serait mieux avec un système de template. Comme on aime bien Blade pourquoi ne pas l’utiliser ? On va utiliser le composant philo/laravel-blade :

composer require philo/laravel-blade

Pour le coup on va se retrouver avec pas mal de composants installés :

img10

Mais on a un système de template performant à disposition !

Pour Blade on va prévoir deux nouveaux dossiers :

img11

Le premier pour le cache de Blade et l’autre pour les vues.

On crée les deux vues avec toujours la même prestation minimale :

img12

La première pour le formulaire (form.blade.php) :

<form action="convert" method="POST">
	<textarea rows="4" cols="50" name="content"></textarea>
	<p><input type="submit" value="OK"></p>
</form>

La seconde pour le résultat (result.blade.php) :

<h1>Conversion obtenue :</h1> {!! $html !!}

Et voilà le nouveau code de index.php :

<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use League\Route\RouteCollection;
use Michelf\Markdown;
use Philo\Blade\Blade;

// Autoload
require 'vendor/autoload.php';

// Initialisation de Blade
$views = __DIR__ . '/views';
$cache = __DIR__ . '/cache';
$blade = new Blade($views, $cache);

// Création du routeur
$router = new RouteCollection;

// Création routes
$router->addRoute('GET', '/convert', function (Request $request, Response $response) use($blade) {

    $response->setContent($blade->view()->make('form')->render());
    $response->setStatusCode(200);
    return $response;

});
$router->addRoute('POST', '/convert', function (Request $request, Response $response) use($blade) {

    $content = $_POST['content'];
    $content = Markdown::defaultTransform($content);
    $response->setContent($blade->view()->make('result')->with('html', $content)->render());
    $response->setStatusCode(200);
    return $response;

});

// On traite la requête
$dispatcher = $router->getDispatcher();
$request = Request::createFromGlobals();
$response = $dispatcher->dispatch($request->getMethod(), $request->getPathInfo());

// On renvoie la réponse
$response->send();

Comme nouveautés on a la référence du composant pour Blade :

use Philo\Blade\Blade;

L’initialisation de Blade :

$views = __DIR__ . '/views';
$cache = __DIR__ . '/cache';
$blade = new Blade($views, $cache);

L’utilisation de Blade pour générer les vues avec tranmission d’un paramètre dans le second cas :

$response->setContent($blade->view()->make('form')->render());

...

$response->setContent($blade->view()->make('result')->with('html', $content)->render());

L’application est ainsi un peu plus propre sans nous avoir demandé de gros efforts !

On pourrait encore améliorer le code mais je vais m’arrêter là sinon on va se retrouver avec tous les composants de Laravel !

Laisser un commentaire