La version finale de PHP 8.0 vient d’être publiée. C’est une excellente nouvelle ! Au-delà des améliorations techniques qui ont pu être apportées, il y a surtout de nouvelles fonctionnalités dont certaines sont très attendues parmi les développeurs PHP.

Si vous n’avez pas vraiment suivi tout ça, je vous propose de faire un tour des nouvelles features, dans le désordre.

Les arguments nommés

Il est possible de passer les arguments d’une fonction ou d’une méthode en utilisant leur nom de variable, et de fait d’ignorer l’ordre de déclaration des paramètres. On retrouve un comportement similaire avec les arguments nommés de Python.

C’est très pratique, car cela permet également d’ignorer les paramètres facultatifs qui ne nous intéressent pas.

Par exemple pour faire une recherche où seule la casse nous intéresse :

function search(string $terms, $sortBy = 'dates', $caseSensitive = false) {}

// PHP 7.x
$results = search('my query', 'dates', true);

// PHP 8.0
$results = search(caseSensitive: true, terms: 'my query');

On voit clairement l’avantage dans cet exemple, le paramètre $sortBy n’a pas besoin d’être explicitement fourni, et donc on s’appuie toujours sur la valeur par défaut définie par la fonction elle-même. Le couplage est donc légèrement réduit.

Le contre-argument qui a été principalement présenté lors du vote de cette feature en revanche, c’est que l’on rajoute un lien fort sur le nom des paramètres, qui n’existait pas jusqu’alors. Il sera désormais plus difficile de renommer le nom d’une variable, en particulier dans les libraries.

Les attributs

LA feature attendue dans PHP 8.0 est clairement les attributs ; enfin, par moi en tout cas. C’est aussi ce qu’on appelle les annotations, par exemple en Java. Actuellement, la plupart des frameworks et libraries en PHP ont contourné le problème en utilisant des annotations via la PHPDoc, mais maintenant la fonctionnalité est officiellement supportée.

La syntaxe choisie est celle de Rust, c’est-à-dire : #[Attribute].

Exemple avec le routeur Symfony :

// PHP 7.x
class SomeController
{
    /** @Route("/path", name="action") */
    public function someAction() {}
}

// PHP 8.0
class SomeController
{
    #[Route('/path', name: 'action')]
    public function someAction() {}
}

Pour l’anecdote, les débats ont été assez virulents sur la meilleure syntaxe à choisir. En réalité celle retenue n’est même pas celle qui avait été votée en premier lieu. C’était à l’origine <<Attribute()>> qui avait été choisie, mais il s’est avéré que cette syntaxe posait des problèmes au niveau de l’implémentation au sein du langage.

De la même façon, il n’était pas possible de choisir @Attribute(), l’arobase étant déjà un caractère réservé en PHP (pour rendre les erreurs silencieuses, ne l’utilisez pas). L’autre option était @@Attribute().

Les types d’union

Les types d’union (union types) permettent de spécifier plusieurs types sur un même paramètre, retour de fonction/méthode, ou attribut de classe.

Cette fonctionnalité permet de peaufiner encore un petit peu la déclaration des types en PHP introduits en version 7.0, et qui ont déjà subi quelques améliorations depuis leur sortie (nullable, attributs, etc.).

Exemple :

// PHP 7.x
/**
 * @param int|float|null $threeshold
 * @return ArrayAccessImplementation|array
 **/
public function getListByPrice($threeshold) {}

// PHP 8.0
public function getListByPrice(int|float|null $threeshold): ArrayAccessImplementation|array {}

C’est évidemment à utiliser avec parcimonie, puisque l’avantage d’un typage strict des méthodes évite justement que l’on se retrouve avec n’importe quel type en entrée/sortie de celles-ci.

Note : inutile d’en faire une section complète ici, mais le type mixed a également été ajouté au langage, ce qui permet de ne plus jamais avoir de typage non défini, et simplifie grandement la vie des linters.

Déclaration des attributs dans le constructeur

Faute d’une meilleure traduction de cette nouvelle simplification du langage appelée « constructor property promotion », le titre décrit exactement ce changement. Les attributs de classe peuvent être directement et uniquement déclarés dans les paramètres du constructeur.

Voyons ça :

