PSR, PHP Standard Recommendation

Vous connaissez le terme PSR, mais savez-vous réellement de quoi ça parle ?

Jérémy 🤘
Jérémy 🤘

Vous connaissez le terme PSR, mais savez-vous réellement de quoi ça parle ? C'est justement ce que nous allons voir aujourd'hui justement.

Définition

Le PSR, pour PHP Standard Recommendation, est un ensemble de recommandations pour la programmation PHP publiée par le FIG (Framework Interoperability Group). Il permet d'améliorer l'interopérabilité entre les frameworks mais aussi d'uniformiser les syntaxes et le formatage du code.

Echosystème

PSR workflow
PSR workflow

  • Pre-Draft: Discussion autour du concept afin de valider si oui ou non il est intéressant de travailler dessus
  • Draft: Travail sur le nouveau standard jusqu'à ce qu'il soit prêt à être présenté devant le Core Commitee
  • Review: Phase de tests pendant 4 semaines (minimum), et si 2 implémentations viables sont réussies, le standard peut passer au vote d'acceptation
  • Accepted: Accepté lors du vote par le Core Commitee, le standard devient officiel
  • Deprecated: Si le standard n'est plus pertinent ou s'il est remplacé par un autre
  • Abandoned: Suite à un vote du Core Commitee ou s'il n'y a plus d'activité pendant plus de 6 mois

PSR1 & PSR2 : Coding Style

PSR1 et PSR2 proposent des standards sur le formatage du code avec notamment, le nombre d'espaces, le nommage des classes, la ponctuation ... Ces standards sont par défaut dans la configuration de phpcs par exemple.

Copier
<?php

namespace Vendor\Package;

use FooInterface;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;

class Foo extends Bar implements FooInterface
{
    public function sampleMethod($a, $b = null)
    {
        if ($a === $b) {
            bar();
        } elseif ($a > $b) {
            $foo->bar($arg1);
        } else {
            BazClass::bar($arg2, $arg3);
        }
    }

    final public static function bar()
    {
        // method body
    }
}
<?php

namespace Vendor\Package;

use FooInterface;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;

class Foo extends Bar implements FooInterface
{
    public function sampleMethod($a, $b = null)
    {
        if ($a === $b) {
            bar();
        } elseif ($a > $b) {
            $foo->bar($arg1);
        } else {
            BazClass::bar($arg2, $arg3);
        }
    }

    final public static function bar()
    {
        // method body
    }
}

PSR3 : Logger Interface

Le PSR3 définis une interface pour la gestion les logs avec 8 méthodes pour les différents niveaux et une générique.

Copier
<?php

namespace Psr\Log;

interface LoggerInterface
{
    public function emergency(string $message, array $context = []): void;

    public function alert(string $message, array $context = []): void;

    public function critical(string $message, array $context = []): void;

    public function error(string $message, array $context = []): void;

    public function warning(string $message, array $context = []): void;

    public function notice(string $message, array $context = []): void;

    public function info(string $message, array $context = []): void;

    public function debug(string $message, array $context = []): void;

    public function log($level, string $message, array $context = []): void;
}
<?php

namespace Psr\Log;

interface LoggerInterface
{
    public function emergency(string $message, array $context = []): void;

    public function alert(string $message, array $context = []): void;

    public function critical(string $message, array $context = []): void;

    public function error(string $message, array $context = []): void;

    public function warning(string $message, array $context = []): void;

    public function notice(string $message, array $context = []): void;

    public function info(string $message, array $context = []): void;

    public function debug(string $message, array $context = []): void;

    public function log($level, string $message, array $context = []): void;
}
Copier
<?php

namespace Psr\Log;

class LogLevel
{
    const EMERGENCY = 'emergency';
    const ALERT     = 'alert';
    const CRITICAL  = 'critical';
    const ERROR     = 'error';
    const WARNING   = 'warning';
    const NOTICE    = 'notice';
    const INFO      = 'info';
    const DEBUG     = 'debug';
}
<?php

