Quand on développe avec Symfony dans VS Code, Zed ou Neovim, on finit facilement par oublier une chose : notre éditeur ne comprend pas réellement notre application.
Il affiche du PHP, colore Twig, complète quelques méthodes et permet de faire un Ctrl+clic sur une classe. C’est déjà très pratique. Mais dès qu’on traverse la frontière entre PHP, Twig, Doctrine, les routes ou les services Symfony, cette belle intelligence commence parfois à ressembler davantage à une série de suppositions très bien maquillées.
C’est précisément ce que Symfony cherche à améliorer avec Symfony Language Tools, son nouveau Language Server officiel.
Et derrière ce nom légèrement austère se cache finalement une idée beaucoup plus intéressante qu’une simple nouvelle extension pour éditeur : donner à nos outils de développement une véritable compréhension de Symfony.
Un Language Server, c’est quoi au juste ?
Le terme peut donner l’impression qu’il faut encore installer un serveur quelque part, ouvrir trois ports et ajouter un conteneur Docker à son compose.yaml. Heureusement, non.
Un Language Server est simplement un programme qui tourne en arrière-plan et dont le travail consiste à comprendre le code que vous êtes en train d’écrire.
Votre éditeur, lui, reste principalement responsable de l’interface : afficher les fichiers, gérer les onglets, positionner le curseur et vous permettre de changer de thème pendant vingt minutes au lieu de corriger le bug prévu.
Lorsqu’il a besoin d’informations sur le code, il peut interroger un Language Server.
Imaginons que nous ayons ceci :
$user->getEmail();
Quand le curseur arrive sur getEmail(), l’éditeur peut demander :
Éditeur :
"Qu'est-ce que getEmail() ?"
Language Server :
"C'est une méthode de App\Entity\User.
Elle retourne une string.
Elle est déclarée dans User.php ligne 42."
L’éditeur n’a plus qu’à afficher cette information dans une infobulle ou à ouvrir le fichier lorsque vous faites Ctrl+clic.
Le Language Server est donc en quelque sorte le cerveau spécialisé derrière certaines fonctions intelligentes de l’éditeur.
Pourquoi appeler ça un « serveur » ?
Parce que l’éditeur lui envoie des requêtes et qu’il lui répond. Rien de beaucoup plus mystérieux.
Il ne s’agit pas nécessairement d’un serveur distant et encore moins d’un service accessible sur Internet. Dans le cas classique, le programme tourne tranquillement sur votre machine à côté de l’éditeur.
┌─────────────┐
│ VS Code │
└──────┬──────┘
│
│ "C'est quoi cette méthode ?"
▼
┌───────────────────┐
│ Language Server │
└──────┬────────────┘
│
│ "Voilà sa définition."
▼
┌─────────────┐
│ VS Code │
└─────────────┘
Pour que les deux puissent communiquer, ils utilisent généralement un protocole standard : le Language Server Protocol, ou LSP.
Et c’est là que l’idée devient particulièrement élégante.
L’éditeur et l’intelligence sont séparés
Historiquement, chaque éditeur pouvait développer ses propres mécanismes pour comprendre chaque langage. VS Code avait son implémentation, Vim avait la sienne, un autre IDE encore une autre.
Avec LSP, on sépare les responsabilités.
VS Code ───┐
Zed ───────┼──── LSP ──── Language Server
Neovim ────┘
Le même programme spécialisé peut donc potentiellement fournir ses connaissances à plusieurs éditeurs.
Votre éditeur sait afficher une autocomplétion. Le Language Server sait quoi mettre dans cette autocomplétion.
Votre éditeur sait afficher une erreur avec une jolie vague rouge. Le Language Server peut lui expliquer qu’il y a effectivement une erreur à cet endroit.
Votre éditeur sait ouvrir un fichier lorsque vous faites Ctrl+clic. Le Language Server peut lui indiquer quel fichier ouvrir et à quelle ligne.
Cette distinction est importante pour comprendre ce que Symfony vient apporter.
Comprendre PHP n’est pas comprendre Symfony
Un excellent outil d’analyse PHP peut très bien comprendre ceci :
class Product
{
public function getPrice(): int
{
// ...
}
}
Il connaît la classe, sa méthode, son type de retour et les endroits où elle est utilisée directement en PHP.
Mais une application Symfony ne se limite évidemment pas à du PHP.
Dans un template Twig, on peut rencontrer :
{{ product.price }}
{{ path('product_show', {id: product.id}) }}
{{ asset('images/logo.svg') }}
<twig:ProductCard product="{{ product }}" />
Pour nous, développeurs Symfony, tout cela paraît parfaitement logique.
Nous savons que product correspond probablement à un objet PHP. Nous savons que product_show désigne une route Symfony. Nous savons que asset() référence un fichier et que ProductCard peut correspondre à un composant Twig.
Mais pour un outil qui analyse simplement du PHP, ce sont essentiellement des chaînes de caractères perdues dans un autre langage.
Et une chaîne de caractères, aussi sympathique soit-elle, ne raconte pas énormément de choses.
C’est précisément là que Symfony Language Tools devient intéressant
Symfony Language Tools ajoute une couche de connaissance spécifique au framework.
Il ne cherche plus seulement à répondre à la question :
"Que signifie ce code PHP ?"
Il cherche aussi à répondre à :
"Que signifie ce code dans CETTE application Symfony ?"
Et la nuance est énorme.
Le serveur peut connaître les métadonnées de l’application, ses services, ses composants Twig, ses entités Doctrine et différents éléments découverts lorsque Symfony démarre réellement.
On commence donc à obtenir quelque chose qui ressemble davantage à ceci :
ÉDITEUR
│
│ LSP
▼
Symfony Language Tools
│
┌──────────┼──────────┐
▼ ▼ ▼
PHP Twig Symfony
│
┌─────────┼─────────┐
▼ ▼ ▼
Doctrine Routes Services
│
▼
Application
L’éditeur ne voit plus simplement une collection de fichiers indépendants. Il commence à voir les relations qui constituent réellement l’application.
Quand Twig cesse d’être une boîte noire
C’est probablement l’exemple le plus parlant.
Prenons une fonction Twig personnalisée :
{{ product_price(product) }}
Pour un éditeur qui connaît uniquement la syntaxe Twig, product_price est simplement un nom de fonction valide.
Avec Symfony Language Tools, il devient possible de relier cette fonction à son implémentation PHP.
L’éditeur peut alors proposer son nom pendant la saisie, afficher sa signature au survol ou permettre de faire Go to Definition pour arriver directement sur le code PHP correspondant.
Symfony Language Tools sait également exploiter les types déclarés dans les templates Twig :
{% types {
product: 'App\\Entity\\Product',
featured?: 'boolean',
} %}
À partir de là, product n’est plus simplement « une variable Twig appelée product ».
Elle possède un sens.
product
│
▼
App\Entity\Product
│
├── name
├── price
├── description
└── ...
L’autocomplétion peut donc devenir beaucoup plus pertinente et la navigation entre Twig et PHP beaucoup plus naturelle.
On réduit progressivement cette frontière un peu artificielle entre « le backend » et « les templates » que notre cerveau, lui, traversait déjà sans problème.
La différence entre analyser du code et comprendre une application
C’est probablement le véritable intérêt de cette nouveauté.
Symfony Language Tools ne repose pas uniquement sur l’analyse statique des fichiers. Pour certaines informations, il peut démarrer réellement l’application Symfony afin de récupérer ses métadonnées runtime. C’est notamment ce qui lui permet de mieux comprendre des éléments provenant de Doctrine, Twig ou des bundles installés.
Et c’est une différence fondamentale.
Une application Symfony est en partie décrite par son code PHP, mais également par sa configuration, son conteneur de services, ses bundles, ses métadonnées Doctrine et tout ce que le framework construit au démarrage.
Deux projets contenant exactement la même ligne Twig peuvent donc lui donner un sens complètement différent.
Pour comprendre correctement Symfony, il ne suffit pas toujours de lire les fichiers. Il faut parfois demander directement à Symfony ce qu’il a construit.
Votre éditeur devient alors une fenêtre sur Symfony
Le changement peut sembler discret parce qu’il se manifeste par des choses que nous utilisons déjà quotidiennement : autocomplétion, documentation au survol, Ctrl+clic, recherche des références, renommage ou diagnostics.
Mais derrière ces fonctions banales, le niveau de compréhension peut devenir très différent.
Quand vous écrivez :
{{ product.price }}
l’éditeur peut progressivement savoir que :
product
↓
variable Twig
↓
App\Entity\Product
↓
entité Doctrine
↓
champ price
↓
déclaration PHP / mapping Doctrine
Et potentiellement vous permettre de naviguer dans cette chaîne sans avoir à rechercher manuellement chaque élément.
C’est exactement le genre de fonctionnalité qui paraît anecdotique sur une démo de trente secondes, puis devient insupportablement indispensable après quelques semaines d’utilisation.
Et peu importe presque quel éditeur on utilise
C’est également pour cela que le choix d’un Language Server est intéressant.
Symfony Language Tools n’est pas intrinsèquement une fonctionnalité de VS Code. Symfony développe le moteur de compréhension, puis différents éditeurs peuvent communiquer avec lui grâce à LSP.
Le projet fonctionne déjà avec VS Code et Neovim, dispose désormais d’une extension pour Zed et le serveur standalone peut être utilisé par d’autres clients LSP.
Symfony investit donc dans la connaissance du framework, pas dans un éditeur particulier.
Et cette séparation devient encore plus intéressante avec l’arrivée massive des assistants de programmation et des agents capables de modifier directement un projet.
Un outil qui sait qu’une chaîne dans Twig correspond réellement à une fonction PHP possède évidemment beaucoup plus de contexte qu’un outil qui voit simplement deux fichiers vaguement liés.
Ce n’est encore qu’une bêta
Il ne faut toutefois pas imaginer que Symfony Language Tools comprend déjà chaque recoin de chaque application Symfony créée depuis 2005.
Le projet est encore en bêta. Après sa première semaine, Symfony annonçait déjà dix releases en six jours et expliquait tester désormais le serveur sur de véritables applications comme Kimai, Mautic, Sulu, Sylius ou Shopware.
Et c’est probablement une bonne manière de développer ce genre d’outil.
Comprendre une application de démonstration parfaitement rangée est une chose. Comprendre un projet Symfony de huit ans contenant du YAML, du XML, trois générations de conventions et un bundle dont personne n’ose supprimer le dossier est une autre discipline.
Finalement, une petite révolution assez discrète
Symfony Language Tools n’ajoute pas une nouvelle commande spectaculaire à Symfony et ne change pas la manière dont nous construisons une application.
Il travaille à un endroit beaucoup moins visible : entre notre code et notre éditeur.
Et c’est justement ce qui rend le projet intéressant.
Jusqu’ici, notre éditeur pouvait être excellent pour comprendre PHP, correct pour comprendre Twig et relativement malin grâce à différentes extensions. Symfony cherche maintenant à lui fournir une vision beaucoup plus globale de l’application.
Avant :
éditeur → fichiers → PHP / Twig / YAML
Avec un Language Server Symfony :
éditeur
↓
Symfony Language Tools
↓
compréhension des relations
↓
application Symfony
La promesse n’est donc pas simplement d’avoir « une meilleure autocomplétion Twig ».
La vraie idée est beaucoup plus intéressante : faire en sorte que l’éditeur comprenne progressivement la même chose que le développeur lorsqu’il regarde son projet.
Et si demain un Ctrl+clic depuis un obscur appel Twig nous emmène exactement dans la bonne classe PHP au lieu de nous laisser fouiller le projet avec grep, personne ne se plaindra que le Language Server manque un peu de poésie.