Image de couverture : Arrow functions PHP : quand la concision cache un bug silencieux dans Laravel Collections
tech

Arrow functions PHP : quand la concision cache un bug silencieux dans Laravel Collections

01 June 2026
5 min de lecture
28 vues
Sébastien Muler

Arrow functions PHP : quand la concision cache un bug silencieux dans Laravel Collections

Un bug sans message d'erreur, sans exception, sans stacktrace. Juste un comportement inattendu qui s'exécute en silence et tronque vos données. C'est précisément ce qu'a partagé Freek Van der Herten sur X à propos d'un cas réel rencontré dans Laravel Collections. Ce bug, analysé et relayé par Mahmoud Ramadan sur dev.to, illustre parfaitement pourquoi la qualité de code ne se limite pas à l'esthétique ou au style — elle conditionne la fiabilité de vos traitements métier.


Le bug : deux syntaxes, un comportement radicalement différent

Voici le code incriminé :

collect([])->each(
    fn(string $hash) => $this->taggedCache($this->tags)->forget($hash)
);

Et voici le correctif appliqué :

collect([])->each(function(string $hash) {
    $this->taggedCache($this->tags)->forget($hash);
});

Au premier coup d'œil, la différence semble purement stylistique : une arrow function remplacée par une fonction anonyme classique. Pourtant, ce changement corrige un comportement silencieusement destructeur.


Comprendre la mécanique du problème

Le comportement de each() dans Laravel Collections

La méthode each() de Laravel itère sur chaque élément d'une collection et exécute un callback. Ce que la documentation précise — et qu'il est facile d'oublier — c'est que si le callback retourne false, l'itération s'arrête immédiatement, comme un break dans une boucle.

Ce mécanisme est intentionnel et utile dans certains cas. Mais il devient un piège lorsque le callback retourne false sans que le développeur l'ait voulu.

Le rôle du retour implicite des arrow functions

En PHP, une arrow function (fn() =>) retourne toujours implicitement le résultat de l'expression. Il n'y a pas de corps de fonction avec un return explicite : l'expression est évaluée et sa valeur est automatiquement retournée.

Dans notre cas :

fn(string $hash) => $this->taggedCache($this->tags)->forget($hash)

La valeur retournée par le callback est le résultat de forget(). Si cette méthode retourne false (ce qu'elle peut faire selon son implémentation), alors each() interprète ce false comme un signal d'arrêt et stoppe l'itération du reste de la collection.

Pourquoi la fonction anonyme corrige le problème

Avec une fonction anonyme classique :

function(string $hash) {
    $this->taggedCache($this->tags)->forget($hash);
}

Aucune valeur n'est retournée explicitement. En PHP, une fonction sans return retourne null. Or null !== false, donc each() ne détecte pas de signal d'arrêt et continue d'itérer sur l'ensemble de la collection.

Le tableau suivant résume la différence :

Syntaxe Valeur retournée Comportement each()
Arrow function Résultat de forget() (potentiellement false) Arrêt prématuré possible
Fonction anonyme null Itération complète garantie

Pourquoi ce type de bug est particulièrement dangereux

Ce bug appartient à une catégorie redoutable : les erreurs silencieuses. Aucune exception n'est levée. Aucun log d'erreur n'est généré. L'application continue de fonctionner normalement en apparence. Seul le résultat final est incorrect — et encore, uniquement si forget() retourne false sur au moins un élément.

Dans un contexte de cache taguée (comme c'est le cas ici), cela signifie que certaines entrées de cache ne sont jamais supprimées. Les conséquences possibles :

  • Données obsolètes servies aux utilisateurs
  • Comportements imprévisibles selon l'état du cache
  • Debugging laborieux car le bug n'est pas reproductible de façon déterministe

C'est exactement le type de bug qui passe à travers les tests unitaires si ceux-ci ne couvrent pas le cas où forget() retourne false.


Bonnes pratiques pour éviter ce piège

1. Réserver les arrow functions aux transformations pures

Les arrow functions sont idéales pour les opérations de transformation qui retournent une valeur exploitée : map(), filter(), reduce(). En revanche, pour les effets de bord (suppression, écriture, envoi), préférez les fonctions anonymes classiques.

// ✅ Arrow function adaptée : la valeur retournée est utilisée
$doubled = collect([1, 2, 3])->map(fn(int $n) => $n * 2);

// ⚠️ Arrow function risquée : la valeur retournée est ignorée... ou pas
collect($hashes)->each(fn(string $hash) => $cache->forget($hash));

// ✅ Fonction anonyme recommandée pour les effets de bord
collect($hashes)->each(function(string $hash) use ($cache) {
    $cache->forget($hash);
});

2. Expliciter le retour quand vous utilisez each()

Si vous tenez à utiliser une arrow function avec each(), retournez explicitement null ou true pour éviter toute ambiguïté :

collect($hashes)->each(function(string $hash) {
    $this->taggedCache($this->tags)->forget($hash);
    return true; // Iteration continue explicitement
});

3. Documenter le contrat des méthodes de callback

Lorsque vous concevez des méthodes acceptant des callbacks, documentez clairement l'effet d'un retour false. Une ligne de PHPDoc peut éviter des heures de debugging à vos collègues.


Conclusion

Ce bug issu de la communauté Laravel rappelle une vérité fondamentale du développement : la lisibilité d'un code n'implique pas sa correction. Les arrow functions PHP sont une fonctionnalité puissante et élégante, mais leur comportement de retour implicite peut interagir de façon inattendue avec des APIs qui interprètent la valeur de retour du callback.

Chez MulerTech, ce type de subtilité fait partie des points de vigilance que nous intégrons dans nos revues de code et nos conventions d'équipe. La concision syntaxique est une qualité, mais jamais au détriment de la prévisibilité du comportement.

Source originale : Laravel Collections Bug Caused by Arrow Functions par Mahmoud Ramadan sur dev.to, d'après un cas partagé par Freek Van der Herten.

Partager cet article