Clonez un projet Symfony que vous n'avez jamais vu, et vous tombez sur une douzaine de dossiers aux noms courts : bin, config, public, src, var. La bonne nouvelle : ils sont toujours là, toujours au même endroit, avec toujours le même rôle. Cette régularité n'est pas de la bureaucratie, c'est une carte. Une fois qu'on l'a en tête, on arrête de chercher où poser son code.
Ce billet fait le tour du propriétaire, dossier par dossier : à quoi sert chacun, lesquels on ne touche jamais, et où regarder quand ça plante. À la fin, vous saurez lire l'arborescence d'un projet inconnu en un coup d'œil, et savoir d'instinct où va une nouvelle classe.
Vue d'ensemble
Voici l'ossature d'un projet Symfony récent, réduite à l'essentiel :
mon-projet/
├── bin/
│ └── console
├── config/
│ ├── packages/
│ ├── routes.yaml
│ └── services.yaml
├── public/
│ └── index.php
├── src/
│ ├── Controller/
│ ├── Entity/
│ ├── Repository/
│ └── Kernel.php
├── templates/
├── tests/
├── var/
│ ├── cache/
│ └── log/
├── vendor/
├── .env
├── composer.json
└── composer.lockToute la logique de ce rangement tient en une phrase : chaque dossier a une responsabilité, et une seule. Votre code d'un côté, la configuration de l'autre, le code des autres encore ailleurs. Voici la vue d'ensemble avant la visite pièce par pièce.
| Dossier | Rôle | On modifie ? |
|---|---|---|
bin/ |
exécutables (la console) | rarement |
config/ |
comportement de l'application | oui |
public/ |
racine web exposée | surtout les assets |
src/ |
votre code métier | tout le temps |
templates/ |
les vues Twig | oui |
tests/ |
les tests automatisés | oui |
var/ |
cache et journaux générés | jamais (mais on vide le cache) |
vendor/ |
le code des dépendances | jamais |
bin/ : la console
Ce dossier contient les exécutables de l'application. Le seul que vous manipulez au quotidien s'appelle console. C'est la porte d'entrée vers Symfony en ligne de commande :
php bin/console cache:clear
php bin/console make:controller
php bin/console debug:routerVider un cache, générer un contrôleur, lister les routes, inspecter le conteneur de services : presque tout passe par lui. Un réflexe utile quand on débute : php bin/console list affiche toutes les commandes disponibles, et debug:* regroupe les commandes qui répondent à la question « qu'est-ce que Symfony a compris de ma config ? ».
config/ : la configuration
C'est ici qu'on règle le comportement de l'application, sans toucher à son code. Trois entrées à connaître :
routes.yaml: le plan de circulation, quelle URL mène à quel contrôleur.services.yaml: le câblage interne, l'injection de dépendances.packages/: la configuration de chaque brique tierce (Doctrine, Security, Twig).
Le format par défaut est le YAML. Sa syntaxe est lisible, mais elle se paie : l'indentation fait partie du sens. Une espace de trop ou de moins, et Symfony refuse de démarrer avec un message parfois obscur.
public/ : la racine web exposée
public/ est le seul dossier que le serveur web expose au monde. Il contient index.php, le contrôleur frontal : ce fichier reçoit chaque requête HTTP, démarre le noyau Symfony et renvoie la réponse. Les assets compilés (CSS, JavaScript, images publiques) y vivent aussi.
Le point à retenir dépasse la simple organisation. Tout ce qui n'est pas dans public/ est injoignable depuis un navigateur. Votre code, votre configuration, vos secrets restent hors de portée par construction. C'est une frontière de sécurité, pas juste un dossier.
Zoom sur les assets
AssetMapper : le frontend de Symfony sans Node ni bundler
src/ : votre code métier
Voici votre espace de travail, la logique propre à votre projet. Symfony n'impose pas grand-chose ici, mais une organisation par responsabilité s'est imposée dans la communauté.
Controller
Il accueille la requête, comprend l'intention, appelle les bons services et retourne une réponse. On le veut mince : il coordonne, il ne calcule pas. Dès qu'un contrôleur se met à contenir de la logique métier, c'est le signe qu'il faut extraire un service.
Pour aller plus loin
Arrêtez les God Controllers, passez à l'ADR
Entity et Repository
Une Entity est la représentation objet d'une donnée. Avec Doctrine, chaque entité correspond en général à une table. Le Repository associé est l'endroit où vivent les requêtes : c'est lui qui sait retrouver les objets, en DQL ou via le QueryBuilder. L'entité porte la donnée, le repository sait la chercher.
Service
Le travailleur spécialisé. Envoi d'e-mail, calcul de facture, transformation de données : dès qu'une logique est réutilisable ou un peu complexe, elle va dans un service. C'est ce qui garde le reste du code découplé et testable.
Sujets connexes
L'injection de dépendance, ou comment être fainéant avec élégance
EventListener et EventSubscriber
Les observateurs. Ils réagissent à un événement (« utilisateur connecté », « commande payée ») sans modifier le code qui l'émet. Très pratique, à doser : trop d'écouteurs invisibles rendent le flux d'exécution difficile à suivre.
templates/ : les vues Twig
Ici règne Twig, le moteur de gabarits de Symfony. Au-delà de générer du HTML, il apporte deux choses. La sécurité d'abord, avec l'échappement automatique des variables, qui coupe court à la majorité des failles d'injection dans les vues. La modularité ensuite, avec l'héritage et l'inclusion de gabarits, qui évitent de copier-coller le même en-tête partout.
tests/ : les tests
Ce dossier reflète src/. Il abrite deux familles de tests. Les tests unitaires vérifient une classe isolée, souvent un service. Les tests fonctionnels simulent de vraies requêtes HTTP et vérifient que l'application répond correctement de bout en bout. L'un valide une pièce, l'autre valide le bâtiment en marche.
var/ : cache et journaux
var/ est ce que Symfony génère pour tourner. Deux sous-dossiers comptent :
cache/: Symfony y compile sa configuration pour aller vite. On ne l'édite jamais à la main, mais on le vide sans hésiter avecphp bin/console cache:clearquand un comportement devient inexplicable.log/: les journaux de l'application. C'est le premier endroit où regarder quand quelque chose casse.
vendor/ : les dépendances
vendor/ contient le code des autres : Symfony lui-même, Doctrine, Monolog, et toutes vos dépendances. Il est géré de bout en bout par Composer. On n'y modifie jamais un fichier, car la moindre retouche saute à la prochaine mise à jour. Ce dossier ne se committe pas non plus : il se reconstruit tout seul à partir de composer.json et composer.lock.
Pour tout ce qui touche à vendor/ et Composer
Composer : le manifeste du développeur moderne
Les fichiers à la racine
Trois fichiers méritent votre attention à la racine du projet :
composer.json: le manifeste du projet, la liste de ce dont il a besoin.composer.lock: les versions exactes réellement installées, pour que la production soit identique au poste de développement..env: les variables d'environnement (base de données, clés d'API), souvent surchargées par un.env.localnon versionné pour les secrets.
Ce que ce billet ne dit pas
On est resté sur la carte, pas sur chaque recoin. La mécanique interne des bundles, la référence complète de la configuration, l'ordre de résolution des variables d'environnement, les recettes Symfony Flex : autant de sujets qui méritent leur propre visite. Ici, l'objectif était de savoir se repérer, pas de tout démonter.
Se repérer, c'est déjà maîtriser
Cette régularité a un nom chez Symfony : la convention plutôt que la configuration. Le prix à payer est d'accepter une carte qu'on n'a pas dessinée. Le gain est immense : n'importe quel développeur ouvre n'importe quel projet Symfony et sait, en trente secondes, où vit le code métier, où se règle la configuration, et où regarder quand ça brûle.
La prochaine fois que vous récupérez un projet inconnu, ne commencez pas par le code. Ouvrez son src/ et son config/, et demandez-vous : est-ce que je saurais dire ce que fait cette application avant même de lire une ligne de logique ?
Vous vous repérez ?
Une coquille, une erreur dans ce billet ? Signale-la-moi.