Page d'accueil Mes articles

PHP 8.1, dépréciations et nouveautés

La release est prévue pour le 25 novembre. Je vous propose un tour rapide sur ce que cette nouvelle version peut nous apporter en tant que développeur PHP.

Publié en Novembre 2021.
Dernière mise à jour Septembre 2022.

Ce qui ne fonctionnera plus sur PHP 8.1

Si vous êtes friand de l'utilisation des $GLOBALS, la migration vers 8.1 risque de vous impacter. Il est toujours possible dans cette version de venir définir une variable en particulier dans ce tableau mais il n'est plus possible de faire des modifications de masse. En effet, ce genre de traitement était très coûteux pour le moteur PHP. La décision d'abandonner cette façon de faire simplifie ainsi le moteur. Normalement, si vous utilisez n'importe quel Framework PHP, l'utilisation de $GLOBALS devrait être pour vous un lointain souvenir. Depuis le temps que j'utilise Symfony, je n'ai jamais eu besoin d'utiliser $GLOBALS 😁

Une évolution cependant qui peut vous impacter même si vous êtes sur un Framework et qui concerne les fonctions intégrées de PHP. S'il vous arrive par exemple d'utiliser les fonctions chaînes de caractères de PHP en passant en paramètres nulle au lieu de string dans certains cas, et bien maintenant sur PHP 8.1 cela sera déprécié .

<?php

$foo = str_word_count(null);
❯ docker run -v /Users/yoann/dev/chocteau/:/srv/ -it php:8.1.0RC6-cli-alpine3.14 php /srv/php8.php

Deprecated: str_word_count(): Passing null to parameter #1 ($string) of type string is deprecated in /srv/php8.php on line 3

En parlant de typage également, si vous faites un héritage sur une classe native de PHP vous devrez maintenant définir le type de retour en cas de surcharge de méthode. L'exemple pris pour illustrer cela dans la RFC et la surcharge de DateTime.

class A extends DateTime
{
    public function modify($modifier) { }
}

Ce code, sur PHP 8.1 vous générera maintenant un deprecated vous forçant ainsi à définir le type de retour

class A extends DateTime
{
    public function modify($modifier): DateTime|false { }
}

Le problème, c'est que les retours de type en union ne sont disponibles qu'à partir de la version 8 de PHP. Or, si vous faites de l'open source vous devez probablement gérer des versions plus ancienne de PHP. Il est donc possible d'utiliser un attribut pour éviter ce deprecated.

class A extends DateTime
{
    /**
     * @return DateTime|false
     */
    #[ReturnTypeWillChange]
    public function modify($modifier) { }
}

Attention tout de même à la version 9 qui devra générer une erreur fatale dans ce genre de cas.

Le typage de PHP passe donc une nouvelle étape sur cette version 8.1. Personnellement, cela me plaît car j'utilise déjà le côté typé du langage. Et vous, que pensez-vous de cela ?

On notera également avec l'arrivée de PHP 8.1 un changement de comportement au niveau des variables statiques. Jusqu'à aujourd'hui, si on définissait une variable statique dans une méthode de classe, son comportement n'était pas répercuté sur les classes enfants à la différence des attributs de classe. Maintenant, le comportement est le même.

Il y a également un certain nombre de modifications sur des fonctions permettant la manipulation de la base de données, le FTP ou encore les fichiers. Je ne rentrerai pas dans le détail dans cet article mais je vous invite à consulter le fichier UPGRADING pour en savoir plus.

Ce qui est nouveau sur PHP 8.1

Nous arrivons à la partie la plus intéressante pour nous en tant que développeur : Ce que cette nouvelle version va nous proposer de nouveau !

Les énumérations

Pour moi, c'est clairement l'avancée majeure de cette nouvelle version. J'ai été amené à utiliser des types enums en Java, Dart ou encore Typescript, et je suis vraiment content de les voir enfin arriver sur PHP. On distingue d'un côté les énumérations basiques pour lesquelles les cas n'ont pas de valeur scalaire...

