PSR mon amour : 50 nuances de conventions

Temps de lecture : 18 min

Découvrez les PSR en PHP, ces standards qui promettent de transformer notre code et de nous permettre de faire revenir l'être aimé, faire le café et ranger la forêt. Style, autoload, logs, HTTP : tout y passe !

Bienvenue dans le monde merveilleux des PSR sont ces fameuses PHP Standard Recommendations qui nous promettent amour, gloire et beauté. Officiellement, il s’agit d’un ensemble de standards visant à améliorer les pratiques de programmation en PHP et l’interopérabilité entre les frameworks . En gros, le PHP-FIG (Framework Interop Group), sorte d’ONU des développeurs PHP, s’est dit un jour : « Et si on essayait de faire ranger sa chambre à la communauté PHP ? » – comprendre, fixer des règles pour que tout le monde code un peu pareil.

FIG, kézako ? Le PHP-FIG est un groupe fondé en 2009 rassemblant des lead dev de frameworks PHP. Leur but affiché : « trouver des moyens de mieux travailler ensemble » . Avouons que c’est plus sympa que de se taper à coup de pelle éternellement sur des histoires d’accolades ou de tabulations.

Au fil du temps, ce cénacle a pondu une série numérotée de recommandations baptisées PSR-1, PSR-2, etc., censées apporter la paix et l’harmonie dans le code… ou pas…

Certaines sont devenues incontournables, d’autres ont fini au cimetière des bonnes idées comme le fait si bien Google.

PSR-1 : Les bases du code PHP

La PSR-1, adoptée en 2012, est le point de départ : le B.A.-BA pour coder proprement en PHP. Elle pose les normes de codage de base requises pour une interopérabilité technique optimale . En clair, c’est une checklist des bonnes manières dès qu’on écrit un fichier PHP :

En résumé, PSR-1 c’est un peu le code de la route du dev PHP : des règles élémentaires pour éviter les accidents de lecture de code. Par exemple, voici un mini-code respectant PSR-1 :

<?php
namespace MonProjet\Util;

class MaSuperClasse {
    const VERSION = '1.0';
    public function faisUnTruc() {
        // ...
    }
}

Ici, on a bien nos tags <?php d’ouverture, un namespace MonProjet\Util cohérent avec l’arborescence, un nom de classe MaSuperClasse en StudlyCaps, une constante VERSION en majuscules, et une méthode faisUnTruc() en camelCase. Ouf, l’honneur est sauf.

La PSR-1, c’est un peu comme apprendre à dire « bonjour » et « merci » en société. Basique, mais quand on voit certains vieux scripts PHP dans la nature, on se dit que ça ne fait pas de mal de le rappeler. Allez, range ta chambre, développeur, et n’oublie pas de fermer la porte avec un ?>… ou plutôt non, on ne ferme pas la balise PHP en fin de fichier si y’a que du PHP (ça c’est aussi une bonne pratique implicite, merci). En tout cas, avec PSR-1, plus d’excuse pour les constantes nommées MaConstanteSecrete en camelCase : ici c’est MA_CONSTANTE_SECRETE, et en majuscules s’il vous plaît !

PSR-2 : Guide de style de codage

Si la PSR-1 est la base, PSR-2 vient fignoler la présentation. Publiée initialement en 2012, elle a fixé tout un tas de détails de mise en forme du code pour réduire ce qu’ils appellent la “friction cognitive” quand on lit du code de plusieurs auteurs . En gros, que le code soit écrit par Alice ou Bob, il devrait ressembler à du code PHP standardisé, histoire qu’on passe plus de temps à comprendre la logique qu’à buter sur du formatage bizarre.

Quelles règles le grand livre PSR-2 nous assène-t-il ? Voici le best-of des exigences  :

Et la liste continue sur la longueur des lignes, l’absence de whitespace en fin de ligne, une seule instruction par ligne, etc. Tout un poème. Pour illustrer, voici à quoi ressemble un code conforme PSR-2 :

class Demo
{
    public function doStuff($x, $y = null)
    {
        if ($x === $y) {
            echo "égal";
        } elseif ($x > $y) {
            echo "plus grand";
        } else {
            echo "plus petit";
        }
    }
}

