PHP 7.1 est sorti le 1er décembre. Pas de titre “2x plus rapide”, pas de réécriture du moteur. Il comble les lacunes que la 7.0 avait laissées dans le système de types, et ces lacunes étaient vraiment agaçantes.

Les types nullables

La 7.0 permettait de déclarer string $name comme type de paramètre. Ce qu’elle ne permettait pas, c’était de dire “ça peut aussi être null”. On devait soit abandonner le type hint complètement, soit bricoler autour. La 7.1 ajoute le préfixe ? :

function findUser(?int $id): ?User {
    if ($id === null) return null;
    return $this->repository->find($id);
}

Ça semble mineur. Ce n’est pas le cas. Les types nullables font la différence entre une signature qui dit ce que fait une fonction et une qui ment par omission. Chaque codebase sur lequel j’ai travaillé a des fonctions qui peuvent retourner null. Maintenant on peut vraiment le dire plutôt que de le cacher dans un docblock.

Le type de retour void

Le complément du nullable : une fonction qui ne retourne intentionnellement rien :

public function process(Order $order): void {
    $this->dispatcher->dispatch(new OrderProcessed($order));
}

void rend l’intention explicite et empêche de retourner accidentellement une valeur depuis une fonction qui ne devrait pas. Combiné aux types nullables, le système de types de PHP en 7.1 est bien plus expressif qu’en 7.0.

La visibilité des constantes de classe

Un petit correctif mais bienvenu. Les constantes dans les classes étaient toujours publiques avant la 7.1. Maintenant :

class Config {
    private const DB_PASSWORD = 'secret';
    protected const VERSION = '2.0';
    public const MAX_RETRIES = 3;
}

Garder les détails d’implémentation privés, ça compte. Ça aurait dû exister depuis le début.

Attraper plusieurs exceptions

try {
    // ...
} catch (InvalidArgumentException | RuntimeException $e) {
    // gérer les deux
}

Évite un bloc catch dupliqué quand deux exceptions nécessitent un traitement identique. Simple, utile.

Déstructurer des tableaux sans list()

list() est dans PHP depuis la 4.0 et a toujours semblé un peu à côté syntaxiquement. La 7.1 ajoute un raccourci avec [] qui se lit bien plus naturellement :

[$first, $second] = $coordinates;

foreach ($rows as [$id, $name, $email]) {
    // ...
}

Elle gagne aussi le support des clés, ce qui rend la déstructuration de tableaux associatifs enfin utilisable :

['id' => $id, 'name' => $name] = $user;

foreach ($records as ['id' => $id, 'status' => $status]) {
    // ...
}

Avant ça, extraire des clés nommées d’un tableau signifiait soit extract() (qui déverse tout dans le scope et invite les collisions) soit un tas d’assignations individuelles. C’est juste plus propre.

Le type iterable

Si on écrit une fonction qui accepte soit un tableau soit un générateur, il n’y avait pas de type hint propre pour ça en 7.0. On typait soit en array et on excluait silencieusement les générateurs, soit on abandonnait le hint complètement :

function processItems(iterable $items): void {
    foreach ($items as $item) {
        $this->handle($item);
    }
}

iterable accepte tout ce sur quoi on peut faire un foreach : les tableaux et les implémentations de Traversable. Ça fonctionne aussi comme type de retour. Pas dramatique, mais ça comble un vrai manque.

Les offsets négatifs sur les chaînes

L’indexation de chaînes avec [] ou {} accepte maintenant les valeurs négatives, en comptant depuis la fin :

$str = 'hello';
echo $str[-1]; // "o"
echo $str[-2]; // "l"

Plusieurs fonctions de chaînes ont reçu le même traitement : strpos(), substr(), substr_count() et d’autres acceptent maintenant un offset négatif. Cohérent avec ce que Python fait depuis toujours. Mieux vaut tard que jamais.

Closure::fromCallable()

Avant ça, convertir un callable (comme [$object, 'method'] ou une chaîne nom de fonction) en une vraie Closure nécessitait Closure::bind() ou bindTo() avec une gestion de portée délicate. La 7.1 ajoute une méthode de fabrique statique :

class Processor {
    private function transform(string $value): string {
        return strtoupper($value);
    }

    public function getTransformer(): Closure {
        return Closure::fromCallable([$this, 'transform']);
    }
}

La closure résultante capture le bon $this et la bonne portée. C’est particulièrement utile quand on passe des méthodes comme callbacks à des fonctions qui attendent un callable, ou quand on construit des pipelines.

ArgumentCountError

En PHP 7.0, appeler une fonction définie par l’utilisateur avec trop peu d’arguments générait un warning et l’exécution continuait avec des paramètres remplis à null. En 7.1, ça lève une ArgumentCountError :

function connect(string $host, int $port): void { /* ... */ }

try {
    connect('localhost'); // Lève ArgumentCountError
} catch (\ArgumentCountError $e) {
    // ...
}

ArgumentCountError étend TypeError, qui étend Error. Les call sites qui se dégradaient silencieusement auparavant échouent maintenant bruyamment. C’est un risque de migration si on a du code qui comptait sur le comportement permissif, mais honnêtement, c’est la bonne décision.

La 7.1 est le genre de version qui fait davantage faire confiance à une plateforme. La core team regardait clairement les frictions, pas seulement les titres à faire valoir.