enum Status {
    case Todo;
    case InProgress;
    case Done;
    case Cancelled;
}

...des "Backed Enum" qui elles possèdent une valeur scalaire pour chacun des cas. Ce qui est essentiel si nous stockons les valeurs de ces énumération sur une base de données par exemple. Important tout de même, vous devez donner un seul et même type à ses valeurs scalaires.

enum Status: string
{
    case Todo = 'T';
    case InProgress = 'I';
    case Done = 'D';
    case Cancelled = 'C';
}

Il est possible de parcourir facilement l'ensemble des cas grâce a la fonction cases()

foreach (Status::cases() as $case) {
    echo $case->name,': ',$case->value."\n";
}

Le fait de pouvoir donner des valeurs scalaire à nos cas d'énumérations permet par exemple de les stocker en base de données. Mais si nous voulions les utiliser dans une liste déroulante, nous aurions besoin de labels. Étant donné qu'il est possible de définir des méthodes au sein d'un type Enum, nous pouvons faire cela facilement.

enum Status: string
{
    case Todo = 'T';
    case InProgress = 'I';
    case Done = 'D';
    case Cancelled = 'C';

    public function label(): string {
        return match($this) {
            static::Todo => 'À faire',
            static::InProgress => 'En cours',
            static::Done => 'Terminé',
            static::Cancelled => 'Annulé',
        };
    }
}

foreach (Status::cases() as $case) {
    echo $case->value,' : ',$case->label(), "\n";
}

Et bien sur, les deux dernières méthodes essentielles pour manipuler les énumérations en PHP sont celles permettant de créer un enum par son scalaire associé. Nous avons soit la méthode from() qui générera une erreur si le cas n'est pas trouvé, d ou bien la méthode tryFrom() qui elle retournera soit nul soit le cas recherché

$status = Status::from('K'); // Fatal error: Uncaught ValueError: "K" is not a valid backing value for enum "Status" 
$status = Status::tryFrom('K') ?? Status::from('T'); // $status contient alors le cas Todo

Les types d'intersection

Vous connaissiez avec l'arrivée de PHP 8.0 les types d'union. Très pratique dans le cas où votre méthode retourne par exemple un objet ou un booléen. Et bien dans le même esprit nous pouvons désormais utiliser les types d'intersection. Alors bien sûr ce n'est pas le même usage, il s'agit ici de vérifier par exemple qu'un objet passé en paramètre répond bien à plusieurs interfaces. Nous pourrions par exemple imaginer une méthode prenant en paramètre un objet devant répondre à la fois à l'interface Stringable et à l'interface JsonSerializable. Jusqu'à maintenant, nous aurions dû écrire ce type de code

interface MyInterface extends JsonSerializable, Stringable {}

function (MyInterface $object) { };

Maintenant, sur PHP 8.1 il est désormais possible d'écrire ce type de code simplifiant ainsi grandement les choses.

function (JsonSerializable&Stringable $object) { };

Les propriétés en lecture seule

Une nouveauté importante sur PHP 8.1 est la possibilité de définir des propriétés en lecture seule. Si, dans votre classe, une propriété ne doit pas être modifié après son initialisation, il est désormais possible sur PHP de venir définir cette propriété en readonly afin de générer une erreur fatale si cette propriété est modifiée par la suite.

class User
{
    public function __construct(public readonly string $name) { }
}

$user = new User('Bob');
$user->name = 'Boby';
// Fatal error: Uncaught Error: Cannot modify readonly property User::$name

Générer des images pour le web !

Il était déjà possible depuis un moment maintenant de générer des images au format Webp, ce format vous permet de gagner jusqu'à 30 % sur une image JPEG. Il est compris par la plupart des navigateurs excepté Internet Explorer. Plus économe encore que le format Webp, nous avons le format AVIF pouvant nous faire gagner jusqu'à 50 % sur un JPEG. Et bien sur PHP 8.1, nous avons désormais les fonctions imageavif() et imagecreatefromavif() ! Attention tout de même à la compatibilité avec les navigateurs, ce format n'étant pas encore beaucoup supporté.

