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.