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 :
- Random\Engine\CombinedLCG
- Random\Engine\Mt19937
- Random\Engine\PcgOneseq128XslRr64
- Random\Engine\Secure
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 😁