Twitter Facebook Google Plus Linkedin email

Laravel, framework PHP complet et facile à prendre en main

J'ai découvert Laravel suite à cette question posée sur Twitter. Habitué à développer des applications Web en Java sous Play Framework 2, je recherchais un nouveau MVC en PHP pour développer des applications plus légères pouvant être hébergées facilement sur un mutualisé.

J'avais déjà eu l'occasion de tester (ou d'auditer) d'autres framework PHP comme FuelPHP, CodeIgniter (en fin de vie) et Cake PHP, mais je n'avais jamais entendu parler de Laravel. C'est un framework qui existe depuis 2011 mais qui a explosé récemment et qui semble séduire pas mal de développeurs, je m'y suis donc attardé... Voici un résumé des fonctionnalités que j'ai pu apprécier dans ce framework

Composer

Installable via composer, Laravel évite de réinventer la roue et utilise un maximum de librairies déjà éprouvées et quelques composants du framework Symfony. Je trouve composer très pratique pour installer les dépendances, même si j'ai toujours peur de télécharger une backdoor à chaque fois que je composer install/update.

En effet, il suffit qu'une librairie dépende d'un repo backdooré/hacké pour que composer la télécharge automatiquement lors du prochain update. Je doute que composer intègre des mécanismes de sécurité/signature très avancés, mais je ne vais pas m'étendre sur le sujet car je n'ai pas lu le code source et ce n'est pas le but de l'article...

Une gestion des routes intuitive et libre

La première chose qui m'a plu avec Laravel est son côté intuitif, je n'ai pas eu à systématiquement fouiner dans la doc pour mettre en place mes premières routes. D'ailleurs, je trouve particulièrement flexible la gestion des routes, on peut router vers un contrôleur ou définir en dur l'action dans routes.php.

<?php // app/routes.php

// Définition d'une route classique vers un controlleur
Route::get('/controller/action', array( 'uses' => 'MyController@action'));

// Définition d'une route en dur
Route::get('/hello-world', function(){
    return "Hello World";
});

Un autre aspect sympa au niveau du routing est le système de préfixe qui permet de grouper les routes et de leur appliquer des filtres. Ces filtres sont des fonctions appelées avant chaque contrôleur et qui permettent d'effectuer des contrôles d'accès (authentification, droits), de sécurité (vérification du token XSRF) ou tout ce que vous voulez.

<?php // routes.php

// Vérifier que l'utilisateur est administrateur avant toute action
Route::group( ['before' => 'admin'], function(){

    Route::get('/admin', array( 'uses' => 'Admin\HomeController@index'));
    Route::get('/admin/users', array( 'uses' => 'Admin\UserController@index'));
    // ...
});

On est également assez libre pour faire les liens vers nos routes: on peut effectuer du reverse routing, linker les contrôleurs ou utiliser des alias, le framework se chargera de mettre les bons liens... Celui-ci est très flexible et permet de retrouver très rapidement des habitudes que l'on pouvait avoir sur d'autres frameworks.

Contrôle et validation des entrées utilisateurs

Laravel utilise un système de validation des modèles avec des règles assez simples, on peut récupérer facilement les messages d'erreur en fonction des inputs et cela s'intègre très bien au niveau des views/templates.

<?php // exemple de règles de validation
array (
    'email' => 'required|email|unique:users',
    'password' => 'required|between:8,63|confirmed',
    'twitter' => 'alpha_num|between:0,15|unique:users'
);

Pour ma part j'ai utilisé un fork du module Ardent qui permet d'effectuer la validation dans le modèle lorsqu'on sauvegarde (insert ou update) le modèle. Lorsque la validation échoue, rien n'est sauvegardé et il est possible de récupérer les messages d'erreur. L'intérêt d'un tel module est d'éviter de faire la validation au niveau du contrôleur et de ne pas recoder plusieurs fois la même chose (DRY).

ORM et query builder

Mal utilisés, l'ORM et le Query Builder peuvent mener à des failles de sécurité importantes (injections SQL), j'en parle plus en détail dans la partie sécurité.

Eloquent est l'ORM de Laravel. J'ai eu l'occasion de l'utiliser avec des tables ayant des relations assez avancées (many-to-many) et l'ORM a pu tenir la route et gérer toutes ces relations dans mes modèles sans broncher.

Rien à lui reprocher niveau performance non plus, on peut utiliser le eager loading pour faire des jointures et précharger les attributs de ses modèles et ainsi limiter les requêtes vers la base de données.

Mais ce n'est pas tout, si vous n'arrivez pas à tout faire avec l'ORM, Laravel embarque également un query builder fort sympathique. Le but de celui-ci est de rendre le code plus propre et protéger des injections SQL (ce qu'il fait à moitié en réalité, orderBy et groupBy ne sont pas filtrés par exemple).