Observez les accolades : ouvrante à la ligne suivante pour la classe et la fonction, indentations de 4 espaces, pas de tabulations, des if bien espacés… On pourrait presque sortir la loupe pour vérifier qu’il n’y a pas une espace en trop ou manquante. PSR-2, c’est un peu l’instituteur maniaque du style de code qui te tape sur les doigts quand tu mets ta virgule au mauvais endroit. Mais l’intention est louable : que notre code, à défaut d’être parfait, ait au moins bonne mine et cohérence d’un projet à l’autre .

PSR-2 a été pendant longtemps la star des linter PHP. Mais (plot twist) en 2019, on l’a déclarée obsolète et remplacée par sa grande sœur, PSR-12 . Eh oui, même les standards vieillissent : PSR-2 nous interdisait rien de spécifique sur les typed properties ou le strict_types (inconnus à l’époque), et globalement elle a bien fait son job jusqu’à ce que PHP évolue. Ne vous inquiétez pas, on enchaîne avec PSR-12 juste après.

En attendant, si vous croisez un dev qui chipote sur une accolade mal alignée, dites-lui gentiment : « Namasté ma gueule, l’esprit de la PSR-2 est en toi, jeune padawan du code bien formaté ».

PSR-3 : Logger Interface

Passons à autre chose que le style visuel : PSR-3, publiée en 2013, qui standardise la journalisation (logging) des applications PHP. En clair, elle définit une interface commune pour les bibliothèques de log . Pourquoi ? Pour permettre à n’importe quel bout de code (framework ou lib tierce) de balancer ses messages de log au travers d’un logger central sans se soucier de l’implémentation derrière . Un peu comme un standard universel du « note ça quelque part ».

Concrètement, la PSR-3 introduit l’interface Psr\Log\LoggerInterface avec huit méthodes correspondant aux fameux niveaux de log (débbugage, info, notice, warning, error, critical, alert, emergency) . Oui, huit niveaux, rien que ça – du petit pépin (debug) à « tout est en flammes » (emergency). Pour les indécis, il y a même une neuvième méthode log($level, $message, $context) qui prend en paramètre le niveau souhaité . En gros, « appelle-moi avec le niveau que tu veux, on gère ». Si tu t’amuses à passer un niveau inconnu, ça doit lancer une InvalidArgumentException – parce qu’il y a des limites au freestyle, hein.

Voici un exemple simple d’utilisation de PSR-3 dans la nature :

use Psr\Log\LoggerInterface;

function traiterTruc(LoggerInterface $logger, $donnee) {
    $logger->info("Traitement de la donnée en cours", ['data' => $donnee]);
    // ... suite du traitement ...
    if (!$donnee) {
        $logger->warning("Donnée vide, attention !"); 
    }
}

Ici, la fonction accepte n’importe quel LoggerInterface. Du coup, tu peux lui passer un Monolog, un Zend\Log, bref ce que tu veux du moment que ça respecte PSR-3. On utilise $logger->info() pour noter une info avec un contexte (un tableau associatif ['data' => $donnee]), ou $logger->warning() pour signaler un petit souci. Grâce à PSR-3, le code n’est pas couplé à une implémentation précise : le jour où tu changes de librairie de log, pas besoin de tout refactorer, victoire.

PSR-3 prévoit aussi un système de placeholders dans les messages ({placeholder}), remplacés par les valeurs du contexte fourni . Pratique pour éviter de concaténer des strings en dur dans les logs. Et bonus : l’interface s’accompagne de quelques utilitaires comme Psr\Log\LogLevel (une classe avec les constantes de niveaux) et un NullLogger (logger poubelle qui ne fait rien, pour ceux qui veulent désactiver les logs proprement) .

PSR-3, c’est un peu le coach thérapeutique des devs PHP. Il t’encourage à verbaliser tes problèmes (sous forme de messages de log) plutôt que de tout garder pour toi. Est-ce que ça rend ton code plus propre ? Disons que ça évite surtout de réinventer la roue à chaque fois qu’on veut écrire un log. Et franchement, standardiser le logging c’est pas plus mal : avant PSR-3, chacun avait sa petite fonction logMeThat($msg) perso dans un coin. Maintenant, plus d’excuse, tu fais ->info() comme tout le monde et tu restes calme. Jordi Boggiano (Mr Composer) est d’ailleurs l’éditeur de cette PSR , on peut donc remercier la communauté d’avoir confié le journal intime du PHP à quelqu’un de confiance. Allez, on respire, on loggue, et on passe à la suite.

