PHP 8 & 8.1 : nouveautés, compatibilités et migration
PHP 8 a été officiellement lancé le 26 novembre 2020, cela fait donc un peu plus d’un an que nous avons pu expérimenter les différentes nouveautés introduites par cette version très attendue ! Comme pour toute montée de versions majeures, celle-ci est venue avec son lot d’incompatibilités avec les anciennes versions.
Nous vous proposons dans cet article de récapituler tout d’abord les différentes nouveautés apportées par PHP 8 et PHP 8.1. Nous aborderons ensuite les incompatibilités et risques de bugs en cas de montée de versions depuis PHP 7. Enfin, nous vous donnerons notre checklist pour une montée de version réussie et les outils permettant de l’effectuer simplement et rapidement.
Mais pourquoi passer à PHP 8 ?
La première question que l’on se pose lors de la sortie d’une nouvelle version majeure est de savoir si les nouveautés apportées par celle-ci justifie de consacrer plus ou moins rapidement du temps à la mise à jour de son code et dans la gestion des risques associés.
De notre propre expérience, passer à PHP 8 présente des avantages considérables :
- L’argument majeur est que PHP 8 vient avec une amélioration de performance non négligeable. Sans rentrer dans les détails niveau machine, PHP 8 permet d’effectuer de 20 % à 30 % de requêtes en plus par seconde par rapport aux versions antérieures (cela est précisé ici).
- PHP 8 vient renforcer grandement l’aspect « typé » du langage PHP, PHP peut donc garantir que les bons types soient passés au lieu de faire des checks manuels. Cela vient renforcer la maintenabilité du code et ainsi passer à PHP 8 permet donc de réduire votre dette technique à long terme.
- Enfin, PHP 8 a mis un accent majeur sur le fait de rendre le code PHP plus lisible et documenté ce qui facilitera la montée en compétence de vos équipes.
Ainsi, chez TheCodingMachine nous sommes persuadés que vous ne regretterez pas le temps consacré à la montée de version de votre environnement ! Et pour les éventuelles complexités, pas de soucis cet article est là pour vous aider.
Les nouveautés introduites par PHP 8 et PHP 8.1 :
PHP 8 et 8.1 ont introduit de nombreuses nouveautés, voici les plus notables :
Nullsafe operator : introduction d’un opérateur logique s’écrivant « ?-> » permettant de vérifier simplement à la chaîne si des variables sont nulles au lieu de faire plusieurs boucles if.
Named arguments : les arguments nommés permettent de passer des arguments à une fonction en fonction du nom du paramètre, plutôt que de la position du paramètre. Cela simplifie la documentation et la compréhension du code, rend les arguments indépendants de l’ordre et permet de sauter arbitrairement les valeurs par défaut (pour ne fournir que certains paramètres à une fonction). Ex : positional arguments : array_fill(0, 100, 50) ; named arguments : array_fill(start_index: 0, num: 100, value: 50).
Attributes : les annotations (/** @Annotation() */
), sont très couramment utilisées en PHP afin de rajouter des métadonnées au code (classes, fonctions, propriétés… ). PHP 8 propose à présent des attributs (#[Attribute]
) permettant de définir ces métadonnées directement grâce au langage. Ainsi, au lieu des annotations PHPDoc ou d’utiliser des outils comme la librairie Doctrine Annotations, vous pouvez désormais utiliser des métadonnées structurées avec la syntaxe native de PHP.
Lambda function : permet d’avoir une syntaxe simplifiée pour les fonctions anonymes afin de gagner en lisibilité.
Match expression : l’expression match est similaire dans son rôle à switch mais contrairement à switch, match ne repose que sur des comparaisons strictes (ce qui évite certains résultats surprenants), son résultat peut être stocké dans une variable ou retourné (en utilisant return) et chaque branche de l’expression match ne nécessite pas l’écriture d’un break; (celui-ci est implicite, un match n’exécute qu’une seule branche, il n’y a pas de « fall-through »).
Union types : la prise en charge des Union types dans le langage permet de déplacer plus d’informations anciennement dans la documentation (phpdoc) vers les signatures de fonction : les types sont en fait appliqués, de sorte que les erreurs peuvent être détectées tôt et que la dette technique est réduite.
[PHP 8.1] Enums : constantes typées fortes, empêchant les valeurs non souhaitées pour des variables de type string or integer sans aucune vérification nécessaire.
[PHP 8.1] Call new in initializers : il est à présent possible d’instancier un objet lors de l’initialisation. Cette initialisation peut concerner les paramètres par défaut d’une fonction, ou la valeur par défaut d’une propriété de classe,… Cela facilite la documentation et la recherche d’information grâce aux annotations directement dans le code. Cela permet aussi de renseigner directement une valeur par défault, plutôt que « null« , et débloque l’utilisation d’attributs dans d’autres attributs (ex : #[new Validalidation(new Integer())]).
Bien sûr il y a d’autres nouveautés, la documentation complète de PHP 8 est disponible sur le site officiel de PHP en cliquant ici et il en va de même pour PHP 8.1 ici.
Les incompatibilités pour une montée de version vers PHP 8
Bien qu’il soit difficile, voire impossible, d’anticiper toutes les complexités pouvant apparaître lors d’une montée de version, une partie des problèmes récurrents peuvent être anticipés. En effet, PHP 8 a renforcé l’aspect typé du langage PHP et par exemple certains conditionnements sont devenus stricts. Ainsi, certains codes, qui étaient souvent faux, vont à présent émettre des exceptions.
Voici, une petite liste des erreurs que nous avons rencontré lors de nos propres expériences et comment les éviter :
- Associativité des opérateurs ternaires : les opérations arithmétiques dans une string ne sont plus autorisées, ex : var_dump(12 + ‘a’); // int(12) => Uncaught TypeError: Unsupported operand. De même, l’utilisation de plusieurs opérateurs ternaires (par exemple ?:?:) sans parenthèse émettra une « Fatal error ».
- Comparaison non-stricte : les comparaisons entre des nombres et des chaînes de caractères non numériques retournent à présent false.
- New
match
keyword, il n’est plus possible de déclarer le keyword match en tant que symbol parcequ’il est réservé par PHP, ex : function match() { // => Parse error: syntax error, unexpected token « match ». - Calling non-static class methods statically result in a fatal error : une « non-static » méthode est une méthode qui n’est pas déclarée avec le mot clé static. Elle peut donc dépendre de l’instance de l’objet ($this), or si elle est appelée dans un contexte static (sans instance d’objet) cela renvoie une erreur car $this n’existe pas.
- Value Error : cette erreur est émise lorsque le type d’un argument est correcte mais que sa valeur ne l’est pas (ex : un nombre négatif pour une longueur). Pour éviter de telles erreurs, vous pouvez utiliser notre outil Open-Source Safe.
- Strong typing for resources : dans les versions antérieurs PHP manipulait souvent des « resource »s, beaucoup sont maintenant typées comme le montre le tableau ci-dessous :
Extension | Resource (PHP < 8.0) | Object (PHP >= 8.0) |
Curl Curl Curl GD Sockets Sockets OpenSSL OpenSSL OpenSSL XMLWriter XML GD FTP IMAP finfo PSpell PSpell LDAP LDAP LDAP PgSQL PgSQL PgSQL | Curl curl_multi curl_share gd Socket AddressInfo OpenSSL key OpenSSL X.509 OpenSSL X.509 CSR xmlwriter xml gd font (integer) ftp imap file_info pspell (int) pspell config(int) ldap link ldap result ldap result entry pgsql link pgsql result pgsql large object | CurlHandle CurlMultiHandle CurlShareHandle GdImage Socket AddressInfo OpenSSLAsymmetricKey OpenSSLCertificate OpenSSLCertificateSigningRequest XMLWriter XMLParser GdFont FTP\Connection IMAP\Connection finfo PSpell\Dictionary PSpell\Config LDAP\Connection LDAP\Result LDAP\ResultEntry \PgSql\Connection \PgSql\Result \PgSql\Lob |
Les étapes clefs pour réussir sa migration et les outils recommandés
Réaliser une montée de version apparaît toujours comme une tâche fastidieuse mais, comme expliqué plus haut, passer à PHP 8 en vaut la peine. Pour vous accompagner dans votre démarche, voici les étapes clés et les outils associés afin de gagner en efficacité :
- Effectuer une analyse statique, vous pouvez vous aider d’outils comme PHPStan ou PhpCodeSniffer,
- Appliquer un typage fort,
- Vérifier que vos dépendances sont mises à jour pour PHP 8,
- Vérifier vos logs pour repérer tout avertissement d’obsolescence (Deprecated notice),
- Vérifier les dépréciations répertoriées,
- Exécuter votre suite de tests.
Les outils que nous recommandons : PHPStan (outil d’analyse statique, TheCodingMachine est fier d’y contribuer), PhpCodeSniffer (Outil permettant de maintenir un code clair et cohérent), Rector (propose une montée de version « quasi » automatique), 3v4l.org (pour tester du code sous différentes versions de PHP).
Bonus : exemple de code reprenant les nouveautés !
Pour les plus passionnés d’entre vous, une « mixtape » des nouveautés PHP 8 composée par nos soins !
class User { public function __construct( public string $firstName, public string $lastName, public ?Address $address = null ) {} } class Address { public function __construct( public string $number, public string $street, public string $city, public string $country ) {} } enum Undefined { case Undefined; } class UserRepository { /** @var array<User> */ private array $users; public function find( string|null $firstName = null, string|null $lastName = null, string|null|Undefined $country = Undefined::Undefined, ): array/*<User>*/ { $users = $this->users; if ($firstName !== null) { $users = array_filter($users, fn(User $user) => $user->firstName === $firstName); } if ($lastName !== null) { $users = array_filter($users, fn(User $user) => $user->lastName === $lastName); } if ($country !== Undefined::Undefined) { $users = array_filter($users, fn(User $user) => $user->address?->country === $country); } return $users; } } // ... $userRepository->find(); // Retrieve all users $userRepository->find(firstName: 'Gandalf'); // Retrieve users whose first name is Gandalf $userRepository->find(country: 'UK'); // Retrieve users living in UK $userRepository->find(country: null); // Retrieve users living in no known country (as far as we know) $userRepository->find(country: null, lastName: 'Troy', firstName: 'Castor'); // Use all arguments you want
A vous de retrouvez dans cet échantillon de code les différentes nouveautés : null-safe operator, lambda function, union type, named arguments et enum !