Page d'accueil Mes articles

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

La release est prévue pour le 24 novembre. Je vous propose un tour rapide des dépréciations ainsi que des nouvelles fonctionnalités apportées par PHP 8.2

Publié en Septembre 2022.

Ce qui ne fonctionnera plus sur PHP 8.2

Avec l'évolution du langage, certaines façons de coder ne seront plus permises sur PHP. La direction prise par la communauté est d'ajouter de la rigueur et du typage à notre vieil éléphant. La comparaison entre PHP 5 et 8 est flagrante ! Le premier changement sur la 8.2 illustre bien cela 😁

L'utilisation de propriétés dynamiques est maintenant dépréciée

Sur PHP, il était possible de définir des propriétés d'un objet de façon dynamique. Ce type de code fonctionne sur la version 8.1 mais deviendra déprécié sur la version 8.2 avant de générer une erreur sur la version 9.

class Quote { }

$quote = new Quote();
$quote->price = 999.99;

L'execution de ce type de code sur PHP 8.2 générera le deprecated suivant

PHP Deprecated:  Creation of dynamic property Quote::$price is deprecated

Utiliser des propriétés dynamiques est bien sûr toujours possible si l'on utilise la méthode magique __set()

class Quote {
    private array $data;
    public function __set($name, $value) {
        $this->data[$name] = $value;
    }
}

Une autre solution est d'utiliser l'attribut suivant

#[AllowDynamicProperties]
class Quote { }

https://wiki.php.net/rfc/deprecate_dynamic_properties

Les fonctions utf8_decode et utf8_encode sont maintenant dépréciées

Le but de ces deux fonctions était de faire des conversions entre le type ISO-8859-1 (“Latin 1”) et UTF-8. Je les ai beaucoup utilisé à l'époque sur PHP 5 pour communiquer avec des systèmes n'ayant pas le même encodage ( base de données, import CSV... ).

Plusieurs raisons de cette suppression sont évoquées dans la RCF. Pour commencer, le nommage de ces fonctions n'était pas clair. Nous faisons bien référence à l'UTF-8 dans leurs noms, mais pas à ISO-8859-1. De fait, des développeurs pouvaient se retrouver face à des comportements non souhaités en convertissant une chaîne de caractère depuis le format Windows Code Page 1252. Pour finir, en cas de conversions incorrectes, ces fonctions ne généraient pas de messages d'erreurs.

N'attendez pas la version 9 pour les retirer de votre code. Les fonctions mb_convert_encoding et iconv vous permettent de déterminer explicitement l'encodage d'origine ainsi que l'encodage de destination. 😁

https://wiki.php.net/rfc/remove_utf8_decode_and_utf8_encode

L'interpolation de variable évolue

Que ce soit dans une simple chaine de caractère avec double quote ou au format heredoc, nous avons la possibilité en PHP d'ajouter des variables dans une chaine de caractères sans utiliser la concaténation .

$name = 'John Doe';
echo "Hello $name";

Une syntaxe un peu plus complexe est proposée lorsque nous avons besoin de manipuler des objets.

class Quote 
{
    public function informations(): string
    {
        return "The quote is published";
    }
}

$quote = new Quote();
echo "Hello {$quote->informations()}";

Personnellement, je connaissais et j'utilisais seulement ces deux façons de faire, mais nous pouvions également utiliser la syntaxe suivante :

$name = 'John Doe';
var_dump("Hello ${name}");

En PHP, une variable peut stocker le nom d'une autre variable. Dans ce genre de cas, nous pouvions alors faire

$firstname = 'John';
$name = 'firstname';
var_dump("Hello ${$name}");

La RFC précise que ces deux façons de faire n'étaient pas très répandues. C'est pour cette raison que ces deux syntaxes seront maintenant dépréciées puis supprimées dans la version 9. Si vous avez des variables dans des variables sur 8.2, la syntaxe à utiliser est la suivante :

$firstname = 'John';
$name = 'firstname';
var_dump("Hello {${$name}}");

https://wiki.php.net/rfc/deprecate_dollar_brace_string_interpolation

Les conversions de casse sont maintenant indépendante de la locale

Dans un souci d'amélioration des performances et de simplicité, les fonctions php agissant sur la casse ne sont plus liées à la locale. Cela évitera bugs et incompréhensions lorsque vous voudrez par exemple passer en minuscule un caractère accentué. PHP agira maintenant uniquement sur les caractères ASCII

// PHP 8.1
echo strtolower('Énergie'); // Énergie
setlocale(LC_ALL, 'fr_FR.UTF-8');
echo strtolower('Énergie'); // ?nergie

// PHP 8.2 
// Code simplifié, ignore la locale
echo strtolower('Énergie'); // Énergie
setlocale(LC_ALL, 'fr_FR.UTF-8');
echo strtolower('Énergie'); // Énergie

Si vous souhaitez passer en minuscule une chaîne de caractère pouvant contenir un caractère accentué, vous devez utiliser la fonction mb_strtolower depuis PHP 4.3.0.

echo mb_strtolower('Énergie'); // énergie