PSR-4 : Autoloading 2.0

Après s’être occupé du style et des logs, revenons à un sujet plus architectural : le chargement automatique des classes, alias autoload. PSR-4, approuvée en 2014, est LA spécification moderne pour autoloader les classes PHP à partir des chemins de fichiers . En fait, elle remplace le vieux PSR-0 (le standard d’autoload originel) qui a été déclaré obsolète car un peu trop contraignant et vieillot . PSR-4 est plus souple, plus propre, bref le nouveau boss de l’autoload.

Le principe de PSR-4 est simple : on définit une correspondance entre un namespace de base et un répertoire de base. Ensuite, la structure des sous-namespaces reflète la structure des sous-dossiers, et le nom de la classe détermine le nom du fichier .php. Dit comme ça, c’est abstrait, prenons un exemple concret :

Supposons que dans ton fichier de config (par exemple le composer.json de ton projet), tu déclares que le namespace "MonAppli" correspond au dossier src/. Du coup :

PSR-4 précise également que les noms de classes sont sensibles à la casse (fini les casse-tête Windows vs Linux, tu respectes la casse partout) . Contrairement à PSR-0, les underscores _ n’ont plus de signification spéciale dans les noms de classes (sous PSR-0, un underscore simulait un séparateur de namespace à l’ancienne). En gros, PSR-4 dit : « Arrange tes dossiers selon tes namespaces et tout ira bien ».

Un petit exemple de configuration Composer pour illustrer ça :

{
    "autoload": {
        "psr-4": {
            "MonAppli\\": "src/"
        }
    }
}

Avec ce réglage, si quelque part tu fais new \MonAppli\Controleur\Accueil(), Composer (ou tout autoloader PSR-4) saura automatiquement qu’il doit inclure le fichier src/Controleur/Accueil.php (contenant la classe MonAppli\Controleur\Accueil). Plus besoin de dizaines de require_once écrits à la main partout. On définit la règle, et l’autoloader se charge du boulot.

La PSR-4, c’est un peu le Marie Kondo du PHP – elle te force à ranger tes fichiers bien comme il faut, et en échange, elle promet que ton code « t’apportera de la joie » (ou au moins moins d’erreurs d’inclusion). Les projets modernes en PHP adoptent quasi tous PSR-4 : Composer l’a intégré nativement, donc tu respectes PSR-4 sans même le savoir quand tu utilises Composer pour charger tes classes. Et si jamais tu repenses avec nostalgie à l’autoload PSR-0, dis-toi que PSR-4 est juste mieux fichu (plus besoin de nommer les classes Truc_SousTruc pour refléter l’arborescence). On vit dans le futur, rangeons nos classes de manière PSR-4 et arrêtons de pleurer le passé.

PSR-7 : HTTP message interfaces

On arrive à PSR-7, standard approuvé en 2015, qui a fait couler pas mal d’encre (ou de pixels). PSR-7 définit une série d’interfaces pour représenter les messages HTTP (requêtes et réponses) de façon universelle . En gros, au lieu d’avoir chaque framework avec sa classe Request maison incompatible avec celle du voisin, le PHP-FIG a dit : « On va créer LA façon commune de gérer une requête et une réponse HTTP en PHP ». Dit comme ça, super ambitieux.

PSR-7 introduit 7 interfaces principales :

Ouf, ça en fait du monde ! En pratique, tu n’implémentes pas ça toi-même à la main (sauf si tu aimes souffrir), tu utilises une bibliothèque qui fournit des classes concrètes implémentant ces interfaces (par ex. Guzzle HTTP, Laminas Diactoros, Slim PSR-7…). L’intérêt, c’est que si tout le monde utilise ces interfaces, tu peux échanger les composants (middleware, clients HTTP, frameworks) plus facilement.

