PHP Operador Pipe (|>) PHP 8.5+
Transformar um valor em vários passos é o pão de cada dia: tirar espaços, passar para minúsculas, trocar espaços por hífens. Até o PHP 8.4, isso virava funções dentro de funções (lidas de dentro para fora) ou uma pilha de variáveis temporárias. O PHP 8.5 trouxe o operador pipe |> ("cano"): o valor entra pela esquerda e passa por cada função, na ordem em que você lê.
- O que o
|>faz:$x |> f(...)é o mesmo quef($x). - A encadear vários passos:
" Olá Mundo " |> trim(...) |> mb_strtoupper(...). - A usar closures e arrow functions no meio do cano (com parênteses).
- As limitações: só funções que recebem um argumento, nada de referência.
- A precedência: quando o
|>precisa de parênteses em volta. - O mesmo código em três versões: aninhado, com variáveis temporárias e com pipe.
O pipe usa a sintaxe trim(...), explicada em Callables de Primeira Classe. Vale ler aquela aula antes.
Em versões anteriores, o PHP não conhece o |> e o arquivo nem começa a rodar: dá Parse error (erro de sintaxe). Se o seu servidor ainda roda 8.4 ou menos, use as formas "antes" desta aula. Confira com php -v (veja Versões do PHP).
A ideia: a linha de montagem
Numa fábrica de bolos, a massa passa por uma esteira: primeiro a batedeira, depois o forno, depois a cobertura. Cada máquina recebe o que a anterior entregou. Ninguém descreve a fábrica como "cobertura de forno de batedeira de massa", mas é assim que o código aninhado se lê:
mb_strtoupper(trim($texto)) // primeiro trim, depois mb_strtoupper, mas lê-se ao contrário
O pipe é a esteira, escrita na ordem dos acontecimentos:
$texto |> trim(...) |> mb_strtoupper(...) // primeiro trim, depois mb_strtoupper
O primeiro pipe
<?php $texto = " Olá Mundo "; $resultado = $texto |> trim(...) |> mb_strtoupper(...); var_dump($resultado); var_dump($texto |> trim(...) |> strtoupper(...));
- $texto
- O valor que entra no cano.
- |>
- O operador pipe: "entregue o valor da esquerda para o callable da direita". Uma barra vertical e um sinal de maior, colados.
- trim(...)
- Um callable: a função
trim, ainda não chamada. O pipe a chama com o valor:trim(" Olá Mundo ")→"Olá Mundo". - |> mb_strtoupper(...)
- O resultado do passo anterior entra no próximo:
mb_strtoupper("Olá Mundo")→"OLÁ MUNDO".
$ php ola.php string(10) "OLÁ MUNDO" string(10) "OLá MUNDO" « strtoupper não converteu o "á"
O pipe funcionou nos dois casos; a diferença está na função. strtoupper só conhece as letras sem acento (ela trabalha byte a byte), e o á ficou minúsculo. mb_strtoupper entende UTF-8. Esse é o assunto de Acentos e mbstring. (Os dois textos têm 10 bytes, porque á e Á ocupam 2 bytes cada.)
A regra do operador cabe numa linha:
| Escrita com pipe | É o mesmo que |
|---|---|
$x |> f(...) | f($x) |
$x |> f(...) |> g(...) | g(f($x)) |
$x |> f(...) |> g(...) |> h(...) | h(g(f($x))) |
Missão: rode e repare no á minúsculo em OLá MUNDO. Troque o último passo do cano por mb_strtoupper(...) para a saída ficar OLÁ MUNDO. Depois, por conta própria, acrescente mais um passo no fim: |> strrev(...) (inverte o texto) e veja o que acontece com o acento.
<?php $texto = " Olá Mundo "; echo $texto |> trim(...) |> strtoupper(...), "\n";
<?php $texto = " Olá Mundo "; echo $texto |> trim(...) |> mb_strtoupper(...), "\n";
Antes × depois: o mesmo código de três jeitos
A Ana quer transformar o título de um produto num slug, o pedaço de endereço usado nos links da loja (/produtos/caneca-de-cafe-grande). Três passos: tirar espaços das pontas, passar para minúsculas e trocar espaços por hífens.
<?php
$titulo = " Caneca de Café GRANDE ";
// 1. aninhado: lê-se de dentro para fora
$slug = str_replace(" ", "-", mb_strtolower(trim($titulo)));
echo $slug, "\n";
// 2. variáveis temporárias: uma linha por passo
$passo1 = trim($titulo);
$passo2 = mb_strtolower($passo1);
$slug = str_replace(" ", "-", $passo2);
echo $slug, "\n";
// 3. pipe: lê-se da esquerda para a direita
$slug = $titulo
|> trim(...)
|> mb_strtolower(...)
|> (fn($s) => str_replace(" ", "-", $s));
echo $slug, "\n";
$ php antes.php caneca-de-café-grande caneca-de-café-grande caneca-de-café-grande
O resultado é idêntico; muda a leitura:
| Jeito | Vantagem | Desvantagem |
|---|---|---|
| 1. Aninhado | Curto | Lê-se de dentro para fora; os argumentos extras (" ", "-") ficam longe da função a que pertencem; com 4 ou 5 passos, vira um amontoado de parênteses |
| 2. Variáveis temporárias | Funciona em qualquer versão do PHP; cada passo pode ser inspecionado com var_dump | Nomes inventados ($passo1, $passo2) que só existem para passar o valor adiante |
| 3. Pipe 8.5+ | Lê-se na ordem dos acontecimentos, um passo por linha, sem nomes inventados | Só PHP 8.5; funções com mais de um argumento precisam de uma arrow function |
No jeito 3, repare na formatação: um passo por linha, cada linha começando com |>. É o estilo recomendado quando há mais de dois passos.
Closures e arrow functions no cano
O lado direito do |> pode ser qualquer callable que receba um argumento: uma função com (...), um método, uma variável com uma closure, até a forma antiga de texto.
<?php
class Loja
{
public function comTaxa(float $p): float { return $p * 1.1; }
public static function formatar(float $v): string { return "R$ " . number_format($v, 2, ",", "."); }
}
$loja = new Loja();
echo 100 |> $loja->comTaxa(...) |> Loja::formatar(...), "\n";
echo "caneca" |> 'strtoupper', "\n";
echo "caneca" |> function (string $s): string { return ucfirst($s); }, "\n";
$dobro = fn($n) => $n * 2;
echo 21 |> $dobro, "\n";
$ php outros.php R$ 110,00 CANECA Caneca 42
| Lado direito | Exemplo |
|---|---|
| Função | |> trim(...) |
| Método de objeto | |> $loja->comTaxa(...) |
| Método estático | |> Loja::formatar(...) |
| Variável com closure | |> $dobro (sem (...): ela já é um callable) |
| Função anônima | |> function ($s) { return …; } |
| Arrow function | |> (fn($s) => …), com parênteses |
| Texto (forma antiga) | |> 'strtoupper' (funciona, mas prefira strtoupper(...)) |
A arrow function precisa de parênteses
Uma arrow function "engole" tudo o que vem depois do =>, inclusive os próximos |>. Para evitar a confusão, o PHP exige os parênteses:
<?php
$slug = "Caneca Grande"
|> mb_strtolower(...)
|> fn($s) => str_replace(" ", "-", $s)
|> strrev(...);
var_dump($slug);
$ php sem-parenteses.php
Fatal error: Arrow functions on the right hand side of |> must be parenthesized in /home/ana/pipe/sem-parenteses.php on line 2
Stack trace:
#0 {main}
"Arrow functions do lado direito de |> precisam estar entre parênteses." O conserto é |> (fn($s) => str_replace(" ", "-", $s)).
Exemplo com arrays
O pipe brilha quando os passos são funções de array. A Ana quer o total dos pedidos já pagos:
<?php
$pedidos = [
['cliente' => 'Bruno', 'total' => 69.8, 'pago' => true],
['cliente' => 'Carla', 'total' => 120.0, 'pago' => false],
['cliente' => 'Davi', 'total' => 35.5, 'pago' => true],
];
$totalPago = $pedidos
|> (fn($lista) => array_filter($lista, fn($p) => $p['pago']))
|> (fn($lista) => array_column($lista, 'total'))
|> array_sum(...)
|> (fn($v) => number_format($v, 2, ",", "."));
echo "Total pago: R$ $totalPago\n";
$ php arrays.php Total pago: R$ 105,30
Lendo de cima para baixo: os pedidos → só os pagos (array_filter) → só a coluna total (array_column) → a soma (array_sum) → formatada. Sem o pipe, seriam quatro funções aninhadas ou quatro variáveis temporárias.
Limitações
1. O valor entra como o único argumento
O pipe chama o callable com um argumento: o valor da esquerda. Funções que precisam de mais argumentos obrigatórios falham:
$ php dois.php « $partes = "a,b,c" |> explode(...);
Fatal error: Uncaught ArgumentCountError: explode() expects at least 2 arguments, 1 given in /home/ana/pipe/dois.php:2
Stack trace:
#0 /home/ana/pipe/dois.php(2): explode('a,b,c')
#1 {main}
thrown in /home/ana/pipe/dois.php on line 2
O explode precisa do separador e do texto, e recebeu só o texto. E não dá para "pré-preencher" os outros argumentos com ... no meio:
$ php parcial.php « "Caneca Grande" |> str_replace(" ", "-", ...);
Parse error: syntax error, unexpected token ")" in /home/ana/pipe/parcial.php on line 2
Os três pontos só funcionam sozinhos dentro dos parênteses. (Existe uma proposta para permitir isso, chamada "aplicação parcial", mas ela não faz parte do PHP 8.5.) A solução é a arrow function, que diz onde o valor entra:
"a,b,c" |> (fn($s) => explode(",", $s))
"Caneca Grande" |> (fn($s) => str_replace(" ", "-", $s))
Funções cujos argumentos extras são opcionais funcionam direto: |> round(...) arredonda para inteiro, |> trim(...) tira espaços.
2. Nada de passagem por referência
Funções que mudam a variável que recebem (por referência), como sort, não combinam com o pipe, porque o valor no meio do cano não é uma variável:
$ php ref.php « $ordenado = [3, 1, 2] |> sort(...);
Fatal error: Uncaught Error: sort(): Argument #1 ($array) could not be passed by reference in /home/ana/pipe/ref.php:2
Stack trace:
#0 {main}
thrown in /home/ana/pipe/ref.php on line 2
Mesmo que funcionasse, o sort devolve true, não o array ordenado. Para ordenar no meio do cano, use uma arrow function com uma cópia: |> (function ($l) { sort($l); return $l; }), ou faça a ordenação fora do pipe.
3. A precedência: quando usar parênteses
Contas e concatenação acontecem antes do pipe. Veja o que acontece ao tentar somar 1 no fim do cano:
$ php precedencia.php « $r = " 3 " |> trim(...) |> intval(...) + 1;
Fatal error: Uncaught TypeError: Unsupported operand types: Closure + int in /home/ana/pipe/precedencia.php:2
Stack trace:
#0 {main}
thrown in /home/ana/pipe/precedencia.php on line 2
O PHP leu intval(...) + 1 primeiro: "somar 1 a um Closure", o que não existe. O mesmo acontece com o ponto (concatenação): "abc" |> strtoupper(...) . "!" dá Object of class Closure could not be converted to string. Coloque o cano entre parênteses:
$ php p3.php « var_dump((" 3 " |> trim(...) |> intval(...)) + 1);
int(4)
Comparações (==, ===, <) e o ?? ficam depois do pipe, então "abc" |> strlen(...) == 3 funciona e dá true. Na dúvida, parênteses. Veja Precedência.
Quando usar o pipe
| Situação | Melhor escolha |
|---|---|
Um passo só: trim($nome) | A chamada normal. $nome |> trim(...) não ganha nada |
| Dois ou mais passos de transformação, cada um com um argumento | Pipe |
| Passos que precisam de vários argumentos | Pipe com arrow functions, ou variáveis temporárias se ficar pesado |
| Precisa inspecionar o valor no meio (depurar) | Variáveis temporárias; ou um passo |> (function ($x) { var_dump($x); return $x; }) |
Funções que mudam por referência (sort, array_push) | Fora do pipe |
| Código que precisa rodar em PHP 8.4 ou anterior | Aninhado ou variáveis temporárias |
Quando dá errado
O que significa: você escreveu |> fn($x) => … sem parênteses.
Como consertar: |> (fn($x) => …).
O que significa: a função do passo precisa de mais de um argumento; o pipe só passa um.
Como consertar: embrulhe numa arrow function que complete os outros: |> (fn($s) => explode(",", $s)).
O que significa: uma conta ou concatenação no fim do cano foi feita antes do pipe, com o callable em vez do resultado.
Como consertar: parênteses em volta do cano inteiro: ($x |> f(...)) + 1.
O que significa: funções como sort, shuffle, array_push precisam de uma variável, e o pipe passa um valor.
Como consertar: faça esse passo fora do pipe, ou com uma função anônima que trabalha numa cópia e a devolve.
|>, num servidor mais antigo
O que significa: o PHP é anterior ao 8.5 e não conhece o |>. (A mensagem exata depende da versão; o laboratório deste guia só tem o 8.5.)
Como consertar: atualize o PHP ou reescreva com variáveis temporárias.
Referência
| Escrita | Significado |
|---|---|
$x |> f(...) | f($x) |
$x |> f(...) |> g(...) | g(f($x)) |
$x |> $obj->m(...) / |> Classe::m(...) | $obj->m($x) / Classe::m($x) |
$x |> (fn($v) => f($v, 2)) | f($x, 2) |
$x |> $closure | $closure($x) |
($x |> f(...)) + 1 | Parênteses antes de contas e de . |
Exercícios
1. Traduzir para pipe
Complete a versão com pipe de strrev(trim($s)):
2. Traduzir de volta
Escreva sem pipe, aninhado, o mesmo que $nome |> trim(...) |> ucfirst(...):
3. Dois argumentos
Complete o passo que troca vírgulas por pontos com str_replace:
4. O que " ana " |> trim(...) |> strtoupper(...) devolve?
5. Por que "a,b" |> explode(...) dá erro?
6. Qual linha está correta?
Missão final: reescreva a linha aninhada usando o pipe, um passo por linha: comece com $entrada, passe por trim(...), depois por uma arrow function com mb_convert_case($s, MB_CASE_TITLE) (primeira letra de cada palavra maiúscula, entendendo acentos) e por fim por outra arrow function que monta "Olá, …!". A saída deve continuar Olá, Ana Maria!.
<?php $entrada = " ana MARIA "; $saudacao = "Olá, " . mb_convert_case(trim($entrada), MB_CASE_TITLE) . "!"; echo $saudacao, "\n";
<?php
$entrada = " ana MARIA ";
$saudacao = $entrada
|> trim(...)
|> (fn($s) => mb_convert_case($s, MB_CASE_TITLE))
|> (fn($s) => "Olá, $s!");
echo $saudacao, "\n";
Resumo
- PHP 8.5+
$x |> f(...)éf($x); encadeado, lê-se da esquerda para a direita, na ordem dos passos. - O lado direito é um callable de um argumento:
trim(...),$obj->m(...),Classe::m(...), uma closure, ou(fn($v) => …)entre parênteses. - Funções com mais argumentos obrigatórios: embrulhe numa arrow function.
f(a, ...)não existe. - Nada de funções por referência (
sort). - Contas e
.vêm antes do pipe: use($x |> f(...)) + 1. - Use
mb_strtoupper, e nãostrtoupper, para textos com acento.