https://wiki.php.net/rfc/strtolower-ascii

Ce qui est nouveau sur PHP 8.2

Nous arrivons maintenant à la partie la plus intéressante, voir ce que cette nouvelle version va pouvoir nous apporter de nouveau dans notre façon de coder.

RNG, Random Number Generator

En PHP, l'algorithme utilisé pour générer des nombres pseudo-aléatoires est Mersenne Twister. Performant et rapide, il échoue néanmoins à certains tests statistiques comme "Big Crush". Pour palier à cette faiblesse, et dans des cas vraiment particuliers, les développeurs devaient parfois mettre en place leur propre classe RNG. Côté performance, le compte n'y était pas. Difficile de gagner face à un algorithme présent dans le moteur de PHP.

Pour cette raison là, nous pouvons désormais utiliser d'autres algorithmes que le Mersenne Twister.

$random = new Random\Engine\Secure();
var_dump(bin2hex($random->generate())); // string(16) "1497aeb9e5f1b35d"

Les algorithmes disponibles sont les suivants :

Si l'on souhaite récupérer un nombre aléatoire ou mélanger un mot à partir d'un de ces algorithmes, nous devons passer par la classe Randomizer.

$randomizer = new Random\Randomizer(new Random\Engine\Secure());
var_dump($randomizer->nextInt()); // int(9157011420924314696)
var_dump($randomizer->shuffleBytes('maison')); // string(6) "oaimns"

Cette nouvelle approche orientée objet, en plus de nous fournir des nouveaux algorithmes que nous n'avions pas en utilisant par exemple mt_rand ou str_shuffle, va nous permettre d'éviter un comportement non désiré que nous pouvions avoir en développement.

if (!$isProduction) {
    mt_srand(1234);
}
var_dump(mt_rand(0, 100)); // 20
var_dump(mt_rand(0, 100)); // 8
var_dump(mt_rand(0, 100)); // 87

Étant donné que j'ai initialisé mon générateur en développement, je suis capable de prévoir les tirages à venir. Le problème, c'est que si mon état change en appelant par exemple str_shuffle entre la méthode mt_srand et mt_rand, et bien cela va venir logiquement modifier les résultats attendus.

if (!$isProduction) {
    mt_srand(1234);
}
// ...
str_shuffle('maison');
// ...
var_dump(mt_rand(0, 100)); // 56
var_dump(mt_rand(0, 100)); // 7
var_dump(mt_rand(0, 100)); // 86

Sur PHP 8.2, le résultat sera le même, mais le fait que cela soit réalisé en POO rend le code beaucoup plus facile à maintenir et à débogguer. De plus, l'appel de shuffleBytes pourrait par exemple être effecuté sur une autre instance de Randomizer.

$rng = $isProduction
    ? new Random\Engine\Secure()
    : new Random\Engine\PcgOneseq128XslRr64(1234);

$randomizer = new Random\Randomizer($rng);
var_dump($randomizer->shuffleBytes('maison')); // string(6) "mnosai"
var_dump($randomizer->nextInt()); // int(8390619848869632856)

https://wiki.php.net/rfc/random_extension_improvement

Il est désormais possible de typer en null, false ou true

Depuis PHP 8, nous pouvons utiliser les types unions afin de determiner qu'une méthode peut retourner un des types cités en retour. Nous pouvions alors typer en retour string|false|null mais pas false|null. Il était alors nécessaire de typer avec bool|null. Mais le problème, c'est que bool pouvait alors valoir la valeur true.

Pour pallier à cette incohérence, false, true et null sont maintenant utilisable comme des types autonomes.

class Quote 
{
    public function informations(): false|null
    {
        return null;
    }
    public function isLocked(): false { }
}

Dans la RCF, afin de justifier cet ajout, ils évoquent également la fonction php gmp_random_seed retournant null si tout s'est bien passé et false en cas d'erreur. Un typage de méthode false|null n'est donc pas incohérent sur PHP.

https://wiki.php.net/rfc/null-false-standalone-types

L'arrivée de la forme normale disjonctive pour le typage

Une belle avancée en matière de typage. Cette syntaxe va nous permettre de combiner les unions de types ( >= 8.0 ) avec les intersections de types ( >= 8.1 ).

Dans cet exemple, la classe H accepte dans son constructeur soit une instance implémentant les interfaces A et B ou bien une instance implémentant l'interface C.

interface A {}
interface B {}
interface C {}

class D implements A, B {}
class E implements C {}
class F implements A, C {}
class G implements A {}

class H
{
    public function __construct(private (A&B)|C $o) { }
}

new H(new D());
new H(new E());
new H(new F());
new H(new G()); // Argument #1 ($o) must be of type (A&B)|C, G given

L'exemple avec les interfaces est parlant, mais nous pouvons bien sûr utiliser cette syntaxe avec tous les types disponibles.

https://wiki.php.net/rfc/dnf_types

Possibilité de définir des constantes dans les traits

Il n'était pas possible jusque-là sur PHP de pouvoir définir des constantes dans les traits. Lorsque nous avions besoin d'utiliser des constantes dans un trait, notre code ressemblait alors à cela.

