Retour d’expérience d’un développeur Symfony qui découvre Laravel

En tant que développeur PHP junior, mon expérience s’est jusqu’à aujourd’hui limitée à Symfony.
Curieux de nature, j’ai décidé de me lancer dans l’aventure Laravel, un framework réputé pour sa simplicité et sa rapidité de développement. Cette expérience m’a permis de découvrir les forces et les faiblesses de Laravel, et de comparer son approche à celle de Symfony. Voici donc mon retour d’expérience complètement subjectif !
Section intitulée premieres-impressionsPremières impressions
J’ai d’abord été séduit par la simplicité et la rapidité de mise en place d’un projet Laravel. Le framework est assez facile à prendre en main et a une courbe d’apprentissage peu pentue. L’outillage intégré, combiné à une configuration quasi-"magique", permet de démarrer rapidement sans se perdre dans les méandres de la configuration.
À ce titre, Symfony propose une expérience de démarrage similaire, avec une configuration initiale simple et de nombreux bundles pour étendre les fonctionnalités.
La principale différence ici est à mon sens la personnalisation de l’environnement créé initialement. Symfony crée un projet avec plus ou moins de packages en fonction des options passées à la commande symfony new
. Hormis celà, la base reste toujours à peu près la même.
Laravel lui, nous pose plusieurs questions. Il demande si on veut supporter le dark-mode; initialiser un repository Git; utiliser un starter kit; avec quelles options (Alpine ? Vue ? API ?); quelle base de données et quel framework de test on souhaite utiliser.
Au-delà de la configuration initiale, Laravel diffère également par sa syntaxe et sa structure de projet. Les migrations avec Eloquent, par exemple, portent des noms explicites qui reflètent leur fonction. Dans Symfony, lorsqu’on utilise Doctrine, elles sont plutôt identifiées avec un timestamp uniquement (bien qu’il soit techniquement possible de les nommer).
De même, la définition des modèles se fait directement dans les fichiers de migration, plutôt que de générer ces migrations à partir des entités comme c’est le cas avec Symfony. Si cette approche peut offrir une grande flexibilité, j’ai surtout trouvé cela déroutant au début. Avec Symfony, l’entité PHP me donne une vision claire et précise de la structure de mes données, alors qu’avec Laravel, il faut constamment se référer aux fichiers de migration pour comprendre la composition de mes modèles. C’est sûrement juste une habitude à prendre, mais qui m’a perturbé au début.
// database/migrations/2024_04_30_095715_create_posts_table.php
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('author_id')
->constrained('users')
->cascadeOnUpdate()
->cascadeOnDelete();
$table->string('title');
$table->string('slug')->unique();
$table->text('summary');
$table->text('content');
$table->timestamp('published_at')
->useCurrent()
->nullable();
});
J’ai aussi été déconcerté par le fait que les routes soient définies dans un fichier séparé des contrôleurs. Cependant, j’ai rapidement apprécié la lisibilité accrue et la facilité de création de groupes de routes avec des préfixes, des middlewares, etc. Bien que je regrette un peu de ne pas avoir mes routes directement au-dessus de mes méthodes de contrôleur comme dans Symfony, je peux m’en passer.
Route::prefix('{_locale?}')
->where(['_locale' => '[a-zA-Z]{2}'])
->middleware('SetAppLocale')
->group(function () {
Route::get('/', function () {
return view('homepage');
})->name('homepage');
Route::prefix('blog')->name('blog.')->controller(BlogController::class)->group(function () {
Route::get('/', 'index')
->defaults('page', 1)
->defaults('_format', 'html')
->name('index');
Route::get('/search', 'search')
->name('search');
…
});
});
En revanche, l’absence d’attributs, une fonctionnalité moderne de PHP, m’a étonné. Je suis plutôt d’avis qu’il faille utiliser pleinement les nouvelles capacités du langage pour avoir un framework où une application à la pointe (dans la mesure où ça reste utile évidemment).
J’ai également dû créer manuellement mes rôles « Admin » et « User » avec un Enum pour intégrer une logique de permissions, similaire à ce que je fais en Symfony, faute d’avoir trouvé un équivalent direct dans Laravel. Je ne suis toujours pas certain d’avoir adopté la meilleure approche à ce sujet.
// app/Models/User.php
public function isAdmin(): bool
{
return $this->roles->contains(\App\Enum\Role::ADMIN);
}
En résumé, Laravel m’a laissé une impression de fraîcheur et de modernité. Le développement est rapide et on se sent très libre. Cependant, cette liberté peut être à double tranchant pour un développeur novice, car elle permet de faire n’importe quoi assez facilement si l’on ne s’y connaît pas. La communauté a heureusement fait une liste des bonnes pratiques qui m’a bien servi pour m’assurer que mon code soit toujours qualitatif.
La documentation est également excellente. Elle mélange technique et explications avec brio. Je la trouve très lisible pour un développeur qui débute.
Section intitulée les-points-forts-de-laravelLes points forts de Laravel
Par où commencer ? Venant de Symfony, je me suis retrouvé assez facilement dans l’utilisation de Blade, qui semble être tout aussi puissant que Twig. On peut même y mettre des morceaux de script PHP et y inclure des classes avec @use (mais il est déconseillé de mettre du script dans les template, je ne m’y risquerait pas 😛). On peut évidemment enregistrer des méthodes/extensions custom, qu’on appelle directives, pour notre template en les définissant dans un ServiceProvider.
Blade::directive('formatMoney', function ($amount) use ($settings) {
if (\is_string($amount)) {
$amount = (float) $amount;
}
$formatter = new \NumberFormatter(config('app.currency_locale', 'en_US'), \NumberFormatter::CURRENCY)
return $formatter->formatCurrency($amount, $settings->app_currency);
});
Exemple ici d’une directive pour formater une somme d’argent
Ces ServiceProviders sont d’ailleurs très utiles pour enregistrer toutes sortes de choses. Dans mon projet par exemple, j’y ai défini un macro utilisable sur tous mes Models pour faire de la recherche facilement, ainsi qu’une Gate (accès basique au système d’authorization) pour vérifier les permissions d’administration.
public function boot(): void
{
Builder::macro('search', function (string $column, string $value) {
return $value ? $this->where($column, 'like', "%{$value}%") : $this;
}); // ce qui me permet d'appeler Post::search($column, $name) depuis n’importe où
Gate::define('admin', function ($user) {
return $user->roles->contains(Role::ADMIN);
});
}
Peut-être pas le plus propre des codes, mais très pratique et facile à utiliser.
En parlant de choses pratiques à utiliser, j’ai adoré travailler avec les “helper functions” globales et les Facades. Je n’ai jusqu’ici utilisé que l’injection de dépendances dans mes projets. J’y suis habitué, et je trouve cette manière de faire très robuste.
Mais j’avoue avoir aimé pouvoir m’en passer pour réduire le code « passe-partout » répétitif de mes constructeurs. C’est valorisant de produire un code propre et élégant, conforme aux bonnes pratiques, mais je pense qu’il n’y a pas de mal à coder de manière pragmatique pour des projets moins exigeants. Les Facades sont très rapides à utiliser et, à moins d’un immense projet complexe, elles ne poseront pas de problèmes, à mon avis.
// Envoyer un mail
Mail::to($sendTo)->send(new UserListMail($users));
// Vérifier une permission
Gate::allows('admin');
// Chercher un Tag
Tag::where('name', $tagName)->first();
// Render un template
View::make('blog._rss');
Cependant, j’ai découvert en ligne que ce sujet est source de débats passionnés, ce qui m’était totalement inconnu avant d’entamer la rédaction de cet article ! Certains développeurs critiquent l’utilisation des façades, les considérant comme une violation du principe de l’état global ainsi que les principes SOLID. D’autres les défendent pour leur aspect pratique, privilégiant une approche pragmatique plutôt que théorique. J’ai trouvé les discussions dans ce post reddit très intéressantes sur le sujet. En fin de compte, Laravel offre la flexibilité de choisir entre les deux approches, ce qui est un point fort certain. On n’est pas contraint à un modèle unique, et cette liberté permet d’adapter son style de codage en fonction du contexte et des préférences personnelles.
Passons ! Pour en revenir au sujet de cet article, je dirais qu’un autre avantage indéniable de Laravel réside dans sa popularité mondiale.
Grâce à une communauté active, ainsi qu’à une quantité impressionnante de contenu en ligne, chaque problème rencontré trouve généralement sa solution dans un article de blog, un tutoriel vidéo ou un forum de discussion.Je repense notamment à la liste des bonnes pratiques que j’ai partagé plus haut. Cette entraide communautaire facilite grandement l’apprentissage et le dépannage, et constitue un atout majeur pour tout développeur, novice ou expérimenté.
Enfin, comment parler des points forts de Laravel sans mentionner son écosystème. Je n’ai pas encore eu l’occasion d’essayer tous les packages proposés sur la page d’accueil de Laravel, mais lire toutes leurs descriptions fait déjà saliver à l’idée de tout ce que l’on peut réaliser avec.
Pour l’instant, je n’ai d’expérience qu’avec Laravel Breeze (authentification), Laravel Prompts (création d’interfaces en ligne de command) et Livewire (création d’interfaces dynamiques). Laravel Pint (formateur de code) aussi qui m’a permis de voir à quoi doit ressembler du beau code Laravel, ce qui m’a un peu surpris au début. Vous l’avez peut-être remarqué dans certains codeblock que j’ai partagé au dessus, les points-virgules ne demandent pas de retour à la ligne après avoir enchaîné des méthodes. Ces packages se sont intégrés facilement à mon projet pour lui apporter rapidement de nouvelles fonctionnalités sans effort supplémentaire. Je pense qu’il est donc possible de construire des projets fonctionnels en imbriquant plus de ces packages, et ce de manière rapide et bien faite.
En parcourant l’écosystème Laravel, j’ai également découvert des packages prometteurs tels que Pulse (monitoring) et Cashier (gestion des abonnements et des paiements avec Stripe), qui m’auraient été d’une grande utilité dans certains de mes projets Symfony passés.
Section intitulée les-points-faibles-de-laravelLes points faibles de Laravel
La magie de Laravel, bien que séduisante au premier abord, peut rapidement devenir un véritable casse-tête lors du débogage. L’absence d’un outil de profilage intégré, tel que le Profiler de Symfony, complique l’analyse des performances et du comportement de l’application.
Bien que Laravel Telescope puisse combler cette lacune, son installation, qui nécessite des commandes et des migrations supplémentaires, peut sembler excessive pour une fonctionnalité de débogage. D’ailleurs Telescope vient sans debugbar, il faut en installer une externe comme celle-ci qui semble copier celle de Symfony.
La différence entre Telescope et Profiler cependant, c’est que Telescope peut aussi s’utiliser en production pour monitorer pleins de choses sur son site que des outils externes ne pourrait pas forcément voir.
Sur une installation sans Telescope, la page d’erreur de Laravel reste lisible et compréhensive, mais je trouve la stacktrace généralement moins claire que ce que je peux avoir en Symfony. Il y manque également des informations que le Profiler fournit (mais que Telescope intègre).
Un autre aspect qui m’a un peu déçu est la gestion des formulaires. Les formulaires Laravel, bien que fonctionnels, manquent cruellement de la puissance et de la flexibilité offertes par les FormType de Symfony. Ces derniers permettent de créer des formulaires complexes en quelques lignes de code, avec une gestion automatique de la validation et de l’affichage des erreurs.
En comparaison, les formulaires Laravel semblent rudimentaires et nécessitent souvent beaucoup d’écriture de code HTML. Leur “puissance” vient surtout de la manière dont la requête est ensuite traitée.
Voici un exemple de form en Laravel classique :
<form action="{{ route('admin.post_update', $post) }}" method="POST">
@csrf
@method('PATCH')
<div>
@error('title')
{{ $message }}
@enderror
@error('slug')
{{ $message }}
@enderror
<label for="title">Title</label>
<input type="text" name="title" id="title" value="{{ old('title') ?? $post->title }}">
</div>
<div>
@error('summary')
{{ $message }}
@enderror
<label for="summary">Summary</label>
<input name="summary" id="summary" value="{{ old('summary') ?? $post->summary }}">
<small class="text-gray-500">No Markdown here</small>
</div>
// Un tas d’autres champs ici
<div>
<button type="submit">
Submit
</button>
</div>
</form>
Ce form ressemble finalement beaucoup à du HTML tout ce qu’il y a de plus classique. Cependant en utilisant Livewire, il est possible de donner du panache à ces formulaires en les transformant en Composants. Cela permet d’ajouter beaucoup de features UX telles que de la validation en temps réel, des indicateurs de chargement, du submit en temps réel et plus.
Il existe aussi des packages pour leur donner des features semblables aux FormType, tel que filament ou laravel-form-bridge (qui reprend directement les FormType Symfony). Mais ce sont des packages externes, non intégrés au framework. On voit bien ici la force de la communauté de Laravel.
En ce qui concerne la validation des données, bien qu’elle soit très puissante, elle m’a semblé un peu moins intuitive et plus verbeuse que celle à laquelle je suis habitué. L’absence d’attributs de validation, qui permettent de définir les règles directement sur les propriétés des entités, oblige parfois à répéter ces règles à plusieurs endroits.
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return Gate::allows('admin');
}
public function rules(): array
{
return [
'title' => [
'required',
'string',
'max:255',
'unique:posts,slug',
],
'summary' => 'required|string|max:255',
'content' => 'required|string|min:10',
'published_at' => 'required|date',
];
}
public function messages(): array
{
return [
'title.unique' => 'post.slug_unique',
'slug.unique' => 'post.slug_unique',
'summary.required' => 'post.blank_summary',
'content.required' => 'post.blank_content',
'content.min' => 'post.too_short_content',
'published_at.after' => 'post.invalid_date',
];
}
}
Cette duplication de code peut, à mon sens, rendre la validation plus difficile à maintenir et à faire évoluer. Il est possible de faire de la validation à la volée grâce à des attributs en passant par Livewire, mais ça n’est à mes yeux toujours pas l’égal de la validation au niveau des entités que propose Symfony.
Section intitulée conclusionConclusion
En conclusion, mon incursion dans l’univers Laravel en tant que développeur Symfony novice a été une expérience très enrichissante. Les deux frameworks, bien que partageant le même socle technologique, proposent des approches radicalement différentes.
Symfony brille par sa structure rigoureuse, ses outils puissants et sa capacité à gérer des projets complexes. Je le choisirais peut-être en préférence pour les applications de très grande envergure.
Laravel, quant à lui, séduit par sa simplicité, sa rapidité de développement et son écosystème riche. Je pense que ça en fait un choix idéal pour aller plus vite sur des projets de taille plus raisonnable et les prototypes. Cet article posté sur le blog il y a 9 ans maintenant semblait déjà tirer une conclusion similaire. J’y rajouterais cependant qu’aujourd’hui, avec tous les packages officiels qui forment son écosystème, je pense qu’il est plus que possible de construire des projets grandioses avec Laravel.
Pour mettre en lumière ces différences et mieux comprendre les forces et les faiblesses de chaque framework, j’ai réalisé un petit projet reprenant les grandes lignes de la démo Symfony : https://github.com/jolicode/laravel-demo.
Ce projet m’a ouvert les yeux sur la diversité des approches possibles en matière de développement web et m’a encouragé à explorer de nouvelles voies. J’ai par ailleurs découvert ce guide officiel pour se lancer sur Laravel, mais qu’une fois mon projet terminé, je le conseille fortement pour se lancer.
Fort de cette expérience, je suis impatient d’approfondir ma connaissance de Laravel et de découvrir les nombreuses possibilités qu’il offre. Cette aventure m’a donné envie de continuer à apprendre et à expérimenter, et c’est bien là l’essentiel.
Commentaires et discussions
Initialiser rapidement une API REST avec Laravel
Dernièrement, nous avons développé une API REST pour l’un de nos projets clients. Une des exigences était de développer cette API avec Laravel 5. Elle était accompagnée d’une application cliente développée au sein du même projet Laravel avec Blade ainsi que quelques composants React.…
Lire la suite de l’article Initialiser rapidement une API REST avec Laravel
Laravel, le petit dernier qui a appris de ses aînés
Chez JoliCode, nous aimons (beaucoup, passionnément, à la folie) Symfony, et à titre personnel, j’aime beaucoup aussi. Mais il y a un moment que je m’intéresse aussi à Laravel. Laravel est un framework dont nous entendons beaucoup parler dans l’univers PHP. Mais il y a peu de ressources…
Lire la suite de l’article Laravel, le petit dernier qui a appris de ses aînés