1. PHP RFC: Class Friendship

1.1. Présentation de la RFC

Date: 2017-09-21
Url de la RFC : https://wiki.php.net/rfc/friend-classes

Principe de base :
on introduit le mot clef "friend" qui permet à une classe d'avoir accès aux propriétés protected et privée d'une autre classe pour éventuellement séparer les règles d'affichages des paramètres de contenu.

Clairement, je comprend pourquoi cette RFC a été refusée en grande majorité par la communauté (6 votes POUR / 27 votes CONTRE).
Alors déjà que l'utilisation des Traits en PHP me semble être une grosse ruse pour palier à l'héritage multiple, alors ce principe de Friend me semble apporter plus de complexité dans le code que la souplesse que cela entend.
La goutte de trop est l'exemple du FibonacciTest : on commence à mettre une dépendance avec les classes de test DANS le code de l'application.
Techniquement, on sent bien que le dossier src/test ou équivalent ne doit pas partir en l'état en production, le code source qui tourne en production doit être le plus light possible et ne pas embarquer des choses qui ne s'y executeront jamais : d'une part c'est plus "écolo" car ça évite de transporter des milliers de fichiers pour rien sur les serveurs de production mais en plus si quelqu'un arrivait à executer des fichiers de test à distance, rien ne dit qu'il executerait pas un programme qui effacerait la base de données pour charger son jeu de test.

Donc non.

La réponse la plus satisfaisante pour lire des attributs publiques reste de les rendre accessibles par des getters soit généraux, soit spécifiques dans le cas où on souhaiterait qu'une et une seule classe puisse avoir accès aux données.

1.2. Exemple sans l'usage de friend via des getters classiques