Un aspect clé (et polémique) de PSR-7 : elle insiste sur l’immutabilité des objets Request/Response. Toutes les méthodes qui modifient quelque chose (par ex withHeader(), withStatus()…) ne changent pas l’objet courant mais retournent une nouvelle instance modifiée. L’idée est d’éviter les effets de bord imprévus en cours de route. C’est joli sur le papier fonctionnel, mais devine quoi ? Symfony (via son composant HttpFoundation) faisait tout l’inverse depuis des lustres – des requêtes modifiables à foison. Du coup, PSR-7 a provoqué un sérieux débat, car elle demandait à Symfony de renier sa philosophie ou de faire un pont tordu entre les deux mondes . Au final, Symfony a créé une couche d’adaptation pour convertir ses Request/Response en PSR-7 et vice-versa . Ambiance « on fait la grimace mais on s’adapte ». Preuve que même une Recommandation Standard peut se heurter aux réalités du terrain.

Voyons rapidement comment on pourrait utiliser PSR-7 dans un code :

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

function greetUser(ServerRequestInterface $request): ResponseInterface {
    $name = $request->getQueryParams()['name'] ?? 'toi';
    // Créer une réponse (via une implémentation concrète, ex: GuzzleHTTP)
    $response = new \GuzzleHttp\Psr7\Response();
    $response->getBody()->write("Bonjour $name !");
    return $response->withHeader('Content-Type', 'text/plain');
}

Ici, la fonction greetUser peut recevoir n’importe quelle requête implémentant ServerRequestInterface (que ça vienne de Zend Diactoros, Slim, etc.). On extrait un paramètre de query string name (via getQueryParams()), on crée une réponse PSR-7 (en utilisant Guzzle par exemple pour la classe concrète), on écrit dans le corps « Bonjour X ! », et on renvoie la réponse en ajoutant un header de type de contenu. Remarquez le petit $response->withHeader(...) qui retourne une nouvelle instance de réponse avec le header ajouté, au lieu de modifier l’objet original – c’est l’immutabilité en action.

L’avantage de PSR-7, c’est que toutes les libs et frameworks qui l’adoptent parlent le même langage HTTP. Tu peux prendre un middleware de Slim, le brancher dans Zend Expressive (aujourd’hui Laminas), et ça marche, car ils manipulent tous des RequestInterface et ResponseInterface standard. On a enfin une brique commune dans l’écosystème pour tout ce qui touche aux requêtes/réponses HTTP .

PSR-7, c’est un peu la révolution copernicienne qu’on a voulu imposer à tous, avec ce slogan : « Désormais, la Terre (le code) tournera autour du Soleil (PSR-7) ! ». Sauf que certains avaient déjà leur propre système solaire (coucou Symfony) et ont un peu grincé des dents. On peut voir PSR-7 comme un standard brillant mais exigeant, qui a nécessité des concessions. Heureusement, il a ouvert la voie à un écosystème de middlewares et de micro-frameworks hyper cool. Et puis bon, admettons-le, manipuler ces objets immuables, ça donne l’impression d’être un puriste de la propreté du code – un petit frisson hype pas désagréable pour les développeurs en mal de design pattern.

PSR-11 : Container Interface

On change de registre avec PSR-11, approuvée en 2017, qui standardise l’interface des containers d’injection de dépendances. Dit simplement, un container c’est cette boîte magique où on enregistre nos instances de services, et qu’on interroge pour obtenir tel ou tel objet dont on a besoin. Chaque framework avait sa façon de faire, PSR-11 a dit : « fini le bazar, on va tous utiliser la même interface pour nos conteneurs » .

ContainerInterface, défini par PSR-11, est d’une simplicité biblique : deux méthodes seulement ! Un get($id) pour récupérer un objet à partir d’un identifiant (généralement le nom ou classe du service), et un has($id) pour vérifier si le service existe dans le conteneur . C’est tout. Pas de méthode pour ajouter ou configurer des entrées – la PSR-11 se concentre uniquement sur l’utilisation du container, pas sur sa construction. Pourquoi ? Parce que chaque conteneur (Symfony, Laravel, PHP-DI, etc.) a ses propres recettes pour enregistrer les services (fichiers de config YAML, annotations, autowiring…). Standardiser ça aurait été mission impossible ou trop restrictif. Alors ils ont choisi le plus petit dénominateur commun : comment fetcher une dépendance de manière uniforme .

