Un projet PHP écrit à la main finit toujours par accumuler une longue liste d'inclusions en tête de fichier, dont personne ne sait plus lesquelles sont encore nécessaires. L'autoload PSR-4 avec Composer supprime cette liste d'un coup : les classes sont chargées au moment où elles sont utilisées, à partir d'une correspondance simple entre leur espace de noms et leur emplacement sur le disque. Ce n'est pas une technique réservée aux cadriciels, c'est un standard de l'écosystème PHP que n'importe quel projet peut adopter en une demi heure, y compris un projet ancien. Le gain immédiat est la disparition des inclusions manuelles ; le gain durable est une organisation des fichiers qui devient auto documentée, puisque le chemin d'une classe se déduit de son nom et réciproquement.
Ce que PSR-4 impose réellement
La norme tient en quelques règles, bien plus simples que sa réputation ne le laisse croire. Elle décrit uniquement comment un chargeur automatique doit transformer un nom de classe pleinement qualifié en chemin de fichier, et rien d'autre : elle n'impose ni structure de projet, ni cadriciel, ni style de code. C'est la première brique posée quand nous reprenons un projet existant dans la rubrique création de site Internet, parce qu'elle conditionne tout le travail d'organisation qui suivra.
La correspondance entre nom et chemin
Le principe est une substitution de préfixe. On déclare qu'un préfixe d'espace de noms correspond à un dossier de base ; le reste du nom de la classe, après ce préfixe, devient le chemin relatif à l'intérieur de ce dossier, chaque séparateur d'espace de noms devenant un séparateur de dossier, et le nom de la classe devenant le nom du fichier suivi de son extension. Une classe déclarée dans un espace de noms correspondant à un dossier source se trouvera donc toujours à un emplacement prévisible, et un développeur qui lit un nom de classe sait immédiatement où ouvrir le fichier, sans chercher.
Un fichier, une classe, même nom
La contrainte pratique est stricte : chaque fichier ne doit contenir qu'une seule classe, et son nom doit correspondre exactement à celui de la classe, casse comprise. Cette exigence en dérange certains, habitués à regrouper plusieurs petites classes dans un même fichier, mais elle est la condition d'un chargement automatique sans scan préalable. Les rares cas où plusieurs déclarations doivent cohabiter, notamment les fonctions et les constantes globales, se traitent par des fichiers explicitement listés à part, chargés systématiquement plutôt que découverts par le nom.
La casse, qui ne pardonne pas
Le développement se fait souvent sur un système de fichiers indifférent à la casse et le déploiement sur un serveur qui, lui, la respecte scrupuleusement. Une classe dont le fichier s'appelle differemment de la classe elle même, ne serait ce que par une majuscule, fonctionnera parfaitement en local et provoquera une erreur fatale en production. C'est la panne la plus fréquente lors du premier déploiement, et elle se prévient par un contrôle automatique en intégration plutôt que par la vigilance, qui échoue toujours au bout de quelques semaines.
Un chargement paresseux, pas un scan
La différence entre PSR-4 et les anciennes conventions tient au moment où le fichier est lu. Rien n'est parcouru au démarrage : le chargeur ne fait strictement rien tant qu'aucune classe inconnue n'est demandée, et se contente alors de calculer un chemin et de tenter de lire ce fichier précis. Le coût d'une requête ne dépend donc pas du nombre total de classes du projet mais du nombre de classes réellement utilisées, ce qui est une propriété très différente des chargeurs qui indexent un dossier au démarrage. Sur un projet de plusieurs centaines de classes dont une page n'en mobilise qu'une douzaine, l'écart est considérable, et il explique pourquoi cette approche s'est imposée sur des projets de toutes tailles.
Ce que la norme ne dit pas
PSR-4 ne prescrit ni les noms de dossiers, ni le découpage en couches, ni le nombre de niveaux d'espaces de noms. Rien n'oblige à reproduire l'arborescence d'un cadriciel connu, et un projet de taille moyenne se porte très bien avec quatre ou cinq espaces de noms de premier niveau correspondant à ses domaines réels. La tentation d'imiter une structure vue ailleurs produit en général des dossiers vides et des niveaux inutiles, qui allongent les noms de classes sans rien apporter à la lisibilité.