<?php // utilisation du query builder jooq
$users = DB::table('users')
        ->where('votes', '>', Input::get('age'))
        ->orWhere('name', 'John')
        ->get();

Dans le même genre en Java, j'aime utiliser Jooq qui remplit les mêmes fonctionnalités mais qui en plus gère l'auto-completion (noms des tables, noms des colonnes) et déclenche une erreur à la compilation en cas d'erreur SQL, très pratique !

J'ai également remarqué une gestion de la pagination qui semble très pratique mais que je n'ai pas eu l'occasion de tester.

Enfin, il y a l'utilitaire artisan (la ligne de commande Laravel) qui permet, entres autres, de créer, migrer et de seeder la base de données. On regrettera cependant l'absence de génération de fichier SQL.

Le caching

Pour ce qui est du cache, Laravel dispose d'une interface simple et complète, ici rien de transcendant mais c'est toujours bon à savoir que c'est géré par défaut.

<?php // cache

Cache::put('key', 'value', $minutes);
$value = Cache::get('key');

Par défaut le système de fichiers est utilisé pour stocker le cache mais il suffit de changer le driver dans la configuration pour utiliser APC, memcached ou redis out of the box.

Les queues

Certaines taches pouvant pendre du temps n'ont pas besoin d'être exécutées immédiatement car elles peuvent ralentir le traitement de la requête inutilement. C'est par exemple le cas lors de l'envoi d'un email à un utilisateur ou lors de l'exécution d'une grosse requête SQL.

Laravel intègre un système de queues vraiment très simple à utiliser et supportant plusieurs drivers par défaut (beanstalkd, ironmq, amazon SQS).

<?php // Pushing A Job Onto The Queue
Queue::push('SendEmail', array('message' => $message));

Pour ma part j'ai utilisé beanstalkd sur un autre serveur dédié afin d'envoyer des emails en asynchrone et éviter d'utiliser l'IP du mutualisé (pour une meilleure délivrabilité) et il n'y a rien dire, c'est vraiment très simple d'utilisation et rapide.

Je regrette cependant qu'il n'y ait pas de contrôles d'accès avec beanstalkd, n'importe qui peut lui envoyer des messages et exécuter le code PHP de son choix... beanstalkd se limite donc à une utilisation en local ou avec un firewall (et encore...).

Debugging

En mode développement, Laravel utilise la lib whoops pour afficher un message d'erreur complet avec backtrace, affichage du code source, contenu des variables, environnement... C'est très complet comme on peut le voir sur cet exemple.

Au passage je recommande également kint, une petite lib pour aider au débugage basic. On doit pouvoir trouver d'autres extensions plus complètes ou des profilers sur l'Internet...

Sécurité

La documentation n'est pas claire à propos de l'ORM et du Query Builder, celle-ci spécifie qu'il n'est pas nécessaire d'échapper ou de filtrer les données, ce qui est faux. Seules les données utilisées dans les where()/like() sont échappées, dans tous les autres cas (limit, offset, orderBy, groupBy, join, ...) les entrées doivent être filtrées avant utilisation. Il s'agit d'une faille de sécurité importante, j'ai ouvert un bug à ce sujet. [fixed].

