Ça commence par une alerte Sentry un mardi soir. Une requête a échoué, le message d'exception est là, et dedans, imprimée en toutes lettres, tu trouves l'adresse e-mail d'un client et la moitié d'un numéro de téléphone. Personne n'a loggé ça volontairement. QueryException a simplement toujours interpolé les valeurs liées dans son message, et ce message se retrouve copié dans tes fichiers de logs, dans la table failed_jobs, dans l'APM auquel tu envoies tes exceptions. Laravel 13.27 met enfin un interrupteur là-dessus : mask_bindings_in_exception_messages, configurable par connexion, garde les placeholders ? dans le texte du message, tandis que getBindings() continue de renvoyer les vraies valeurs quand tu les demandes délibérément.
<?php
// config/database.php (excerpt)
return [
'connections' => [
'mysql' => [
'driver' => 'mysql',
'mask_bindings_in_exception_messages' => env('DB_MASK_BINDINGS', false),
],
],
];Ce drapeau est mon coup de cœur d'une période inhabituellement dense : onze versions mineures entre 13.17 et 13.27, sorties en moins de trois mois. La tête d'affiche, c'est évidemment le nouveau composant image officiel, et j'y viendrai. Mais voici ma position : les fonctionnalités qui méritent ton après-midi de mise à jour sont les défensives, celles dont la valeur n'apparaît que sous trafic concurrent ou en plein incident, et la plupart arrivent désactivées ou planquées dans une ligne de changelog que personne ne retweete. Parcours ces versions en cherchant du brillant et tu passeras à côté des ajouts qui t'auraient épargné un postmortem.
Le drapeau de masquage n'est pas seul. La 13.23 a discrètement fait passer la comparaison du secret de contournement du mode maintenance par hash_equals() partout, alors qu'avant elle était résistante aux attaques temporelles sur certains chemins de code et un simple === sur d'autres. Un détail, d'accord. Mais l'hygiène des logs et les comparaisons à temps constant, c'est la catégorie de travail qui ne fait jamais une keynote et qui décide si ton prochain audit de sécurité sera ennuyeux. Je mettrais DB_MASK_BINDINGS à true dans chaque nouveau projet et, pour les anciens, va faire un grep des @ dans la table failed_jobs. Ce qui va remonter risque de ne pas te plaire.
Vient ensuite la famille des race conditions. La 13.27 ajoute refreshForUpdate() sur les modèles Eloquent. Imagine un coupon avec trois utilisations restantes juste après l'envoi de l'e-mail marketing : deux requêtes touchent l'endpoint dans les mêmes cent millisecondes, les deux lisent le compteur, les deux voient de la marge, les deux écrivent, et tu as offert plus de réductions que le coupon n'en autorisait. Le verrouillage pessimiste règle ça, et Eloquent le supporte depuis des années, mais seulement au moment de la requête. Quand un modèle était déjà entre tes mains, le contournement était maladroit : lancer une seconde requête verrouillée dans la transaction, puis continuer avec cette copie fraîche pendant que la variable d'origine restait là, leurre périmé qui n'attendait que de te faire trébucher. refreshForUpdate() réduit cette gymnastique à un seul appel qui relit le modèle que tu tiens, avec lockForUpdate() appliqué, à l'intérieur de la transaction, là où un verrou veut vraiment dire quelque chose. Ce genre de course ne se reproduit à peu près jamais sur un laptop avec un seul utilisateur, et c'est pour ça qu'une primitive d'une ligne compte plus qu'elle n'en a l'air. Dans le même registre, whereBinary() offre aux utilisateurs de MySQL et MariaDB des comparaisons sensibles à la casse sans retomber dans le SQL brut.
Le déploiement a aussi progressé. queue:pause --all et queue:resume --all arrêtent toutes les files en un seul appel artisan, au lieu de te faire courir après des noms de queues qui ont dérivé depuis la dernière fois que quelqu'un les a notés quelque part. schedule:work gère maintenant proprement SIGINT, SIGTERM et SIGQUIT, ce qui compte dès que Kubernetes replanifie ton pod pendant qu'une tâche est à mi-course. L'événement WorkerStopping transporte désormais jobsProcessed et lastJobProcessedAt, donc le débit par worker devient visible sans que tu bricoles des compteurs dans la boucle de jobs. Deux briques d'infrastructure suivent le même schéma défensif : la 13.17 apprend à la config pgsql la différence entre un endpoint poolé (PgBouncer, RDS Proxy, Neon) et un endpoint direct, et envoie d'elle-même les migrations et schema:dump sur la connexion directe. Et le driver de filesystem en read-through de la 13.26 sert depuis un disque principal, retombe sur l'ancien disque quand le fichier manque et copie les fichiers au fil des demandes, ce qui transforme une migration de fournisseur de stockage d'un marathon rsync de week-end en un processus d'arrière-plan que tu finis par oublier.
Maintenant, la concession honnête : le côté brillant de ces versions est bon. Illuminate\Image, introduit en 13.20, enrobe Intervention Image v4 (tu fais toujours composer require intervention/image toi-même, avec GD ou Imagick en dessous) et il a mûri vite. Les uploads HEIC et HEIF venant des iPhone sont acceptés depuis la 13.24 au lieu d'être rejetés, dominantColor() te donne une couleur de fond de placeholder pour les photos en lazy loading, et comme Image implémente Responsable, tu peux renvoyer une image transformée directement depuis une route. Les instances sont immuables, donc une même photo source peut produire un petit avatar ici et un visuel flouté là sans que les transformations se contaminent entre elles. C'est la fonctionnalité que les équipes vont adopter dès cette semaine, et l'adoption maintient un framework en vie. Je ne vais pas prétendre le contraire.
Je vais aussi concéder le contre-argument à mon propre choix. La personne d'astreinte à 3 heures du matin veut les valeurs liées imprimées dans l'exception, parce que la valeur fautive est souvent le chemin le plus court vers le bug, et le masquage met une étape entre elle et la réponse. C'est vrai, et c'est probablement pour ça que le drapeau est désactivé par défaut. J'atterris quand même sur l'activation : dans le code que tu contrôles, getBindings() te donne les valeurs de toute façon, alors que le texte du message voyage vers des prestataires tiers, des politiques de rétention et des archives de sauvegarde que tu ne contrôles pas du tout. La friction de débogage se rattrape. Le numéro de téléphone d'un client dans le stockage de logs de quelqu'un d'autre, non.
Il reste plein de choses dans cette période dont je n'ai pas débattu : Validator::fakeDnsLookups() pour que les tests avec email:dns survivent aux réseaux capricieux, l'attribut #[RouteKey('slug')], une règle de validation base64, #[DebounceFor] sur les listeners d'événements en file, le support de HTTP QUERY via Http::query(), et même un driver de log mensuel. Parcours le changelog toi-même. Ma question est plus étroite : quand une requête explose en production, veux-tu les valeurs liées dans le message, ou derrière un appel délibéré à getBindings() ? J'ai fait mon choix, et je soupçonne que le monde de l'astreinte a des histoires qui le mettraient à l'épreuve. Raconte-moi où ce compromis t'a déjà mordu.




Commentaires
Pas encore de commentaire — écris le premier.
Lance la discussion
Pas de compte ni de mot de passe — saisis simplement ton adresse e-mail et nous t’envoyons un lien de connexion à usage unique. Première visite ? Tout se met en place automatiquement.
Ton évaluation sera appliquée automatiquement après ta connexion.
Vérifie ta boîte mail
Nous avons envoyé un lien de connexion à …. Ouvre-le sur cet appareil — cet onglet te connectera automatiquement.
Rien reçu ? Vérifiez le dossier spam — et marquez le message « Non spam » pour qu'il arrive directement la prochaine fois.