namespace Psr\Log;

class LogLevel
{
    const EMERGENCY = 'emergency';
    const ALERT     = 'alert';
    const CRITICAL  = 'critical';
    const ERROR     = 'error';
    const WARNING   = 'warning';
    const NOTICE    = 'notice';
    const INFO      = 'info';
    const DEBUG     = 'debug';
}
Copier
<?php

namespace Psr\Log;

interface LoggerAwareInterface
{
    public function setLogger(LoggerInterface $logger);
}
<?php

namespace Psr\Log;

interface LoggerAwareInterface
{
    public function setLogger(LoggerInterface $logger);
}

PSR4 : Autoloading

Le PSR4 remplace le PSR0, qui est maintenant deprecated. Il permet de définir comment un fichier est chargé automatiquement grâce à son namespace. Exemple si on définit que le namespace App est dans le dossier src il va charger automatiquement tout le reste.

Chemin Namespace
src/Foo/Bar \App\Foo\Bar

PSR6 : Caching Interface

Le PSR6 définis 2 interfaces et 2 exceptions permettant une gestion de cache.

Copier
<?php

namespace Psr\Cache;

interface CacheItemInterface
{
    public function getKey(): string;

    public function get();

    public function isHit(): bool;

    public function set($value): self;

    public function expiresAt(?\DateTimeInterface $expiration): self;

    public function expiresAfter($time): self;
}
<?php

namespace Psr\Cache;

interface CacheItemInterface
{
    public function getKey(): string;

    public function get();

    public function isHit(): bool;

    public function set($value): self;

    public function expiresAt(?\DateTimeInterface $expiration): self;

    public function expiresAfter($time): self;
}
Copier
<?php

namespace Psr\Cache;

interface CacheItemPoolInterface
{
    public function getItem(string $key): CacheItemInterface;

    public function getItems(array $keys = []): \Traversable;

    public function hasItem(string $key): bool;

    public function clear(): bool;

    public function deleteItem(string $key): bool;

    public function deleteItems(array $keys): bool;

    public function save(CacheItemInterface $item): bool;

    public function saveDeferred(CacheItemInterface $item): bool;

    public function commit(): bool;
}
<?php

namespace Psr\Cache;

interface CacheItemPoolInterface
{
    public function getItem(string $key): CacheItemInterface;

    public function getItems(array $keys = []): \Traversable;

    public function hasItem(string $key): bool;

    public function clear(): bool;

    public function deleteItem(string $key): bool;

    public function deleteItems(array $keys): bool;

    public function save(CacheItemInterface $item): bool;

    public function saveDeferred(CacheItemInterface $item): bool;

    public function commit(): bool;
}

PSR7 : Http Message Interface

Le PSR7 définit 7 interfaces :

  • MessageInterface: Interface générique de gestion d'un message (protocole, en-têtes et corps)
  • RequestInterface: Étend de MessageInterface, ajout des cibles, méthode et URL
  • ServerRequestInterface: Étend de RequestInterface, gestion des paramètres GET et POST, cookies, fichiers uploadés
  • ResponseInterface: Étend de MessageInterface, représentation de la réponse à retourner, ajout d'un statusCode
  • StreamInterface: Représentation d'un flux de données
  • UriInterface: Représentation d'un lien (schéma, nom d'hôte et chemin)
  • UploadedFileInterface: Représentation d'un fichier uploadé et utilisé dans ServerRequestInterface

PSR11 : Container Interface

PSR11, avec l'arrivée du pattern Dependency Injection, la nécessité de devoir passer par un container pour gérer tout cela s'est posée.

Copier
<?php

namespace Psr\Container;

interface ContainerInterface
{
    public function get(string $id);

    public function has(string $id): bool;
}
<?php

namespace Psr\Container;

interface ContainerInterface
{
    public function get(string $id);

    public function has(string $id): bool;
}