Un petit exemple pour la route :

use Psr\Container\ContainerInterface;

function utiliseCache(ContainerInterface $container) {
    if ($container->has('cache')) {
        $cache = $container->get('cache');
        $cache->clear();  // On appelle une méthode du service cache
    }
}

Ici, on ne sait pas si on nous a refilé un container Symfony, Laravel, Pimple ou autre chose implémentant ContainerInterface. On s’en fiche : on peut appeler ->has('cache') et ->get('cache') de manière standard. Le code sera compatible tant que le conteneur suit PSR-11. Pour info, Symfony’s DependencyInjectionContainer, Laravel’s container, PHP-DI, etc., tous implémentent désormais PSR-11 (soit nativement, soit via un adaptateur). Ça permet par exemple à une lib tierce de dire « donnez-moi n’importe quel conteneur PSR-11, je saurai m’en servir pour récupérer mes dépendances » .

Attention, PSR-11 a aussi ses détracteurs sur le plan philosophique : certains considèrent que d’utiliser le container directement dans le code (genre $container->get() partout) c’est un anti-pattern (Service Locator), et qu’on devrait lui préférer l’injection de dépendances “pure” (via les constructeurs par exemple). D’ailleurs, le meta document de la PSR-11 le dit noir sur blanc : « on ne recommande pas de passer le container aux objets pour qu’ils aillent chercher eux-mêmes leurs dépendances » . Le container doit idéalement rester dans les coulisses (framework) et injecter ce qu’il faut où il faut, pas devenir omniprésent dans ton code métier.

PSR-11, c’est un peu comme standardiser la prise électrique pour tous les chargeurs de téléphone. Sur le papier, c’est super : plus de galère, tout le monde s’alimente pareil (ici tout le monde obtient ses objets pareil). Mais ça ne dit pas comment on fabrique l’électricité derrière (comment les instances sont configurées). Au moins, avec PSR-11, fini les conteneurs propriétaires ingérables : tu peux toujours dire “je fournis un ContainerInterface”, c’est passe-partout. Et si tu abuses du container partout, tes collègues viendront sûrement te rappeler (sarcastiquement) que *« oui, c’est standard, mais n’en fait pas ta béquille ! ». Bref, PSR-11 c’est la neutralité : juste ce qu’il faut pour brancher la prise, pas plus.

PSR-12 : Le guide de style étendu

Souvenez-vous de PSR-2, notre guide de style maniaque. Eh bien PSR-12, validée en 2019, est venue l’étendre, l’expanser et la remplacer . En effet, entre 2012 et 2019, PHP a beaucoup évolué (arrivée de PHP 7, de nouvelles syntaxes, etc.), donc il fallait mettre à jour les règles de style pour coller à l’époque moderne. PSR-12 reprend toutes les règles de PSR-2, et y ajoute des précisions pour les nouveautés de PHP et quelques durcissements de ton.

Qu’est-ce que PSR-12 a de plus que sa maman PSR-2 ? Quelques exemples notables :

En résumé, PSR-12 est un superset de PSR-2 qui adapte le style de code aux réalités de PHP 7+ et même 8. Elle est devenue la référence actuelle pour tout l’outillage (PHP_CodeSniffer et consorts l’intègrent par défaut maintenant, en lieu et place de PSR-2).

Un petit exemple de code qui illustre l’esprit PSR-12 :

<?php
declare(strict_types=1);

namespace MonProjet\LeFutur;

use MonProjet\Truc\{Tool, Utilisateur as User};
use function MonProjet\Utils\helperFunction;

class MaNouvelleClasse extends MaAncienneClasse implements MonInterface
{
    public function traiteDonnee(int $valeur): ?string
    {
        if ($valeur === 0) {
            return null;
        }
        // Autre traitement...
        return (string) $valeur;
    }
}

On y voit : la déclaration strict_types en haut (imposée sur sa propre ligne), l’usage du grouping dans les use (plusieurs classes importées depuis MonProjet\Truc{…}), le type hint int et le return type ?string collés sans espace avant les deux-points (règle PSR-12 oblige), etc. Visuellement, ça reste proche de PSR-2 mais c’est mis à jour aux nouveautés syntaxiques.