New in initializers

Il était jusqu'à maintenant impossible en PHP de donner comme valeur par défaut à une variable la création d'un objet. Nous devions lui définir sa valeur nulle par défaut, et ensuite, dans la méthode, créer l'objet souhaité si le paramètre était nul.

function ($class = null) {
    if ($class === null) {
        $class = new StdClass();
    }
};

Depuis cette nouvelle version, nous pouvons faire beaucoup plus simple. Il est désormais possible de donner comme valeur par défaut à une variable la création d'un objet.

function ($class = new StdClass()) { };

Les fibers

En PHP, nous avons l'habitude d'écrire du code s'exécutant de façon synchrone. Si, par exemple, une requête SQL ou un appel API prend du temps pour fournir une réponse et bien l'exécution du script se met tout simplement en pause. Dans la plupart des cas cela ne pose aucun problème. Mais dans certains cas, nous souhaiterions que PHP permette l'exécution de code asynchrone. Le projet https://amphp.org/ répond à ces attentes. Je ne l'utilise pas directement, mais il est présent dans mes dépendances. J'ai donc utilisé la commande composer why pour savoir par qui il était utilisé.

❯ composer why amphp/amp
amphp/byte-stream         v1.8.1  requires  amphp/amp (^2)      
amphp/parallel            v1.4.1  requires  amphp/amp (^2)      
amphp/parallel-functions  v1.0.0  requires  amphp/amp (^2.0.3)  
amphp/process             v1.1.2  requires  amphp/amp (^2)      
amphp/sync                v1.4.2  requires  amphp/amp (^2.2)    
phpro/grumphp             v1.5.0  requires  amphp/amp (^2.4) 

... Et bien c'est par Grumphp ! Effectivement, cela fait sens ! Si nous voulons lancer des contrôles avant notre commit, c'est quand même beaucoup mieux si ils sont exécutés de façon asynchrone. Je n'ai pas regardé dans le code de grumpPHP pour vérifier que c'est bien de cette façon là que amphp est utilisé.

Nouvelle syntaxe pour créer des callables

Jusqu'à maintenant pour créer une callable à partir d'une fonction ou d'une méthode, nous devions utiliser Closure::fromCallable. La syntaxe a été clairement simplifié dans cette nouvelle version de PHP 8.1.

<?php

class Hello
{
    public function giveMethodAsCallableToSayHello(): callable
    {
        // Avant PHP 8.1
        // return Closure::fromCallable([$this, 'sayHello']);
        // Depuis PHP 8.1
        return $this->sayHello(...);
    }

    private function sayHello($name) 
    {
        echo "Hello $name !";
    }
}

$hello = new Hello();
$sayHello = $hello->giveMethodAsCallableToSayHello();
$sayHello('Boby');

Autres nouveautés en bref

On notera l'arrivée d'un nouveau type "noreturn" à utiliser sur nos méthodes utilisant throw ou exit. Il est également désormais possible en PHP de cumuler Argument Unpacking et Named arguments. Et pour finir, si vous utilisiez jusqu'à maintenant les fonctions permettant de connaître l'heure de lever du soleil ainsi que l'heure de coucher du soleil sachez qu'elles sont maintenant deprecated. date_sunrise() et date_sunset() doivent être remplacé dans vos applications par date_sun_info().

Pour conclure

Ce tour des nouveautés et changement sur PHP 8.1 n'est bien sûr pas complet. N'hésitez pas à consulter le fichier https://github.com/php/php-src/blob/php-8.1/UPGRADING pour voir si des évolutions impactent votre projet.

En ce qui me concerne, la possibilité de faire un nativement en PHP des énumérations est clairement la chose que j'attendais le plus sur ce langage. Et vous ? Quelles nouveautés préférez-vous ? Dites-le-moi dans les commentaires

Cet article vous a été utile ? Faites le connaître sur les réseaux sociaux Twitter twitter LinkedIn linkedin

Un commentaire ?

codeur
Ou connectez-vous avec GitHub