// PHP 7.x
class BlogPost
{
  protected string $title;
  protected TagList $tags;
  protected DateTimeInterface $createdAt
  public function __construct(string $title, TagList $tags, DateTimeInterface $createdAt) {
    $this->title = $title;
    $this->tags = $tags;
    $createdAt = $createdAt;
  }
}

// PHP 8.0
class BlogPost
{
  public function __construct(
    protected string $title,
    protected TagList $tags,
    protected DateTimeInterface $createdAt,
  ) {}
}

En réalité avec un IDE avancé comme PHPStorm, le volume de code écrit est plus ou moins le même. Mais il faut avouer que c’est plus lisible.

Virgule de fin dans les paramètres

Vous aurez peut-être remarqué une petite subtilité dans la déclaration du constructeur de la section précédente : le dernier paramètre est suivi d’une virgule… et ce n’est pas une typo !

En effet, les listes de paramètres acceptent désormais une virgule de fin, comme les arrays, à la déclaration et à l’appel. Aucun doute que j’ajouterai cette règle à ma configuration PHPCS.

Pour l’exemple, voyez au-dessus.

Nouvelles fonctions str_*

Déjà il y a 10 ans, l’une des premières versions de Shaarli contenait les fonctions startsWith() et endsWith() pour faire des vérifications sur les chaînes de caractères. Ces méthodes et leurs dérivées à base de strpos je les ai vues dans quasiment les projets sur lesquels j’ai pu travailler.

Une décennie plus tard, 3 nouvelles fonctions sont désormais disponibles dans la bibliothèque standard, et leur nom parle d’elles-mêmes : str_contains, str_starts_with() et str_ends_with().

Utilisation :

// All return true.
str_contains('All work and no play makes Jack a dull boy', 'Jack');
str_starts_with('All work and no play makes Jack a dull boy', 'All work');
str_ends_with('All work and no play makes Jack a dull boy', 'a dull boy');

Nouvelle interface Stringable

Stringable est une nouvelle interface de la bibliothèque standard. Pour l’implémenter il suffit d’implémenter une méthode __toString() qui sera appelée par défaut lors d’une conversion de l’objet.

En réalité, tout ça existe déjà, et il n’est pas nécessaire de spécifier explicitement implements Stringable, cela permet simplement de mieux gérer le typage.

En gros, cette nouvelle interface vous permet de faire ça :

class Greeting
{
  public function __toString(): string {
    return 'hi!';
  }
}

class NotGreeting
{
  public function __toString(): string {
    return 'come back later';
  }
}

class Greeter
{
  public function greet(Stringable $greeting): string {
    return 'Greeting: ' . $greeting;
  }
}

$greeter = new Greeter();
echo $greeter->greet(new NotGreeting()); // 'Greeting: come back later'
echo $greeter->greet(new Greeting()); // 'Greeting: hi!'

Pour faire cela en PHP 7.x, il n’aurait pas été possible de spécifier le type de la méthode greet() sans réunir NotGreeting et Greeting sous une même interface.

L’opérateur nullsafe

Une de mes features préférées de PHP 7.0 était l’opérateur null coalescent. Il n’y a rien de plus contre-productif que d’écrire 8 lignes de if pour vérifier que isset, array_key_exists, !== null ou autre instanceof.

PHP 8.0 me ravit donc en ajoutant l’opérateur nullsafe, qui permet de faire des appels de méthode sur un objet null.

// PHP 7.x
if (!$blogPost instanceof BlogPost || $blogPost->getCreatedAt() === null) {
  return null;
}
return $blogPost->getCreatedAt()->format('Y-m-d');

// PHP 8.0
return $blogPost?->getCreatedAt()?->format('Y-m-d');

Et si $blogPost ou $createdAt est null, on retourne tout simplement null, sans générer d’erreur fatale.

Lever des exceptions dans les expressions

Aller, tant qu’on est dans les petites améliorations qui améliorent la productivité et la lisibilité du code, il est désormais possible de lever des exceptions dans n’importe quelle expression. Je n’ai jamais bien compris pourquoi ce n’était pas possible - probablement des questions d’implémentation au niveau du code source de PHP - mais c’est réglé.

// PHP 7.x
$item = $collection->getItem($id);
if ($item === null) {
  throw new ItemNotFoundException();
}
return $item;