J'ai découvert plusieurs injections SQL au niveau du QueryBuilder et de l'ORM, après ouverture du bug report sur github, celles-ci ont été corrigées en quelques heures. Rares sont les vulnérabilités corrigées aussi rapidement, c'est plutot bon signe (malgré la découverte d'une faille de sécurité).

Pour le hashing des mots de passe, Laravel utilise bcrypt, c'est à dire un hash salé, chiffré avec l'algorithme blowfish et une complexité de 8/32 (en rapport avec le nombre d'itérations, plus la complexité est élevée, plus le hash est dur à cracker). Pour faire court, c'est excellent.

<?php // création d'un hash sécurisé avec Laravel
$password = Hash::make('secret');

Laravel embarque également un petit helper sympa qui permet de chiffrer en AES rapidement.

<?php // chiffrement AES
$encrypted = Crypt::encrypt('secret');

La protection des formulaires contre les failles xsrf est bonne, il suffit d'ouvrir un formulaire avec l'helper Form::open pour implicitement ajouter un token xsrf. Il faut ensuite décider au niveau du contrôleur (ou des filtres) si vous souhaitez vérifier le token.

<?php // vérification par défaut du token xsrf dans filters.php
Route::filter('csrf', function()
{
    if (Session::token() != Input::get('_token')){
        throw new Illuminate\Session\TokenMismatchException;
    }
});

J'ai eu l'occasion de faire de l'upload de fichiers, la fonction qui gère l'upload nettoie correctement le nom du fichier en enlevant les caractères indésirables \0 \ / .., ce qui est une bonne chose facilitant le travail du développeur.

Laravel embarque quelques fonctions basiques d'authentification, cependant, je n'ai rien vu d'intéressant au niveau de la gestion des droits d'accès et des groupes, ce sera à vous d'implémenter ces procédures et de mettre en place les contrôles (pensez aux routes et filtres). Au passage je déconseille les modules entrust et confide car vraiment pas au point.

Concernant les XSS, c'est au développeur de bien contrôler ses modèles et d'utiliser htmlentities+ENT_QUOTES à bon escient lors du rendu.

Je n'ai que très peu regardé le core de Laravel, une rapide recherche sur Google révèle peu d'alertes de sécurité, cela peut être dû au fait que Laravel réutilise pas mal de composant. Cependant, les problèmes d'injection SQL que j'ai pu découvrir ne sont pas très rassurants et montrent que le framework n'a pas encore été assez audité.

Ce qui m'a déplu (c'est-à-dire pas grand-chose)

Trop gros, environ 42 mo pour une installation Laravel 4, il y a probablement beaucoup de fichiers inutiles parmi tous ces packages.

Des performances moyennes sur mutualisé. Je ne veux pas faire de raccourci facile entre la taille de l'installation et les performances, il se peut que Laravel ne charge pas beaucoup de fichiers, il faudrait utiliser un profiler pour voir d'où vient le problème. Sur un mutualisé d'OVH ça tourne vraiment moyen (beaucoup plus vite lorsqu'on active le mode fpm en beta). Néanmoins, en local ou sur un dédié, avec ou sans APC, ça tourne très bien.

Déjà en version 4 en 2 ans d'existence, personnellement ça me fait un peu peur... Je n'ai aucune idée du volume de modification entre les versions et s'il y a beaucoup de refactoring à faire, j'ai uniquement testé la version 4.

Le support de l'internationalisation est bof, on a droit à un système de traduction basic basé sur les tableaux. On peut mettre des préfixes facilement au niveau de la génération des urls pour différencier les versions (fr.domains.com ou domains.com/fr par exemple) mais rien ne permet de gérer des urls multilingues par défaut (/fr/voiture.html, /en/car.html).

Peut-être encore un peu jeune et pas assez testé.

TLDR; Conclusion

C'est un excellent framework que j'ai adopté, même si c'est un projet qui doit encore murir un peu, c'est largement suffisant: il est complet et facile à prendre en main.