trait PrincingTrait
{
    public function isFairPrice(): bool
    {
        return (
            self::MIN_PRICE <= $this->price &&
            $this->price <= self::MAX_PRICE
        );
    }
}

class Quote
{
    public const MIN_PRICE = 100;
    public const MAX_PRICE = 1000;
    use PrincingTrait;
    public function __construct(private int $price) { }
}

class Invoice
{
    public const MIN_PRICE = 100;
    public const MAX_PRICE = 1000;
    use PrincingTrait;
    public function __construct(private int $price) { }
}

$quote = new Quote(7);
var_dump($quote->isFairPrice()); // false
$invoice = new Invoice(200);
var_dump($invoice->isFairPrice()); // true

Une amélioration était tout de même possible, celle d'ajouter une interface dont le but était de définir les constantes communes aux deux classes.

interface PrincingInterface
{
    public const MIN_PRICE = 100;
    public const MAX_PRICE = 1000;
}

class Quote implements PrincingInterface
// ...

class Invoice implements PrincingInterface
// ...

Grâce à PHP 8.2, plus besoin de passer par une interface, nous pouvons désormais définir les constantes au niveau du trait.

trait PrincingTrait
{
    public const MIN_PRICE = 100;
    public const MAX_PRICE = 1000;
    // ...

https://wiki.php.net/rfc/constants_in_traits

Caviardage des paramètres dans la trace d'appels

Alors si comme moi vous bloquez sur le sens du mot caviardage, et bien il s'agit selon la définition du Larousse du "fait de censurer, de supprimer une partie d'un texte, d'une publication.".

Lorsqu'une méthode prenant en paramètre une donnée sensible comme un mot de passe génère une exception...

function connect(
    string $login,
    string $password) {
    throw new \Exception('Big problem !');
}

connect('login', 'password');
Stack trace:
#0 dev/php82/test.php(9): connect('login', 'password')

... Et bien cette donnée sensible apparaît en clair dans votre trace d'appels. Dans l'absolu, on pourrait se dire que cela n'est pas grave puisque en production nous n'affichons jamais ces informations là à l'internaute. Nous lui affichons seulement une erreur 500, et nous stockons cette trace d'appels dans un fichier log.

Le problème c'est que nous externalisons maintenant la gestion des logs via des sociétés externe (sentry par exemple). Le fait que des données sensibles puissent être stockées sur d'autres systèmes d'informations peut poser des problèmes de confidentialités.

function connect(
    string $login,
    #[\SensitiveParameter] string $password) {
    throw new \Exception('Big problem !');
}

connect('login', 'password');
Stack trace:
#0 dev/php82/test.php(9): connect('login', Object(SensitiveParameterValue))

Fait très intéressant si vous faites de l'open source, ce code est compatible avec PHP >= 8.0 correspondant à l'arrivé des attributs. Et étant donné que PHP 7.4 va disparaitre quelques jours après l'arrivée de PHP 8.2, c'est une bonne nouvelle 😁

https://wiki.php.net/rfc/redact_parameters_in_back_traces

Possibilité de récupérer des propriétés d'une énumération dans une constante.

enum Status: string
{
    case DRAFT = 'draft';
    case PUBLISHED = 'published';
    case ARCHIVED = 'archived';
}

const Status = Status::DRAFT->value;

Ce type de code sur la version 8.1 générait l'erreur fatale "Constant expression contains invalid operations". Il fonctionne désormais sur la version 8.2. Mais vous vous demandez sûrement comme moi dans quel cas on peut avoir besoin de faire cela.

Et bien dans la RFC, ils font référence à cette issue et cela prend tout son sens. Le développeur utilise une librairie tierce en attribut et il souhaite lui envoyer un tableau construit à partir de son énumération.

#[ListensTo([Status::DRAFT->value = true])]
class MyTestCase { }

Cette évolution débloque ainsi ce problème.

https://wiki.php.net/rfc/fetch_property_in_const_expressions

La possibilité de définir une classe en readonly

Vous connaissiez les propriétés en lecture seule depuis la 8.1.

class Quote
{
    public function __construct(
        public readonly int $price,
        public readonly string $customer
    ) {}
}
$quote = new Quote(999.99, 'John Doe');
$quote->price = 1000; // Cannot modify readonly property Quote::$price

Nous avons maintenant la possibilité sur PHP 8.1 de définir toute une classe en lecture seule. Pratique lorsque l'on a plusieurs attributs.

readonly class Quote
{
    public function __construct(
        public int $price,
        public string $customer
    ) {}
}
$quote = new Quote(999.99, , 'John Doe');
$quote->price = 1000; // Cannot modify readonly property Quote::$price

Pour conclure

Ce tour des nouveautés et changements sur PHP 8.2 n'est bien sûr pas complet. N'hésitez pas à consulter les RCF embarqués sur PHP 8.2 pour plus d'informations.

En ce qui me concerne, l'arrivée de la forme normale disjonctive pour le typage ainsi que le caviardage des paramètres dans la trace d'appels sont les deux nouveautés qui me plaisent le plus. 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