// PHP 8.0
return $collection->getItem($id) ?? throw new ItemNotFoundException();

match, le cousin du switch

J’utilise relativement rarement les switch {}, peut-être inconsciemment parce que c’est une expression assez verbeuse… switch, case:, break, et on répète. PHP 8.0 ajoute désormais l’expression match dont la syntaxe est similaire à celle existante en Java.

Concrètement, ça répond au même besoin que le switch, mais en plus lisible et compact avec une syntaxe array-like.

Voyons plutôt :

// PHP 7.x
switch ($locale) {
  case 'en_GB':
  case 'en_US':
    $language = Languages::ENGLISH;
    break;
  case 'de_DE':
    $language = Languages::GERMAN;
    break;
  default:
    $language = Languages::FRENCH;
}

// PHP 8.0
$language = match ($locale) {
  'en_GB', 'en_US' => Languages::ENGLISH,
  'de_DE' => Languages::GERMAN,
  default => Languages::FRENCH,
};

JIT (Just In Time) compiler

JIT compiler est une fonctionnalité de PHP 8.0 qui permet de compiler du code ré-utilisable directement pendant l’exécution, de la même façon qu’un cache pourrait opérer. Cela peut mener à de grandes amélioration de performances dans certains contextes.

C’est prometteur, mais c’est aussi une fonctionnalité assez complexe que je préfèrerait développer dans un article dédié. On en reparle ici bientôt !

Type de retour static

Je ne vais pas refaire le discours self vs static ici, mais sachez qu’il est désormais possible de définir le type de retour à static, en plus de self qui était déjà disponible.

class Entity {
  public function setProperty(int $property): static {
    $this->property = $property;

    return $this;
  }
}

Utilisation de ::class sur les objets

Il est maintenant possible d’appeler ::class directement sur les objets et pas uniquement sur les classes. Encore une petite amélioration qui simplifie la vie des développeurs.

// PHP 7.x
$className = get_class($myObject);
$className = MyClass::class;

// PHP 8.0
$className = $myObject::class;
$className = MyClass::class;

DateTime createFromInterface()

Vous avez déjà essayé de manipuler des DateTimeInterface, de créer des DateTimeImmutable et d’effectuer des opérations sur tout ça ? Non ? Eh bien vous avez de la chance, puisque l’implémentation en PHP 7.x est trouée, et qu’il y a de quoi y perdre quelques cheveux.

PHP 8.0 vient régler ça en ajoutant une méthode createFromInterface() aux classes DateTime et DateTimeImmutable, ce qui permet de convertir les objets de l’un à l’autre facilement.

Exemple, qui devrait vous faire saisir l’idée :

public function addMonth(DateTimeInterface $dateTime): DateTimeImmutable {
  $dateTime = DateTime::createFromInterface($dateTime);
  $dateTime->add(new DateInterval('P1M');

  return DateTimeImmutable::createFromInterface($dateTime);
}

Conclusion

Ça fait déjà pas mal de choses à se mettre sous la dent. Cet article n’a pas non plus vocation à être une release note complète.

Concernant les breaking changes, ils sont assez peu nombreux. Ça a toujours été une volonté forte de la part des développeurs de PHP - la contrepartie étant que cela peut freiner les évolutions.

Concrètement, la bascule en PHP 8.0 se fera facilement, si vous n’avez pas utilisé de syntaxes alambiquées. Mais attention, un certain nombre d’avertissements ont été convertis en erreur réelle. Il faudra donc peut être revoir votre gestion des erreurs.

Bon, on ne va pas se mentir, les montées de version sont souvent plus douloureuses au niveau de la gestion des dépendances que du langage en lui-même. Vous devriez évidemment attendre quelques semaines pour que les choses se stabilisent.

Encore quelques nouveautés pour conclure :

  • Les méthodes privées (private) ne peuvent plus être déclarées comme final, ce qui était de toute façon un non sens.
  • Il est désormais possible de catch une exception sans l’utiliser - et donc sans déclarer son nom de variable.
try {
} catch (SomeException) {
  // Do something not related to the exception variable
}
  • La signature des méthodes abstraites dans les Trait doit désormais être strictement respectée dans les classes qui l’utilisent.
  • Une implémentation de WeakMap a été ajoutée dans PHP 8.0. J’y reviendrais possiblement dans un article dédié.