Die rechte Seite des neuen |>-Operators in PHP 8.5 muss ein Callable sein, kein Funktionsaufruf. Alles Interessante an dem Feature versteckt sich in dieser Anforderung. Der Operator selbst passt auf einen Klebezettel: Er wertet den Ausdruck auf seiner linken Seite aus und übergibt das Ergebnis als erstes Argument an das, was rechts steht. Du schreibst also $input |> trim(...) und stützt dich dabei auf die First-Class-Callable-Syntax, die wir seit 8.1 haben. Aber sobald ein Schritt ein zweites Argument braucht, sagen wir ein str_replace mit Suchmuster und Ersatz, musst du ihn einpacken: (fn($s) => str_replace(' ', '-', $s)), und die äußeren Klammern sind keine Deko. Lässt du sie weg, schnappt sich die Arrow Function gierig den Rest der Kette, und der Parser gibt dich auf.
<?php
// PHP 8.5: each step takes a value, returns a value
function sanitizeComment(string $input): string
{
return $input
|> trim(...)
|> strip_tags(...)
|> (fn($s) => htmlspecialchars($s, ENT_QUOTES, 'UTF-8'));
}Dieser Wrapper ist die Beschwerde, die ich über die aktuelle Implementierung am häufigsten höre, und ja, für PHP 8.6 ist Partial Function Application geplant, mit der du Argumente vorbefüllen und die meisten dieser Closures loswerden kannst. Ich hoffe, das lässt sich Zeit. Die Geschwätzigkeit leistet nämlich stille pädagogische Arbeit. Jedes Mal, wenn du fn($s) => tippst, stellt dir die Sprache eine kleine, spitze Frage: Ist dieser Schritt wirklich ein anonymer Zwischenstecker, oder ist er ein Ding mit einem Namen, das deine Codebasis nächsten Dienstag wieder brauchen wird?
Denn sobald die Schritte Namen bekommen, verdient der Operator plötzlich sein Geld. Statische Methoden lassen sich mit der Callable-Syntax direkt durchreichen, $raw |> Slug::normalize(...) |> Slug::truncate(...) liest sich von oben nach unten ohne jedes Zeremoniell. Instanzmethoden haben diesen Luxus nicht, $formatter->format() musst du weiterhin in eine Closure packen, das ist eine echte Warze, aber es bedeutet auch, dass gepipter Code in Richtung kleiner zustandsloser Funktionen mit einem offensichtlichen Input driftet. Dieser Drift ist zur Laufzeit gratis: Pipe-Ketten kompilieren zu Opcodes, die ungefähr den verschachtelten Aufrufen entsprechen, die du sowieso geschrieben hättest. Es gibt also keine Performance-Rechnung dafür, deine Pipeline in ein Dutzend kleiner benannter Teile zu zerlegen.
Jetzt der ehrliche Teil. Das stärkste Argument dagegen, sich überhaupt mit |> zu befassen, ist, dass PHP schon immer eine völlig brauchbare Antwort hatte: Zwischenvariablen. $trimmed, $slugified, $lowercased. Diese Namen dokumentieren Absicht, sie tauchen in deinem Debugger auf, du kannst jede davon ohne Umstände per var_dump anschauen. Eine Pipe-Kette gibt dir nichts davon, und sie bringt eine Falle mit, die der alte Stil nie hatte: Ein void-Callable evaluiert zu null. Wenn du also mitten in eine Kette ein var_dump wirfst, um kurz nachzusehen, bekommt der nächste Schritt null statt deines Wertes, und die Pipeline vergiftet leise alles, was danach kommt. Keine Exception, keine Warnung, nur falsche Ausgabe drei Schritte später. Dazu die Deployment-Realität: Das ist neue Syntax, die auf 8.4 oder allem Älteren nicht parst, deine ganze Flotte und deine CI-Runner müssen also erst auf 8.5 sein. Schon sieht Skepsis ziemlich vernünftig aus.
Ich lande trotzdem beim Benutzen, und die var_dump-Falle ist ein Teil des Grundes. Eine Pipe-Kette gibt ein Versprechen ab, das die Variablen-Version nie so recht ausspricht: Jeder Schritt nimmt einen Wert und gibt einen Wert zurück, sonst passiert hier nichts. Seiteneffekte passen nicht in die Form, und im Code-Review sieht man diesen Bruch quer durch den Raum. Wenn ein Kollege versucht, mitten in eine Transformation einen Logger-Aufruf zu schmuggeln, protestiert die Kette von selbst. Bei vier Temp-Variablen rutscht derselbe Logger-Aufruf zwischen zwei Zuweisungen durch, und niemand zuckt. Der Operator lässt dich nichts Neues tun. Er ändert, was falsch aussieht, und nach genug Jahren, in denen ich den Sanitization-Code anderer Leute gepflegt habe, ist mir das inzwischen mehr wert als Debugger-Komfort.
Meine Arbeitsregel, seit 8.5 draußen ist: Pipes für alles, was ehrlich eine Transformation ist, Slug-Builder, Input-Sanitizer, der Filter-Map-Unique-Values-Tanz auf Arrays. Schlichte alte Variablen für alles mit Verzweigungen, frühen Returns oder viel Objektzustand, wo eine Kette nur ein Kostüm wäre. Das Tooling stellt sich dir dabei nicht in den Weg, PhpStorm und Intelephense in VS Code verstehen die Syntax Stand Anfang 2026 beide. Und wenn eine Kette mehr als zwei Inline-Closures ansammelt, halte ich vor dem Mergen an und extrahiere benannte Funktionen, denn diese Kette sagt mir, dass die Domäne Vokabular hat, das ich noch nicht aufgeschrieben habe.
Die Elixir-Leute haben |>, seit ein Teil unseres Legacy-Codes noch gar nicht Legacy war, und F# hatte ihn davor, PHP kommt also spät zu dieser Party. Gut. Dafür sind wir mit Optimierung auf Opcache-Niveau und First-Class Callables im Werkzeugkasten angekommen, das ist keine schlechte Art, spät aufzutauchen. Meine offene Frage betrifft 8.6: Wenn Partial Application landet und die (fn($s) => ...)-Wrapper verdunsten, wirst du weiter benannte Schritte extrahieren, oder war die Klammer-Steuer das Einzige, was deine Pipelines ehrlich gehalten hat? Anders gefragt: Wie viele Inline-Closures erlaubst du in einer Kette, bevor du den Merge Request blockierst? Ich sage zwei. Erklär mir, warum ich falschliege.




Kommentare
Noch keine Kommentare — schreib den ersten.
Starte die Diskussion
Kein Konto, kein Passwort nötig — gib einfach deine E-Mail-Adresse ein, wir senden dir einen einmaligen Anmelde-Link. Beim ersten Mal bist du damit automatisch angemeldet.
Deine Bewertung wird nach der Anmeldung automatisch übernommen.
Schau in dein Postfach
Wir haben einen Anmelde-Link an … gesendet. Öffne ihn auf diesem Gerät — dieser Tab meldet dich automatisch an.
Nichts angekommen? Wirf einen Blick in den Spam-Ordner — und markiere die Mail dort als „Kein Spam“, dann landet sie künftig direkt im Postfach.