Déclarer l'autoload dans Composer
Composer n'est pas seulement un gestionnaire de dépendances : il génère aussi le chargeur automatique, et il sait le faire pour le code du projet lui même, pas uniquement pour les bibliothèques installées. C'est le point que beaucoup ignorent, et qui permet d'utiliser Composer sur un projet sans aucune dépendance externe. Ce raccordement complète directement l'organisation exposée dans notre article sur la manière de structurer un projet PHP sans framework.
La section de configuration
Tout tient dans une section du fichier de configuration de Composer, qui associe un préfixe d'espace de noms à un chemin de dossier relatif à la racine du projet. Plusieurs préfixes peuvent cohabiter, ce qui permet par exemple de séparer le code applicatif, un module de compatibilité et un jeu d'utilitaires partagés. Un même préfixe peut aussi pointer vers plusieurs dossiers, fonctionnalité utile lors d'une migration progressive, quand une partie des classes a déjà déménagé et pas le reste. Après toute modification de cette section, il faut régénérer le chargeur, sans quoi rien ne change.
Le fichier à inclure, et une seule fois
Le chargeur généré s'active par une unique inclusion, placée au tout début de chaque point d'entrée du projet : le contrôleur frontal, mais aussi les scripts en ligne de commande, les tâches planifiées et les points d'entrée annexes comme un gestionnaire de médias ou un point de réception de notifications. Oublier cette inclusion dans un point d'entrée secondaire produit une erreur qui n'apparaît que dans ce contexte précis, souvent des semaines plus tard, et qui déroute d'autant plus que le reste du site fonctionne parfaitement.
Les fichiers chargés systématiquement
Certaines choses ne sont pas des classes et ne peuvent donc pas être découvertes par leur nom : des fonctions utilitaires globales, des constantes de configuration, un fichier de compatibilité qui définit des alias. Composer prévoit pour cela une liste de fichiers à inclure inconditionnellement au démarrage. Cette liste doit rester courte, car tout ce qui s'y trouve est chargé à chaque requête, y compris quand rien ne l'utilise. Trois ou quatre entrées sont raisonnables, une vingtaine trahit du code qui aurait dû devenir des classes.
Le dossier des dépendances, à ne pas verser au dépôt
Composer installe les bibliothèques tierces dans un dossier dédié qui n'a pas vocation à être versionné : ce sont les deux fichiers de configuration et de verrouillage qui font foi, et le dossier se reconstitue à l'identique à partir d'eux. Le versionner alourdit inutilement le dépôt, produit des différentiels illisibles et masque les mises à jour réellement effectuées. Le fichier de verrouillage, en revanche, doit impérativement être versionné et déployé, faute de quoi deux installations successives peuvent obtenir des versions différentes des mêmes bibliothèques, ce qui est la source classique du comportement qui diffère entre la préproduction et la production.
Séparer le code de développement
Une seconde section, distincte, permet de déclarer des espaces de noms qui n'existent qu'en développement : jeux de tests, données de démonstration, outils d'analyse. Cette séparation n'est pas cosmétique, elle évite que du code de test se retrouve chargeable en production, où il n'a rien à faire et où il constitue une surface d'attaque supplémentaire. Le déploiement doit alors être fait avec l'option qui exclut ces dépendances et cette section, ce qui suppose que la commande de déploiement soit écrite quelque part et pas retapée de mémoire.
| Besoin | Mécanisme Composer | Coût à l'exécution |
|---|---|---|
| Charger les classes du projet | Correspondance PSR-4 | Un accès disque au premier usage |
| Charger du code sans espace de noms | Classmap sur un dossier | Aucun, la carte est précalculée |
| Charger des fonctions globales | Liste de fichiers inclus | À chaque requête, systématique |
| Code réservé au développement | Section de développement | Nul en production si exclu |
| Accélérer en production | Carte de classes optimisée | Aucun accès de recherche |
| Migration progressive | Plusieurs dossiers par préfixe | Une recherche par dossier déclaré |
Migrer un projet existant sans tout casser
La situation la plus courante n'est pas le projet neuf mais le projet ancien, avec ses centaines de fichiers dépourvus d'espaces de noms et ses inclusions manuelles disséminées. La bonne nouvelle est que la migration peut être progressive, les deux mécanismes cohabitant sans difficulté, et qu'elle peut s'arrêter à tout moment sans laisser le projet dans un état intermédiaire inutilisable. Elle demande simplement de connaître un peu le langage sous jacent, dont nous rappelons les principes dans notre article expliquant ce qu'est PHP et comment il fonctionne.
Commencer par la carte de classes
La première étape ne demande aucune modification du code existant. Composer sait générer une carte de classes en parcourant un dossier et en y recensant toutes les classes déclarées, quels que soient leurs noms de fichiers et leur absence d'espace de noms. Cette carte remplace immédiatement toutes les inclusions manuelles de classes, et permet de supprimer des dizaines de lignes en tête de fichiers sans rien réécrire. Le seul inconvénient est qu'il faut régénérer la carte à chaque ajout de classe, ce qui est acceptable pour un code existant qui bouge peu.
Introduire les espaces de noms par le nouveau code
La deuxième étape consiste à écrire tout le nouveau code dans un espace de noms propre, déclaré en PSR-4 sur un dossier neuf, sans toucher à l'ancien. Les deux mondes coexistent, le code moderne pouvant appeler l'ancien directement, l'ancien appelant le nouveau par son nom pleinement qualifié. Cette cohabitation dure aussi longtemps qu'il le faut, et c'est ce qui rend la démarche réaliste : aucune journée de migration à bloquer, aucune régression massive à craindre, chaque fonctionnalité nouvelle améliore mécaniquement la proportion de code organisé.
Déplacer l'ancien code par lots cohérents
La troisième étape, facultative, consiste à faire migrer l'ancien code par ensembles fonctionnels plutôt que fichier par fichier. Un lot correspond à un domaine identifiable, par exemple tout ce qui touche aux envois de courriel, et se traite en une passe : ajout de l'espace de noms, renommage du fichier, correction des appels, exécution des tests s'ils existent, contrôle manuel sinon. Traiter un lot par semaine sur un projet moyen le fait basculer entièrement en quelques mois, sans jamais immobiliser l'équipe ni retarder les livraisons.
Traiter les classes générées à l'exécution
Certains projets créent des classes à la volée, par évaluation de code ou par génération de fichiers dans un dossier de cache. Ces classes ne peuvent par construction figurer dans aucune carte précalculée, et elles obligent à conserver un mécanisme de repli. Le plus propre consiste à leur réserver un espace de noms distinct et un dossier dédié, déclaré séparément, plutôt que de renoncer à l'optimisation pour l'ensemble du projet à cause de quelques classes particulières. Cette séparation rend au passage visible une part du code qui, autrement, échappe complètement à la lecture d'un nouvel arrivant.
Garder un filet pendant la transition
Pendant la migration, il est prudent de conserver une compatibilité pour les noms de classes qui changent, sous forme d'alias déclarés dans un fichier chargé systématiquement. Ce filet évite qu'un appel oublié dans un coin du code ne produise une erreur fatale en production, et se retire une fois la migration terminée. Le retirer est important : un filet permanent finit par masquer des références obsolètes que plus personne n'ose supprimer, ce qui reconstitue exactement le désordre que la migration devait supprimer.
Répartition observée sur des migrations de projets de taille moyenne. La configuration proprement dite représente la plus petite part du travail, l'essentiel étant du démêlage de dépendances existantes.
Optimiser et déployer
Le chargement automatique a un coût, faible mais réel, et ce coût se paie à chaque requête. Sur un site à fort trafic, la différence entre une configuration par défaut et une configuration optimisée se mesure, sans exiger le moindre changement de code. Encore faut il connaître les options et ne pas les activer au mauvais endroit.
Les trois niveaux d'optimisation
Composer propose trois niveaux, qu'il faut distinguer. Le premier convertit toutes les règles PSR-4 en une carte de classes explicite, ce qui supprime la recherche sur le disque : c'est sans risque et recommandé partout. Le deuxième ajoute une mémorisation des recherches infructueuses, ce qui n'a d'intérêt que si le code déclenche fréquemment des tentatives de chargement de classes inexistantes. Le troisième interdit tout repli sur la recherche dynamique, ce qui accélère encore mais casse tout chargement de classe générée à l'exécution : à réserver aux projets qui n'en produisent aucune.
Régénérer au bon moment
La régénération du chargeur fait partie du déploiement et doit être scriptée, jamais lancée à la main. L'ordre compte : on installe les dépendances sans le code de développement, on génère le chargeur optimisé, puis on bascule le site sur la nouvelle version. Générer après la bascule laisse une fenêtre pendant laquelle le site tourne avec une carte de classes qui ne correspond plus aux fichiers présents, ce qui produit des erreurs fatales intermittentes particulièrement difficiles à diagnostiquer parce qu'elles disparaissent d'elles mêmes.
Le cache d'opcodes et le rechargement
Le gain d'une carte de classes optimisée se combine avec le cache d'opcodes du serveur, qui garde en mémoire la version compilée des fichiers PHP. Ce cache doit être vidé ou invalidé au déploiement, faute de quoi le serveur continue d'exécuter l'ancien code pendant plusieurs minutes. Sur une infrastructure à plusieurs serveurs, cette invalidation doit être faite partout, et l'oublier sur une seule machine produit un comportement aléatoire selon le serveur qui répond, ce qui rend le diagnostic déroutant.
Vérifier que rien n'est resté en chemin
Le contrôle final consiste à s'assurer qu'aucune inclusion manuelle de classe ne subsiste et qu'aucune classe n'est déclarée hors des dossiers couverts. Une recherche textuelle sur les instructions d'inclusion suffit pour la première partie ; pour la seconde, comparer la liste des classes déclarées dans le projet avec le contenu de la carte générée révèle immédiatement les fichiers oubliés. Ce contrôle prend deux minutes et se place utilement dans la chaîne d'intégration, où il empêche toute régression silencieuse de revenir par la porte d'à côté.
Documenter la convention retenue
Le dernier travail est d'écrire noir sur blanc, dans le dépôt, la correspondance retenue entre espaces de noms et dossiers, et la raison de chaque découpage. Une page suffit. Sans elle, chaque nouvel intervenant invente sa propre lecture de l'arborescence, place ses classes là où cela lui semble logique, et la structure se délite en quelques mois sans que personne n'ait pris de mauvaise décision isolément. Cette note est aussi ce qui permet de trancher rapidement les questions de rangement qui surgissent à chaque fonctionnalité un peu transversale, au lieu de les rouvrir à chaque fois. Elle rend aussi le passage à un chargement automatique conforme beaucoup plus simple le jour où le projet grossit, puisque la correspondance entre les espaces de noms et les dossiers y figure déjà noir sur blanc.
💬 0 commentaires
✍️ Laisser un commentaire
Créez un compte gratuit ou connectez-vous pour commenter.