PHP 8.5 introduit enfin l'opérateur |> (Pipe), attendu depuis des années par la communauté. Après deux tentatives de RFC infructueuses, la troisième version a été acceptée et intégrée. Cet opérateur révolutionne la façon de chaîner les fonctions en PHP.
Le problème : les appels de fonctions imbriqués
Avant PHP 8.5, pour enchaîner plusieurs transformations sur une valeur, nous avions deux options peu élégantes :
Option 1 : L'imbrication (illisible)
// Lecture de droite à gauche... 😵
$result = array_sum(
array_filter(
array_map(
fn($n) => $n * 2,
$numbers
),
fn($n) => $n > 10
)
);
// Ou en une ligne illisible
$result = trim(strtoupper(str_replace(' ', '-', $string)));Option 2 : Les variables intermédiaires (verbeux)
// Variables temporaires partout... 😮💨
$doubled = array_map(fn($n) => $n * 2, $numbers);
$filtered = array_filter($doubled, fn($n) => $n > 10);
$result = array_sum($filtered);Ces deux approches posent problème :
- Imbrication : difficile à lire, ordre inversé de l'exécution
- Variables temporaires : code verbeux, pollution du scope
La solution : l'opérateur Pipe |>
L'opérateur Pipe permet de chaîner les fonctions de gauche à droite, dans l'ordre naturel de lecture :
// PHP 8.5 - Élégant et lisible ! 🎉
$result = $numbers
|> array_map(fn($n) => $n * 2, ...)
|> array_filter(fn($n) => $n > 10, ...)
|> array_sum(...);
// Chaînage de strings
$slug = $title
|> strtolower(...)
|> trim(...)
|> preg_replace('/[^a-z0-9]+/', '-', ...);Le résultat de chaque expression est passé comme premier argument de la fonction suivante.
Syntaxe et fonctionnement
Syntaxe avec first-class callables
L'opérateur Pipe utilise la syntaxe des first-class callables introduite en PHP 8.1 avec les (...) :
// Fonctions natives
$result = "Hello World"
|> strtoupper(...) // "HELLO WORLD"
|> str_split(...) // ['H','E','L','L','O',' ','W','O','R','L','D']
|> array_reverse(...) // ['D','L','R','O','W',' ','O','L','L','E','H']
|> implode('', ...); // "DLROW OLLEH"
echo $result; // DLROW OLLEHAvec des fonctions utilisateur
function double(int $n): int {
return $n * 2;
}
function addTax(float $amount): float {
return $amount * 1.20;
}
function formatPrice(float $amount): string {
return number_format($amount, 2, ',', ' ') . ' €';
}
$price = 100
|> double(...) // 200
|> addTax(...) // 240.0
|> formatPrice(...); // "240,00 €"Avec des méthodes statiques
class StringHelper {
public static function slugify(string $text): string {
return strtolower(preg_replace('/[^a-z0-9]+/i', '-', $text));
}
public static function truncate(string $text, int $length = 50): string {
return strlen($text) > $length
? substr($text, 0, $length) . '...'
: $text;
}
}
$slug = "Mon Super Article de Blog !"
|> StringHelper::slugify(...) // "mon-super-article-de-blog-"
|> trim('-', ...); // "mon-super-article-de-blog"Avec des arrow functions
// ⚠️ Les arrow functions nécessitent des parenthèses
$result = 5
|> (fn($n) => $n * 2) // 10
|> (fn($n) => $n + 3) // 13
|> (fn($n) => "Total: $n"); // "Total: 13"⚠️ Parenthèses obligatoires pour les arrow functions
Sans parenthèses, l'arrow function "capture" tout jusqu'à la fin de l'expression, causant des erreurs de parsing.
Cas d'utilisation concrets
Traitement de données
// Traitement d'une liste d'utilisateurs
$activeEmails = $users
|> array_filter(fn($u) => $u->isActive(), ...)
|> array_map(fn($u) => $u->email, ...)
|> array_unique(...)
|> array_values(...);
// Pipeline de validation
$sanitizedInput = $request->input('name')
|> trim(...)
|> strip_tags(...)
|> htmlspecialchars(...);Manipulation de fichiers
// Lecture et parsing d'un fichier JSON
$config = '/path/to/config.json'
|> file_get_contents(...)
|> json_decode(..., true)
|> array_merge(['defaults' => true], ...);Génération de contenu
// Génération d'un slug SEO-friendly
function generateSlug(string $title): string {
return $title
|> mb_strtolower(...)
|> (fn($s) => preg_replace('/[àáâãäå]/u', 'a', $s))
|> (fn($s) => preg_replace('/[èéêë]/u', 'e', $s))
|> (fn($s) => preg_replace('/[ìíîï]/u', 'i', $s))
|> (fn($s) => preg_replace('/[^a-z0-9]+/', '-', $s))
|> trim('-', ...);
}
echo generateSlug("L'été à Paris !"); // "l-ete-a-paris"Calculs en chaîne
// Calcul de prix avec remises
function calculateFinalPrice(
float $basePrice,
float $discount,
bool $isPremium
): string {
return $basePrice
|> (fn($p) => $p * (1 - $discount / 100))
|> (fn($p) => $isPremium ? $p * 0.9 : $p)
|> (fn($p) => $p * 1.20) // TVA
|> round(2, ...)
|> number_format(2, ',', ' ', ...)
|> (fn($p) => "$p €");
}
echo calculateFinalPrice(100, 10, true); // "97,20 €"Limitations importantes
1. Un seul paramètre requis
Chaque callable doit accepter exactement un paramètre requis (le résultat précédent) :
// ✅ Fonctionne - un paramètre requis
function double(int $n): int {
return $n * 2;
}
// ❌ Ne fonctionne PAS - deux paramètres requis
function multiply(int $a, int $b): int {
return $a * $b;
}
// ✅ Solution - utiliser une arrow function
$result = 5
|> (fn($n) => multiply($n, 3)); // 152. Position du paramètre fixe
Le résultat est toujours passé en premier argument. Si vous avez besoin d'une autre position :
// str_replace($search, $replace, $subject)
// Le sujet est en 3ème position...
// ❌ Ne fonctionne pas directement
$result = $text |> str_replace('foo', 'bar', ...);
// ✅ Solution avec arrow function
$result = $text |> (fn($s) => str_replace('foo', 'bar', $s));3. Pas de passage par référence
// ❌ Ne fonctionne pas - sort() modifie par référence
$sorted = $array |> sort(...);
// ✅ Alternative
$sorted = $array;
sort($sorted);4. Fonctions void
// ⚠️ Les fonctions void retournent null
function logValue(mixed $value): void {
error_log(print_r($value, true));
}
// $result vaut null après logValue
$result = "test"
|> strtoupper(...)
|> logValue(...) // Retourne null !
|> strlen(...); // strlen(null) = 0Priorité des opérateurs
L'opérateur Pipe a une priorité basse, ce qui permet des comportements intuitifs :
// Les comparaisons fonctionnent comme attendu
$isLong = $text
|> strlen(...)
> 100; // true si longueur > 100
// Équivalent à :
$isLong = (strlen($text)) > 100;Comparaison avant/après
| Avant PHP 8.5 | Avec Pipe (PHP 8.5) |
|---|---|
trim(strtoupper($s)) | $s |> strtoupper(...) |> trim(...) |
| Lecture droite → gauche | Lecture gauche → droite ✓ |
| Variables temporaires | Chaînage direct ✓ |
| Imbrication profonde | Pipeline linéaire ✓ |
L'avenir : Partial Function Application
Un RFC complémentaire est en cours de développement : Partial Function Application. Il permettrait de pré-remplir certains arguments :
// Futur potentiel (RFC en cours)
$result = $text
|> str_replace('foo', 'bar', ?) // ? = placeholder
|> substr(?, 0, 100);
// Au lieu de :
$result = $text
|> (fn($s) => str_replace('foo', 'bar', $s))
|> (fn($s) => substr($s, 0, 100));Conclusion
L'opérateur Pipe |> est une addition majeure à PHP 8.5 qui :
- Améliore la lisibilité : le code se lit de gauche à droite
- Réduit la verbosité : plus besoin de variables temporaires
- Encourage le style fonctionnel : composition de fonctions élégante
- Modernise PHP : alignement avec d'autres langages (Elixir, F#, Hack)
Bien qu'il ait des limitations (un seul paramètre, position fixe), l'opérateur Pipe transforme la façon d'écrire du code PHP et ouvre la porte à un style de programmation plus fonctionnel.
Si vous êtes sur PHP 8.5, commencez dès maintenant à l'utiliser dans vos pipelines de traitement de données !