Le PSR13 définis comment représenter un lien hypermédia via l'interface LinkInterface et la gestion d'un lien via EvolvableLinkInterface.

Copier
<?php

namespace Psr\Link;

interface LinkInterface
{
    public function getHref(): string;

    public function isTemplated(): bool;

    public function getRels(): array;

    public function getAttributes(): array;
}
<?php

namespace Psr\Link;

interface LinkInterface
{
    public function getHref(): string;

    public function isTemplated(): bool;

    public function getRels(): array;

    public function getAttributes(): array;
}
Copier
<?php

namespace Psr\Link;

interface EvolvableLinkInterface extends LinkInterface
{
    public function withHref(string $href): self;

    public function withRel(string $rel): self;

    public function withoutRel(string $rel): self;

    public function withAttribute(string $attribute, string $value): self;

    public function withoutAttribute(string $attribute): self;
}
<?php

namespace Psr\Link;

interface EvolvableLinkInterface extends LinkInterface
{
    public function withHref(string $href): self;

    public function withRel(string $rel): self;

    public function withoutRel(string $rel): self;

    public function withAttribute(string $attribute, string $value): self;

    public function withoutAttribute(string $attribute): self;
}

PSR15 : HTTP Server Request Handlers

PSR15, avec l'arrivée du design pattern Middleware, le PSR7 devait donc être mis à jour et apporte la représentation d'un middleware. Il se place entre la Request et la Response.

Un exemple simple pour comprendre le fonctionnement est celui d'un firewall. Nous voulons autoriser l'accès à notre application uniquement pour une IP ou une plage. Nous n'allons pas rajouter avant chacune de nos routes cette détection. Et bien le middleware se greffe entre les 2 couches de l'application afin de faire son travail.

Middleware, image Slim Framework
Middleware, image Slim Framework

PSR16 : Simple Cache

Le PSR16 est une simplification du PSR6. Il fusionne les concepts de Pool et Item dans une seule interface CacheInterface.

Copier
<?php

namespace Psr\SimpleCache;

interface CacheInterface
{
    public function get(string $key, $default = null);

    public function set(string $key, $value, $ttl = null): bool;

    public function delete(string $key): bool;

    public function clear(): bool;

    public function getMultiple(string $keys, $default = null): iterable;

    public function setMultiple(iterable $values, $ttl = null): bool;

    public function deleteMultiple(iterable $keys): bool;

    public function has(string $key): bool;
}
<?php

namespace Psr\SimpleCache;

interface CacheInterface
{
    public function get(string $key, $default = null);

    public function set(string $key, $value, $ttl = null): bool;

    public function delete(string $key): bool;

    public function clear(): bool;

    public function getMultiple(string $keys, $default = null): iterable;

    public function setMultiple(iterable $values, $ttl = null): bool;

    public function deleteMultiple(iterable $keys): bool;

    public function has(string $key): bool;
}

PSR17 : HTTP Factories

PSR17, encore une extension du PSR7 qui permet de ne plus être dépendant d'une implémentation de Request ou Response et de retourner notre propre implémentation.

PSR18 : HTTP Client

PSR18, car le PSR7 était encore incomplet, ce standard rajoute la possibilité de créer des bibliothèques découplées des implémentations HTTP Client afin de les rendre réutilisables mais aussi d'utiliser le principe de substitution de Liskov.

PSR à venir (draft)

  • PSR5 & PSR19: PHPDoc Standard
    • Uniformiser la documentation des fonctionnalités de PHP
  • PSR12: Extended Coding Style Guide
    • Extension du PSR1 et PSR2
  • PSR14: Event Manager
    • Arrivée du design pattern Observer
    • Uniformiser le système d’événements

PSR abandonnés (abandoned)

  • PSR8: Huggable Interface
  • PSR9 & PSR10: Security Advisories & Reporting
    • Destiné aux éditeurs de frameworks/librairies
    • Définis le processus pour dévoiler et reporter un problème de sécurité