Eventuellement, ça pourrait ressembler à ceci (note : j'ai enlevé la dépendance avec la classe Uuid, non pertinente) :

NoFriend.php
<?php

class Person
{
    protected $id;
    protected $firstName;
    protected $lastName;

    public function __construct($id, $firstName, $lastName)
    {
        $this->id = $id;
        $this->firstName = $firstName;
        $this->lastName = $lastName;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getFirstName()
    {
        return $this->firstName;
    }

    public function getLastName()
    {
        return $this->lastName;
    }
}

class HumanResourceReport
{
    private $person;

    public function __construct(Person $person)
    {
        $this->person = $person;
    }

    public function getFullName()
    {
        return $this->person->getFirstName() . ' ' . $this->person->getLastName();
    }

    public function getReportIdentifier()
    {
        return "HR_REPORT_ID_{$this->person->getId()}";
    }
}

$person = new Person('uniq_id', 'Alice', 'Wonderland');
$report = new HumanResourceReport($person);

var_dump($report->getFullName()); // string(16) "Alice Wonderland"
var_dump($report->getReportIdentifier()); // string(49) "HR_REPORT_ID_uniq_id"

1.3. Exemple sans l'usage de friend via des getters spécifiques

Voici l'idée : les getters de la classe Person vérifient que la classe appellante est bien la seule à avoir le droit d'accéder aux données.
La difficulté ici est de savoir quelle est la classe appelante et en PHP, une (seule?) façon de faire est d'utiliser la fonction debug_backtrace qui permet de savoir le cheminement entre les différents appels de fonctions.

<?php
class Person
{
    protected $id;
    protected $firstName;
    protected $lastName;

    public function __construct($id, $firstName, $lastName)
    {
        $this->id = $id;
        $this->firstName = $firstName;
        $this->lastName = $lastName;
    }

    public function mustBeGranted()
    {
        $trace = debug_backtrace();

        if (isset($trace[2]['class']) && HumanResourceReport::class === $trace[2]['class']) {
            return true;
        }

        throw new \Exception("The caller class is not allowed to do this.");
    }

    public function getId()
    {
        $this->mustBeGranted();

        return $this->id;
    }

    public function getFirstName()
    {
        $this->mustBeGranted();

        return $this->firstName;
    }

    public function getLastName()
    {
        $this->mustBeGranted();

        return $this->lastName;
    }
}

class HumanResourceReport
{
    private $person;

    public function __construct(Person $person)
    {
        $this->person = $person;
    }

    public function getFullName()
    {
        return $this->person->getFirstName() . ' ' . $this->person->getLastName();
    }

    public function getReportIdentifier()
    {
        return "HR_REPORT_ID_{$this->person->getId()}";
    }
}

$person = new Person('uniq_id', 'Alice', 'Wonderland');
$report = new HumanResourceReport($person);

var_dump($report->getFullName()); // string(16) "Alice Wonderland"
var_dump($report->getReportIdentifier()); // string(49) "HR_REPORT_ID_uniq_id"
var_dump($person->getFirstName()); // PHP Fatal error:  Uncaught Exception: The caller class is not allowed to do this.

1.3.1 Pourquoi retourner une exception ?

1. Parce que c'est mieux que de faire un trigger_error puisqu'on peut le catcher de façon plus élégante.
2. Il faudrait définir une exception spécifique.

1.3.2 Pourquoi l'index 2 ?

Ici on utilise l'index 2 du debug_backtrace car dans le cas où on définirait une méthode intermédiaire mustBeGranted :
index 0 : on aurait class: Person, function: mustBeGranted
index 1 : on aurait class: Person, function: getId (ou l'une des deux autres)
index 2 : on aurait class: HumanResourceReport, function getFullName (ou getReportIdentifier)
Je ne recommande pas forcément ce genre de code pour deux raisons :
1. L'usage de debug_backtrace ne devrait jamais être utilisé ailleurs qu'en développement/test
2. La classe Person ne devrait pas avoir de dépendance avec la classe HumanResourceReport (c'est d'ailleurs aussi pour ça que je suis contre l'idée du 'trait' friend).


2. PHP RFC: Default constructors

2.1. Présentation de la RFC

Date: 2014-11-05
URL : https://wiki.php.net/rfc/default_ctor
Auteur: Stas Malyshev

Cette RFC a été refusée car elle n'a pas eu assez de votes POUR alors qu'en majorité absolue, elle aurait pu passer (27 POUR VS 20 CONTRE).

L'idée derrière cette RFC est assez simple, on permet à une classe fille d'utiliser même s'il n'existe pas le constructeur de la classe mère.

Selon l'auteur, on pourrait toujours faire un parent::__construct(); dans le constructeur de la classe fille sans trop se soucier de ce que fait exactement la classe mère. Cela éviterait aussi de devoir modifier toutes les classes filles si jamais leur classe mère avait d'un coup un constructeur.

Pourquoi ce n'est pas grave si cette RFC ne passe pas ?
Alors, il me semble tout à fait raisonnable de penser que le role du développeur est non seulement d'être curieux sur les classes dont hériteraient les siennes mais en plus d'être responsable en les utilisant de la bonne manière.

La classe mère est modifiée ? Qu'à cela ne tienne : oui il faut peut-être rééditer toutes les classes filles. Oui, cela peut tout à fait être rébarbatif, mais le seul domaine où le développeur ne doit pas être paresseux est justement celui de la programmation.
Créer un constructeur par défaut vide et laisser les développeurs l'utiliser dans leurs classes filles "au cas-où", non seulement ce n'est pas très rigoureux comme façon de faire, mais en plus, rien n'indique que le constructeur, s'il venait à être créé dans la classe mère n'aurait aucun paramètre, donc, il serait fort probable que dans beaucoup de cas, il faille de toute façon revenir sur le constructeur des classes filles.

2.2. Alors que faire pour pallier à ceci ?

Et bien, on peut tout à fait créer une classe intermédiaire dont étendrait les classes filles qui possèderaient un constructeur qui héritera si besoin du constructeur de la classe grand-mère. Cela ne permet pas de répondre à un constructeur qui aurait plusieurs paramètres, mais cela permet en tout cas de répondre au besoin de cette RFC.

2.2.1 Proposition d'implémentation

Nous avons ici quatre classes, la classe GrandPa, celle dont va hériter la classe Dad, elle-même dont héritent les classes Daughter et Son.
La classe Dad joue ici un rôle de tampon entre la classe GrandPa et les deux autres, on peut lui créer un constructeur vide "au cas-où" qui héritera si besoin du constructeur de la classe GrandPa si jamais celui-ci venait à être créé dans le futur.

<?php

class GrandPa
{
    
}

class Dad extends GrandPa
{
    public function __construct()
    {
        
    }
}

class Son extends Dad
{
    public function __construct()
    {
        parent::__construct();
    }
}

class Daughter extends Dad
{
    public function __construct()
    {
        parent::__construct();
    }
}

Ici encore, c'est une idée que je ne cautionne pas dans la mesure où c'est du code qui ne sert à rien au moment où il est fait, autant se retrousser les manches au bon moment plutôt que de prévoir d'innombrabres probables situations.

2.3. Conclusion

C'est plutôt une bonne chose que cette RFC ne soit pas validée car la création de constructeurs fictifs aurait surement créé plus d'effets de bord qu'autre chose, car si jamais la classe mère était mise à jour sans même que le développeur s'en aperçoive, c'est à ce moment là qu'il perdrait la maitrise de son code. Développer sur de l'implicite ne me semble pas être une bonne chose.

3. PHP RFC: Restrict $GLOBALS usage

Date : 2020-12-02
Auteur : Nikita Popov
URL : https://wiki.php.net/rfc/restrict_globals_usage

A creuser mais je ne vois pas l'intérêt de modifier quelque chose qui ne devrait pas être utilisé depuis un moment.

4. PHP RFC: Wall-Clock Time Based Execution Timeout

4.1. Introduction de la RFC

Lien de la RFC : https://wiki.php.net/rfc/max_execution_wall_time
Date d'introduction : 2020-12-12

Cette RFC propose d'introduire le paramètre INI max_execution_wall_time.
Ce paramètre définit un temps de time-out auquel le script PHP doit s'arreter.

Ce qui me semble existe déjà puisque ça porte d'ailleurs le nom de max_execution_time.
Sauf que l'auteur indique le max_execution_time utilise le temps CPU et non pas le "wall-clock time" (alias du real-time).

4.2. Wall-clock time VS CPU time

Tout se joue dans la définition de ces deux termes.
* Dans le cas du CPU Time, il faut que le script PHP fasse un vrai traitement (càd qu'à chaque ligne de code interprété, on compte à nouveau le temps qu'il reste).
* Dans le cas du Real Time, peu importe ce que le script PHP fasse, si le temps est écoulé, il s'arrête, même s'il faisait quelque chose d'important.

Autrement dit, on pourrait penser intuitivement que le max_execution_time correspond au cas de figure numéro 2, alors qu'il correspond au cas numéro 1.

L'auteur pointe donc un problème : si le script PHP est en attente de quelque chose, il continu d'être executé.
Et donc, il risque d'y avoir une accumulation d'execution de script PHP au niveau du serveur web, ce qui peut faire tomber un site web.

4.3. Démonstration

J'ai souhaité vérifier qu'il était possible de dépasser le temps de max_execution_time avec un appel HTTP.
Pour ce faire, j'ai créer un fichier index.php que j'interroge via mon navigateur, rien de très sorcier.

4.3.1 Cas où le script PHP s'arrète après un time out

index.php
<?php
$i = 0;
do {
    $i++;
} while ($i < 100000000000);

Si on considère que le paramètre max_execution_time est à la valeur 30, ce genre de code part en time out (le nombre d'itérations dépend de la puissance de votre CPU.
Ici le script fait un traitement, on peut se mettre à la place d'un outil comme xdebug et faire des boucles visuellement.
Pendant ce traitement les secondes qui défilent sont prises en compte.

Le script s'arrete au bout de 30 secondes, on peut en voir la trace dans le log du serveur web (ici Apache/2.4.41) :
[Tue Dec 15 15:04:05.217052 2020] [php7:error] [pid 1609] [client 127.0.0.1:43918] PHP Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/html/index.php on line 8

4.3.2 Cas où le script PHP ne s'arrète pas

index.php
<?php
sleep(50);

Si on considère que le paramètre max_execution_time est à la valeur 30, ce genre de code continue de tourner après 30 secondes. La page s'affiche au bout de 50 secondes.
Ici c'est un sleep, on peut le changer, sauf que ce problème peut tout à fait arriver lorsque notre code PHP fait appel à un service externe (mysql/memcached/url). Et là c'est effectivement le début d'une belle pagaille car les appels vont se cumuler et le service d'en face va avoir deux options : soit il est très performant au démarrage et il traite tout ce petit monde, soit il décide de dire stop et tous les scripts PHP en cours d'execution font finalement s'arreter.

4.4. Pertinente de la RFC

4.4.1 Le plutôt pour

Alors est ce que cette RFC est pertinente ? Et bien, la réponse me semble oui car on aurait préféré que ce comportement fusse celui du max_execution_time.
Ce qui est ennuyeux, comme le soulève d'auteur, c'est qu'il serait extrèmement dangereux de modifier le comportement du max_execution_time car cela aurait des effets de bords potentiellement très graves chez l'ensemble des utilisateurs du PHP (80% des sites web).
Le plus prudent étant en effet de créer un nouveau paramètre appelé max_execution_wall_time.

4.4.2 Le plutôt contre

Le risque : cependant, il faut aussi se dire que si l'on force le script PHP à réellement s'arreter, cela veut dire qu'il ne pourra plus pallier à la faiblesse des services qu'il utilisera. Autrement dit, si le serveur MySql d'en face met 2 minutes à exécuter une requête non optimisée (cas classique : il manquerait un index), et bien le script PHP plantera alors que ce n'était pas en soi de sa faute.
Donc, avant d'introduire ce paramètre max_execution_wall_time, il faudrait modifier son code pour introduire des temps limites aux appels externes, et donc être capable de determiner pour tous les chemins de code possibles le temps maximum que l'on accorderait aux services externes, chose qui me semble en pratique infaisable.

En bref, si le max_execution_time a ce comportement là, ce n'est peut être pas pour rien. On verra ce qu'il en est dans les mois à venir de cette proposition de Máté Kocsis.

5. PHP et son futur

5.1. LA page à connaitre pour faire de la veille en PHP

Il existe une page que je ne connais que depuis novembre 2020 et cette page a changé ma vie (enfin non, mais bon, c'est pour faire genre) : https://wiki.php.net/rfc

Cette page est peut être LA page qui permet de faire la veille la plus pragmatique que l'on puisse faire sur le PHP (et uniquement le PHP, cela ne couvre donc pas les frameworks, les autres bibliothèques et les autres langages, bien que certaines RFC y fassent parfois référence).

La page est découpée en plusieurs sections :

la section "Under Discussion" : ici tout se débat, tout se discute, ce sont les RFCs imaginées la veille après une bière de trop et écrites tout de même noir sur blanc et envoyé par email à la communauté PHP. C'est une section très riche pour la créativité car on parie un peu sur la pertinence de la proposition. Lorsque la RFC a été bien réfléchie passe alors l'étape du vote pour indiquer si oui ou non cela vaut le coup de l'implémenter dans le langage.
Lorsque les développeurs se sont mis d'accord sur l’intérêt de la RFC, c'est le moment d'en faire un papier un peu plus sérieux dans la section "In Draft", c'est l'étape de la conception et du développement. Lorsque celui ci est terminé, les développeurs votent à nouveau.

Suite au vote sur la RFC et l'acceptation de celle-ci, la RFC atterrit alors dans la section "Accepted", très chaud à ce moment là pour intégrer la version suivante du langage s'il s'agit d'une modification du code (parfois ce sont des RFC qui ne ciblent pas forcément le PHP).

la section "Implemented" qui veut dire que non seulement la RFC a été acceptée par l'équipe de développement mais en plus qu'elle fait partie intégrante de la version du langage ou des futures décisions.
Ce qui est intéressant lorsque l'on est simple utilisateur du PHP, c'est d'assister à ces réflexions extrêmement riches pour l'esprit. Non seulement elles permettent de comprendre pourquoi l'équipe de développement a validé ou non une idée mais en plus permet un allèchement non négligeable sur les futures montées de version.

On se pose également des questions légitimes comme "ha tiens c'est vrai que ça, ça existe sur d'autres langages, c'est étonnant et même curieux que non seulement ce ne soit proposé qu'à peine aujourd'hui mais surtout que l'on a réussi à s'en passer pendant toutes ces années" ; car c'est cela qui est assez magique avec les langages de programmations (oserais-je ajouter "Turingué" histoire d'appuyer le pléonasme) : dès lors que les fondamentaux existent (boucle/conditions/quelques types comme char, bool et float/modification de l'espace mémoire), tout le reste n'est que fioritures et gâteries pour le développeur (on appelle ça du sucre syntaxique).
Car honnetement : si la visibilité n'existait pas, ni les classes d'ailleurs, ni mêmes les types bool, int et même float, on pourrait s'en sortir uniquement avec des for et des string.

A noter qu'une RFC ne concerne pas uniquement une modification du code de PHP mais peut également agir sur la façon même dont les débats sont menés (ex: cette RFC qui revient sur une règle de vote des RFCs (c'est presque de l'inception comme ces parenthèses !))

5.2. Si quelqu'un sait pourquoi...

Ce qui est dommage avec ce système de RFC, c'est qu'il ne me parait pas possible de connaitre les motivations des votants.
Ainsi, même si on a l'intuition qu'une RFC n'est pas une bonne idée, il n'est pas possible de savoir si notre intuition est celle partagée par la majorité des votants ou si l'on est complétement à côté de la plaque.

6. PHP 7.1 Le type void en déclaration de méthode

Alors même si on aurait tendance à dire que ça sert un peu à rien, c'est toujours plus jolie à regarder lorsqu'on visualise une interface.

Mise à part la beauté générale, les programmes de vérification syntaxique auront également moins de peine à verifier que la méthode comporte bien un type de retour.

Exemple :

<?php
function myFunction(): void
{}


6.1.1 A quand la déclaration d'un retour de fonction d'un tableau d'objets ?

Cependant, il y a quelque chose qui me semble manquer beaucoup plus génant, c'est le retour de type tableau d'instances comme ceci par exemple MyClass[], idem pour le nom des paramètres.

Voici un code qui illustre l'utilisation de genre de type de retour :

<?php
function myFunction(): MyClass[]
{
      return [
        (new MyClass())->setAttr(true),
        (new MyClass())->setAttr(false),
      ];
}

C'est ça que j'appelle le vrai typage fort, dans l'idéal il faudrait arreter de penser qu'il faut à tout pris des retours de type array ou mixed (comme c'est le cas en PHP 8.0...), ça créé toujours de l'ambiguité dans le code, et quand on conçoit du code avec des types ambigüs, on a souvent le droit à des heures de TMA derrière. En outre, dans le monde du travail, c'est rarement le même développeur qui concoit et qui débugue par la suite, donc plus le code est explicite, plus c'est simple à corriger.

7. PHP 7.3 Rien de très utile


7.1. PHP 7.3 JsonException


Cette version de PHP nous fait faire l'économie de ne plus avoir à tester si le retour de json_decode est false pour retourner une exception personnalisé de type JsonException.
On peut en effet désormais utiliser l'option JSON_THROW_ON_ERROR pour obtenir directement une \JsonException.

7.1.1 En PHP <7.3 :

<?php
namespace App\Service;

use App\Service\JsonParserInterface;
use App\Exception\JsonException;

class JsonParser implements JsonParserInterface
{
    public static function jsonDecode(string $string): string
    {
        $decodedJson = json_decode($string);

        if (false === $decodedJson) {
            throw new JsonException();
        }

        return $decodedJson;
    }
}

Et on l'appelait par exemple de cette façon-là :

<?php
try {
    $decodedJson = JsonParser::jsonDecode($jsonToDecode);
} catch (JsonException $e) {
    // Log, alert etc...
}


7.1.2 En PHP 7.3, sans utiliser de classe spécifique :

<?php
try {
    $decodedJson = json_decode('bad json', null, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
    // Log, alert etc...
}


On pourrait penser que grâce à la version 7.3 de PHP, on pourrait désormais supprimer le service JsonParser puisque l'exception est désormais renvoyée par json_decode mais étant donné qu'il faut tout de même ajouter l'option JSON_THROW_ON_ERROR, cela semble tout de même toujours plus judicieux de continuer à l'encapsuler afin d'éviter de retrouver des dizaines d'occurences à json_decode dans le code source de son application.

7.1.3 La classe JsonParser en PHP 7.3 :

On pourrait définir cette classe en utilisant l'annotation @throws afin d'indiquer que la méthode retourne une exception native \JsonException.

<?php
namespace App\Service;

use App\Service\JsonParserInterface;

class JsonParser implements JsonParserInterface
{
    /**
    * @throws \JsonException
    */
    public static function jsonDecode(string $string): string
    {
        return json_decode($string, null, 512, JSON_THROW_ON_ERROR);
    }
}

7.1.4 La classe JsonParser en PHP 8.0 :

On peut simplifier l'appel en passant par les attributs nommés, d'où l'intéret de ne le faire qu'une et une seule fois dans son code.

<?php
namespace App\Service;

use App\Service\JsonParserInterface;

class JsonParser implements JsonParserInterface
{
    /**
    * @throws \JsonException
    */
    public static function jsonDecode(string $string): string
    {
        return json_decode($string, flags: JSON_THROW_ON_ERROR);
    }
}


7.2. PHP 7.3 Les virgules en fin de fonctions

Toujours pratique lorsque l'on duplique une ligne courante au moment de l'édition d'une méthode, cela permet d'avoir une façon de coder un peu plus flexible.

C'était déjà bien chouette de pouvoir le faire dans les tableaux.
Par contre, dans le cas des fonctions qui ont peu de paramètres dont le nom du type est court, cela génère beaucoup de lignes.

Voici un exemple d'utilisation où cela fait beaucoup de lignes pour pas grand chose :

<?php
function sum(
    string $a,
    string $b,
) {
   return $a + $b;
}


Voici un exemple d'utilisation où cela est déjà un peu plus pertinent en PHP 8.0 (on voit dans le futur) :

<?php
class UserPDFDocumentConvertor
{
    public function __construct(
        protected UserInterface $userInterface,
        protected DocumentManager $documentManager,
        protected EntityManager $entityManager,
        protected UserToSimpleUserMapper $userToSimpleUserMapper,
        protected PDFConvertor $pdfConvertor,
    ) {}
}

Ici, il est beaucoup plus simple d'injecter des services à notre classe puisqu'avec les raccourcis clavier (Cltr+D par défaut dans PHP Storm 2020.3), il est facile d'ajouter un paramètre de plus.

8. PHP 8.0 Un café bien sucré !

8.1. Introduction

Il y a deux bonnes nouvelles à cette version de PHP, la première est que l'équipe de développement prouve qu'elle se bouge le cul et que la version 7 n'était pas juste un coup d'essai, la seconde est que la compilation JIT vend du rêve : on souhaite tous que sans rien faire à part un apt upgrade, notre code -même moche- s'execute beaucoup plus vite ; c'est bien arrivé avec PHP 7 !

Concernant les autres nouveautés, sans dire que la plupart ne sert pas à grand chose, d'autres sont potentiellement risquées à mettre en place.

Je reprends ici l'article que j'ai vu présentant certaines mises à jour de PHP 8.0, je vous invite à le lire avant cette page : https://hoa.ro/fr/blog/2020-11-26-php-8-0-is-out/

8.2. Les nouveautés une par une

8.2.1 Les arguments nommés

Alors que Java fait ce qu'on appelle du polymorphisme et permet de créer des méthodes du même nom qui traitent d'objets de classe ou de type differents, l'équipe derrière PHP a préféré pallier aux problèmes d'utilisation qu'engendre les arguments facultatifs en introduisant les arguments nommés, le projet était dans les clous depuis 2013, c'est dire la volonté de l'équipe de développer ce truc.

Cette nouvelle version permet, lors de l'appel d'une fonction, de dire quel argument prend quelle valeur, cela permet donc d'éviter de copier/coller les valeurs par défaut de tous les arguments qu'on ne souhaite pas utiliser pour ne modifier que le dernier (ou l'un des suivants), technique donc assez dangereuse puisque, si via une mise à jour du langage, la valeur du paramètre par défaut change, il faudra peut être repassé sur notre code.
Avec les arguments nommés, on doit donc désormais copier coller le nom du paramètre, la bonne affaire !

Autant donner mon avis : c'est continuer à patauger dans la boue que d'utiliser les arguments nommés à tire la rigaud pour pallier aux fonctions natives et historiques ayant trop d'arguments facultatifs.

Ce qui me semble plus judicieux : créer des services et manipuler ces fonctions PHP natives qui ont beaucoup de paramètres facultatifs en jouant sur la valeur des attributs de l'objet qui encapsulera la fonction.
Dit comme cela, c'est très lourd et évidemment si on souhaite utiliser une fonction PHP une seule fois dans une commande par exemple, il est peut être mieux d'utiliser les arguments nommés. Autrement dit, ce n'est qu'une question de mesure et de bon sens.
Il serait assez farfelu par ailleurs d'encapsuler toutes les fonctions de PHP dans des classes. Même si c'est ce que font déjà d'autres langages comme le JS (ex: Math) ou le Java (String).

Certaines fonctions comme la fonction htmlspecialchars mériteraient clairement d'être encapsulées pour éviter de retrouver cette fonction à des milliers d'endroits du code. Ex, la classe PHPEngine du composant Templating de Symfony.

Donc le bon sens pour deux cas de figure :
* vous souhaitez utiliser une fonction native de manière exceptionnelle (dans du code poubelle, dans du code one shoot, dans un algorithme très précis) : OK pour la fonction native et ses arguments nommés
* vous souhaitez utiliser une fonction native de manière fréquente : encapsulez-la dans un service !

8.2.2 Les attributs

Alors là, prudence de chez prudence. La configuration des routes est un très bon exemple, on pourrait le faire en xml, en yaml, en annotation, voilà qu'on peut le faire en attribut. C'est pas loin de la boite de pandore ce qu'ils nous ont pondu là. D'un point de vue personnel, je ne souhaite pas être le premier à me casser les dents là dessus.

Petit détail amusant : on se dit depuis des années qu'il faut arreter d'utiliser le @ qui permet de planquer les erreurs PHP, encore plus pertinent depuis qu'ils ont catchés toutes ces erreurs pour nous permettre de les récupérer sous la forme d'exception. Dire qu'on n'utilise pas le @ parce qu'il est déjà utilisé pour quelque chose de mauvais, c'est d'autant plus dommage. Il aurait fallu qu'ils nous disent "Dès PHP 8, il n'est plus possible d'utiliser le @ pour rendre silencieuse les erreurs". Ils auraient pu alors l'utiliser pour les annotations.

8.2.3 Les types d’union

Cela rejoint mon avis sur les arguments nommés, faire une méthode qui accepte du poulet/du cochon/la valeur 8 et enfin le "j'en sais rien" derrière le mixed, ce n'est vraiment pas ce que je trouve de plus élégant. Le typage fort OUI, à plus forte raison quand on souhaite contrôler ce qui rentre dans les méthodes de nos classes, si c'est pour accepter tout et n'importe quoi en valeur d'argument, autant effectivement ne rien mettre ou mettre du mixed.
Là encore, le polymorphisme me semblait une solution bien plus adaptée.

8.2.3.1 Comparaison polymorphisme VS Type d'union

Si le polymorphisme existait en PHP :

interface MyClass
{
    public function myMethod(int $arg);
    public function myMethod(string $arg);
}


Voilà ce que qu'on peut faire en PHP 8.0 :

interface MyClass
{
    public function myMethod(int|string $arg);
}


Il n'y a alors pas le choix, il faudra forcément distinguer les différents types dans l'implémentation (ou laisser la magie du composant ou de PHP faire le cast lui même, ce qui n'est pas terrible.).

8.2.3.2 Petite apparté pour un cas similaire

Même chose pour le try catch \Exception $e, quand on écrit ceci, c'est surtout de la paresse qui s'exprime et si l'excuse c'est qu'on ne sait pas ce que renvoie telle ou telle librairie comme exception, c'est peut être qu'on n'a pas eu la patience de regarder les cas d'erreurs en détail et donc au final, l'utilisateur de l'application verra un beau message lui indiquant qu'une erreur a eu lieu et que toute l'équipe est sur le pont pour la résoudre, ce qui est bien entendu un mensonge.

8.2.4 Déclaration des attributs dans le constructeur

Alors...oui mais avec parcimonie. Ici aussi c'est du sucre syntaxique, on gagne un peu de temps et on évite de copier coller le nom de l'attribut.
Mais, certains attributs ne sont pas forcément setté par le constructeur, donc cela veut dire qu'il y aura du code dans et en dehors du constructeur, ce qui demandera donc au développeur de regarder à la fois en tête de classe et dans la signature du constructeur, autant dire que pour le côté confort de lecture, on repassera.

8.2.5 Nouvelles fonctions str_*

Mieux vaut tard que jamais. Cela ajoute "peut-être" un gain de performance, à voir. A noter qu'on persiste dans le Snake Case, alors que la tendance est depuis quelques temps au Camel Case (je n'ai pas de préférence entre les deux, mais j'aimerais pour le plaisir éviter de mélanger les deux entre le langage natif et tel ou tel composant).

8.2.6 Nouvelle interface Stringable

Le _toString est quelque chose qui ne devrait être utilisé qu'en dev, tout est dit.

8.2.7 L’opérateur nullsafe

Je dois dire que ce sucre syntaxique là est plaisant mais à utiliser avec parcimonie car comme le @, c'est un coup à se retrouver avec des dizaines de faux '?' qu'un développeur aura mis pour résoudre des bugs alors que peut être que le bug réel est avéré : ex: si le client n'a pas d'adresse postal alors qu'il a passé sa commande et qu'il souhaite être livré, ce n'est pas trop la peine de corriger le bout de code qui fait un getAdress()->getCP() ; non, il faut :
1. mettre à jour la donnée pour renseigner son adresse
2. mettre à jour le code qui aurait dû la renseigner au moment de la commande, quite à ne pas confirmer la commande par ailleurs

8.2.8 Lever des exceptions dans les expressions

Je ne suis pas ultra fan des expressions inlines car elles sont moins lisibles et me semblent plus difficiles à débugguer, mais cela n'est pas génant à voir.

8.2.9 match, le cousin du switch

Encore du sucre, mais plutôt utile.
Dans la plupart des cas cependant, un switch peut aussi faire plusieurs affectations de valeurs, avec le match, on serait obligé de faire du copier/coller tandis qu'avec le switch, chaque test n'est fait qu'une fois.

Ex:

Avec un switch

<?php
$myLang = 'francais';

switch($myLang) {
    case 'francais':
    case 'marseillais':
        $myCountry = 'france';
        $myLocale = 'fr_FR';
        break;
    default:
    case 'anglais':
        $myCountry = 'england';
        $myLocale = 'en_EN';
        break;
}

Avec un match

<?php
$myCountry = match($myLang) {
    'francais' ,  'marseillais' => 'france',
    'anglais', default => 'england',
};

$myLocale = match($myLang) {
    'francais', 'marseillais'  => 'fr_FR',
    'anglais', default  => 'en_EN',
};

Ici, même si on réplique le test, surement négligeable au niveau du temps d'execution, mais si on ajoutait une langue, il faudrait modifier le code à deux endroits.

8.3. Conclusion

Cette version de PHP 8.0 démontre que le développement de PHP continue. On appréciera l'effort que font les développeurs pour simplifier l'utilisation du langage et le rendre plus mature. On regrettera cependant que certaines nouveautés reposent encore sur des bases pointées du doigt depuis longtemps par les développeurs comme très discutables (erreurs @, nombre d'arguments facultatifs, nommage, le type mixed).

9. Les magazines orientés PHP

Il ne s'agit pas à proprement parlé de magazines papier mais plutot de sites web qui indiquent de façon fréquente les nouveautés dans le monde du PHP.

Comme :
* https://blog.jetbrains.com/phpstorm/category/php-annotated-monthly/

La version PDF du numéro de décembre 2020 de PHP arch
* https://www.phparch.com/wp-content/uploads/2020/12/PHP8-Distilled-December-2020-phparch.pdf