On pourrait dire que PSR-12, c’est la version director’s cut de PSR-2 – plus longue, plus complète, pour les vrais fans du détail. Certains développeurs grognons diront « Pff, encore des règles, toujours des règles ». Mais avouons que c’est pratique d’avoir une référence à jour. Et puis bon, on peut en rire : PSR-12 a même standardisé l’art d’écrire une liste d’use dans le bon ordre, si ça ce n’est pas du perfectionnisme… On imagine les réunions enflammées du PHP-FIG pour décider s’il faut une ligne vide entre tel et tel block. Oui, c’est arrivé. Mais ne tirons pas trop sur l’ambulance : grâce à ces maniaques, on a des codebases plus homogènes. Un dev expérimenté suivra probablement déjà 90% de PSR-12 sans s’en rendre compte (à force, on a intégré ces conventions). Les 10% restants serviront de sujet de blague au prochain meetup (« Alors, t’as mis ta ligne vide après ton declare(strict_types=1) ou quoi ? »).

Autres PSR notables en vrac

La liste des PSR ne s’arrête pas à 12, et d’autres recommandations méritent un petit clin d’œil, soit parce qu’elles sont utiles, soit parce qu’elles sont… particulières. En voici quelques-unes supplémentaires, sur le ton léger bien sûr :

On s’arrête là pour le tour d’horizon ! Comme vous le voyez, les PSR couvrent désormais un large spectre de sujets dans l’écosystème PHP. On a même un PSR-20 (Clock) qui a été récemment accepté pour standardiser l’accès à l’horloge (genre pour la tester facilement).

Le mot de la fin

Alors, ces standards PHP, révolution ou poudre aux yeux ?. D’un côté, les PSR ont clairement amélioré la vie des développeurs PHP en alignant nos violons : fini les guerres de style (bon, OK, presque finies…), fini les autoloads incompatibles, fini les mille et une façons d’injecter un service ou de logger un truc. L’interopérabilité a fait un bond en avant, et PHP a gagné en maturité grâce à ces recommandations suivies massivement . On peut coder dans Laravel le matin, faire du Symfony l’après-midi, et on n’est pas totalement dépaysé parce que la base du code est familière (merci PSR-1/2/12). C’est un fait.

D’un autre côté… quelle ironie de voir un langage autrefois qualifié de “foireux” par les puristes devenir l’un des plus normés ! Le FIG a beau dire que ce ne sont que des recommandations, la pression communautaire fait que si tu ne les suis pas, tu passes un peu pour la tanche la moins oxygénée de la rivière. Qui oserait aujourd’hui sortir un gros projet open-source PHP en mettant ses accolades n’importe comment ou en ignorant PSR-4 ? À tes risques et périls de te faire huer sur X par les ayatollahs du code style. Il y a même un adage moqueur qu’on pourrait appliquer : « L’avantage des standards, c’est qu’il y en a tellement parmi lesquels choisir ». PHP a désormais les siens, et gare à celui qui en invente un 23e pour faire son intéressant.

Au final, on peut en rire : les PSR, c’est un peu la Convention de Genève du développeur PHP. On s’est mis d’accord pour ne plus se torturer mutuellement avec du code immonde, on a signé les accords (en gros, on a cliqué “phpcs –standard=PSR12” dans nos IDE) et on essaye de s’y tenir. Évidemment, comme toute convention, il y a les frondeurs et les tatillons. Certains vont chipoter sur la moindre déviation (le chasseur de trailing space, on le connaît tous), d’autres vont coder en cowboy façon 2005 en disant « vos PSR, j’en fais du compost ». Mais globalement, le bénéfice est là : on se comprend mieux entre devs PHP, on partage des packages sans trop se battre, bref on a un langage commun au-delà du langage PHP lui-même.

Les PSR ont apporté de la discipline dans le chaos qu’était PHP à ses débuts, sans (trop) tuer la créativité. On peut toujours coder comme on l’entend à l’intérieur des fonctions, mais au moins sur la forme externe, on parle la même langue. Et si tout ça vous paraît bien sérieux, rappelez-vous qu’ils ont quand même essayé de nous faire gober une interface de câlins (PSR-8) – preuve qu’il reste de l’humour et de l’absurde dans ce monde de conventions.