Cette conférence est volontairement inutile, elle n'existe que pour satisfaire les curieux et curieuses. Vous êtes-vous déjà demandé quelle étaient les différences entre Composer et NPM ? Quel est l'équivalent de l'autoloader dans Node, et comment cela change la perspective sur la gestion de dépendances ? Comment comprendre les promise et async/await quand on programme en PHP depuis toujours ? Pourquoi PHP baigne-t-il moins dans l'asynchrone, est-ce qu'on loupe quelque chose avec notre bon vieux PHP-FPM ? Pourquoi les équivalents JS à Symfony et Laravel partent du frontend plutôt que du backend (Next, React…) ? Comment TypeScript se compare à PHP 8.2 + PHPStan, pourquoi certains projets majeurs s'éloignent de TypeScript, et qu'en retirer pour le futur du PHP typé ? Performances, exceptions, debugging, frameworks, tests, bundling, build, build et rebuild… À quoi ressemble une journée-type quand on a pas besoin de préfixer ses variables par des $ ?
Je fais du PHP depuis 2006 et j'ai plongé dans l'écosystème Node il y a 2 ans. Je participe aujourd'hui à la maintenance de projets open-source JavaScript/TypeScript qui comptent plus de 30 millions de téléchargements/mois. Voici mes découvertes, émerveillements et surprises, expliquées SANS TROLL à celles et ceux qui font du PHP tous les jours (et qui n'ont pas forcément envie de changer).
_ Matthieu Napoli : Je propose de plonger directement dans le sujet parce que je n'ai que 20 minutes. Un petit disclaimer juste avant de démarrer. C'est à ne pas prendre trop au sérieux. Je vais faire de grandes généralités. J'avais prévu de passer des tonnes de petits détails intéressants comme NPN, les Frameworks, etc. Mais je n'ai que 20 minutes. J'ai dû trancher dans les sujets. Je vais me concentrer sur trois gros sujets.
Il se rapproche un petit peu de PHP. Asynchrone, c'est bof. Le typage, vaut mieux du typage plutôt que de l'héritage. Et place aux façades. WTF, pas de problème. On va commencer par le premier. Async. Dans PHP, Pascal a fait une belle introduction sur le sujet plus tôt. On ne fait pas trop d'asynchrones. Mais il y a react, swool. Benoit Viguier en a fait un peu aussi. Ce n'est pas donné à tout le monde de faire de la 50. Je pensais arriver sur du JS, typescript. Query, c'est une méthode qui fait de la requête en base de données et qui retourne immédiatement. C'est une notion asynchrone. Ça permet de lancer une première et une deuxième requête. Avec le résultat await, on attend la réponse. Je vais paralléliser des insertions en base de données. Je vais pouvoir paralléliser plein de choses et paralléliser mon code. C'est vrai et c'est possible. Ce qui m'a surpris, c'est le reste du code JS ou TypeScript que j'ai pu voir. Il y en a pour faire des appels PHP à API. Lire des fichiers, écrire des fichiers, etc. Je trouvais cela gênant et ennuyeux, parce que la plupart du temps, quand je fais quelque chose, je veux le résultat tout de suite. Je me suis posé la question. Pourquoi il y a de l'asynchrone partout alors qu'on s'en fout la plupart du temps. Il faut aller voir comment ça fonctionne Node. Le runtime côté serveur. Comment Node fonctionne sur les serveurs. Pour faire tourner le code.
PHP tourne avec plusieurs processus. Php-fpm. Chaque requête est dans un processus séparé indépendant des autres. Si nous avons cinq Workers FPM, on va pouvoir traiter cinq requêtes de la même manière. Python travaille un peu de la même manière. D'autres langages travaillent autrement. Rust avec du multi-threading. Un thread par requête. Go avec les Goroutine, c'est une sorte de multi-threading. Je vous ai dit que je faisais des généralisations. Node fonctionne différemment. Un seul thread, un seul processus. Un event loop.
Comment nous pouvons gérer sur un serveur avec un thread et un processus 10 000 requêtes par seconde ? C'est bizarre à imaginer. Pour illustrer, voici deux processus qui gèrent deux requêtes. La bleue et l'orange. Quand il y a la couleur, c'est que le code s'exécute. Quand il y a le gris, c'est des inputs et outputs. Un appel API, une requête en base de données. Dans nos bases de back-end, il y a beaucoup de temps qui est passé sur une ** et peu de temps passé sur des calculs complexes. Il y a beaucoup de gris. Quand il y a du gris, pour orchestrer les différentes requêtes. Node va prendre un stade d'exécution et le mettre de côté, on reprend s'il y a un code à traiter en orange, jusqu'à ce que ce soit asynchrone. On reprend le bleu, etc. C'est du multi-testing coopératif réimplémenté au niveau du langage. Qui n'est pas fait par OS lui-même. C'est là où on voit que s'il y a un peu trop de bleu ou orange, ça pose des problèmes pour traiter les autres requêtes.
Je prends l'exemple de plusieurs qui s'exécutent. Rien d'autre ne tourne sur le serveur. Si on a quelque chose qui tourne trop longtemps sur Node. Tout le serveur est bloqué. C'est pour ça que nous avons de l'asynchrone partout. C'est une fonctionnalité utile. Ça permet de faire des trucs sympas.
(coupure de son)
... l'asynchrone, ce n'est pas nul. Il y a des fois où vous pouvez faire des trucs sympas. Mais ce n'est pas la folie. Au milieu, c'est bof. En PHP, on ne loupe pas non plus grand-chose à ne pas avoir l'asynchrone partout et une runtime comme Node. C'est ma conclusion en général.
Passons au deuxième sujet. Le typage. Il y a une chose qui m'a surpris quand je suis passé de PHP à JavaScript. Si je simplifie : Tout est une variable. Il y a des variables en JS. Nous ne sommes pas obligés de mettre les deux là. C'est cool. Mais on peut définir des variables avec des fonctions délinkées comme en PHP. Je crée une fonction et je l'appelle. Mais on peut avoir la fonction hello et la redéfinir. On peut mettre un entier dedans. C'est stupide. Mais je peux. C'est une variable. Il n'y a pas comme en PHP "enregistrer global et général" de tous les fonctions. Il y a un registre des variables. Tout est une variable. C'est pareil avec les classes. Si je définis la classe Foo, c'est une variable que je peux redéfinir. La classe disparaît, mais je peux. Dans la classe elle-même, les propriétés sont des variables de classe, mais les propriétés aussi. Ce qui est à gauche et à droite est équivalent. Une méthode est une propriété qui contient une fonction anonyme. On peut pas avoir une propriété qu'il a le même nom qu'une fonction. C'est une propriété intellectuelle que je trouve intéressante.
L'objet que j'écris à gauche à partir de la classe est équivalent à l'objet anonyme que j'ai créé à droite. Ça ressemble à une du Json. J'ai mis une fonction dedans. Je le nomme Hello. On peut créer des objets à la volée. On peut créer des objets vides. Et créer des méthodes. À des objets existants, on peut même prendre un objet créé dans une classe et remplacer des méthodes. Pour faire un décorateur, au lieu de prendre l'interface, de raper mon objet, je la remplace et je décore la méthode que je veux décorer. C'est bizarre, mais ça marche.
Je ne vais pas dire que c'est nul ou bien. Mais c'est intéressant. Mais là se pose la question : la PO, le typage, comment ça se passe ?
Comment on réconcilie le fait qu'il y a JavaScript d'un côté et TypeScript d'un autre côté qui est extrêmement intéressant au niveau du typage ? 8.1 ou 8.2, level 2000. Comment on réconcilie les deux ?
Petite intro sur TypeScript. C'est un langage basé sur JavaScript. C'est du JavaScript, mais avec des types. En haut, c'est un fichier pointé S. On va créer des types. Node n'exécute pas de TypeScript. Il y a des outils qui sont trop rapides, etc., mais les avantages de TypeScript valent le coup. C'est les types qui sont évalués à la population.
En TypeScript, j'ai retrouvé beaucoup moins de place que je retrouve dans PHP. Par exemple, en PHP... à droite, c'est PHP. J'ai défini une propriété pour chaque clé attendue. Ce typage à la volée de structures de données, c'est une interface à la volée. On peut faire la même chose en PHP avec Stan. C'est plus complexe, mais c'est le même principe. System native en TypeScript. Ce sont de définir des typages d'objets. On a des interfaces, on peut les implémenter dans des classes. Mais on peut faire des trucs plus fun. Je peux créer un objet à la volée après avoir défini mon interface qui est du type de l'interface, mais sans classe, sans implement, on vérifie si c'est compatible avec l'interface. Ça marche.
On peut aussi avoir une fonction qui prend un objet maps. Dans mes tests, je passe d'un objet anonyme défini à la volée. Il va valider que c'est compatible. C'est une interface qui n'a pas besoin d'être implémentée. C'est bizarre. Mais c'est intéressant. C'est logique quelque part. Ce serait de bonnes idées pour PHP.
On n'a pas besoin de faire de sous-classes. Je peux typer cela, mais je peux aussi dire : J'ai une fonction sign-up avec un besoin d'identification et de mot de passe. Je n'ai pas besoin de faire des milliards d'interfaces comme en PHP.
Là où je veux en venir, c'est que ce typage à la volée ou ce typage local, il est plus naturel ou plus facile à écrire. On n'a pas besoin de faire des tonnes de DTO parce que PCR3 ou 4. C'est beaucoup plus simple. Nous dictons plus facilement notre code. Le code plus typé, je considère cela cool. Ma généralité, c'est : Ce genre de typage plus local et à la volée, il donne des codebases plus typés. Ce sont de bonnes idées à reprendre en PHP.
Dernier chapitre qui est dans la continuité, parlons de l'état global. Comme je le disais, nous avons plus de classes en TypeScript. Comparé à PHP. On va retrouver moins d'injection de dépendances. Ça dépend des pratiques et des équipes, d'où elles viennent. Peu de classe, pourquoi ? Pour les classes de modèles, nous allons retrouver les classes et d'autres choses. Mais les services. En PHP, nous les écrivons sous forme de classe. En TypeScript et en JS, on va retrouver des fonctions. Prenons un service PHP classique comme un contrôleur sur une commande. En général, nous avons notre injection de dépendances dans le constructeur et nos méthodes. La même chose en TypeScript. Ça ressemblerait à cela. Un fichier. Nous importons nos dépendances. Ce sont des fonctions. Nous les importons. On s'en fout des exports. Ce sont des fonctions. Mais ce n'est pas si débile. Parce que le service est censé être stateless. C'est des fonctions statiques. "Oui, mais Matthieu, tu fais un petit raccourci. Tu oublies l'injection de dépendances." Ce ne sont pas de pures fonctions. Des fonctions statiques. Nous en avons assez peu dans JavaScript et TypeScript. Nous retrouvons plutôt des imports. Venant d'un monde comme vous, Symfony, l'injection de dépendances, ça m'a fait très bizarre au début. J'ai voulu faire comme les collègues et voir comment ça se passe. J'ai pris du recul pour dire : peut-être qu'il y a différents degrés d'injection de dépendances.
Prenons cet exemple. Nous sommes tous d'accord que c'est de l'injection de dépendances. Mais dans 80 % des cas dans nos applis, est-ce que ce ne serait pas du service locator ? Pourquoi ? Parce qu'ici, dans la majorité des cas, je sais très bien que je suis en train de demander aux Contenors de m'injecter l'entrée. C'est du service locator. Il y a assez peu de différence en pratique avec ça. Faire un contenor get qui est du service locator pur, peut-être que ce n'est pas si éloigné. Quand on compare les deux approches, on peut toujours coder contre des abstractions. Je demande l'interface. On peut toujours manquer cela, avec le service locator.
Dans le deuxième cas, elle est globale à toute application. L'injection de dépendances pure et dure sera la bienvenue pour faire de la librairie, du code qui va être utilisé par des tiers, les codes super complexes et du domaine poussé. Nous écrivons énormément de codes ultras ennuyeux. Des contenors Laravel. Super simple. Le service locator, dans ces cas, ça marche. J'ai pu le voir en JS. C'est pour cela qu'on trouve les services en juillet sous forme de fonctions. Le mot-clé import sert le service locator global. Le système de mocking en TypeScript vient impacter le mot-clé import. Et ça marche. Est-ce que les services PHP, on pourrait les réécrire en fonction ? Peut-être. Il nous manque l'autoloading des fonctions. Ce dont nous aurions besoin aussi, c'est un service locator global bien fait et un système de mocking bien fait. Pour écrire des textes. Peut-être que ça existe.
Les façades Laravel sont parfois décriées. Nous sommes parfois méfiants. Je me dis : En TypeScript, mon expérience est très similaire. Laravel et TypeScript. Ça marche. Je ne l'appliquerais pas partout. Et parfois, ça marche. Nous avons peut-être l'outillage pour le service locator, pour les tests en Laravel même avec les façades. Est-ce que je suis en train de dire : Remplaçons l'injection de dépendances par des façades ? Peut-être un petit peu. Pas tout à fait. Pas 100 %. Mais c'est cool de se poser des questions.
Donc, mes trois conclusions. L'asynchrone, c'est bof. Le typage local plutôt qu'un héritage. Place aux façades ? Il faudrait souligner le ?. Il faudrait challenger un petit peu les prérequis sur lesquels on construit depuis des années. Sortir de sa zone de confort, parfois ce n'est pas si perdu.
Si vous voulez discuter de tout cela, avec grand plaisir autour d'un café à manger tout de suite. Merci beaucoup.
(applaudissements)
_ Merci beaucoup. Nous avons un petit temps pour les questions.
_ Mince, je ne pensais pas.
_ Comment cela ?
_ Salut, Matthieu. Pour les personnes qui ne savent pas, nous avons eu un énorme débat sur Twitter sur le point numéro trois.
(coupure de son)
Symfony s'est battu pendant plus de 10 ans entre Symfony 1 et Symfony 6 aujourd'hui pour essayer que ce soit difficile de déprécier l'abstract controler avec tout cela pour qu'ensuite on requestionne l'architecture qui a été faite sur la base de discussion de l'architecture. Laravel a proposé quelque chose d'intéressant avec les façades. Ce n'est pas inintéressant. Mais il y a des problématiques sur la traceability qui sont non négligeables. Sur la charge cognitive, les besoins internes. Je suis d'accord avec les deux premiers points, mais pas le numéro trois.
_ Je suis d'accord avec toi, mais nous parlons de trucs. LA solution partout à appliquer tout le temps. Il faut une solution pour les utilisateurs. Je fais un rappel avec la conférence que j'ai faite il y a deux ans. L'architecture progressive. Ne pas appliquer toujours les mêmes solutions partout. Faire un truc tout bête, je le fais en façade, je m'en fous. Le code n'est pas compliqué. Ça marche. Prendre la meilleure solution pour le problème qu'on a actuellement. Et j'ai fait tellement de projets bêtes. Ça marchait très bien pour un fichier Excel. Ça dépend des cas. Il faut réfléchir et trouver la bonne solution. Ce n'est pas toujours facile. C'est plus simple de dire qu'il y a un projet et une solution qu'on applique partout.
_ Du coup, je n'ai pas d'autres questions.
(applaudissements)
Tweets