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ê.

Você vai aprender
  • O que o |> faz: $x |> f(...) é o mesmo que f($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.

Só no PHP 8.5 ou mais novo

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:

JeitoVantagemDesvantagem
1. AninhadoCurtoLê-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áriasFunciona em qualquer versão do PHP; cada passo pode ser inspecionado com var_dumpNomes 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 inventadosSó 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 direitoExemplo
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çãoMelhor 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 argumentoPipe
Passos que precisam de vários argumentosPipe 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 anteriorAninhado ou variáveis temporárias

Quando dá errado

Arrow functions on the right hand side of |> must be parenthesized

O que significa: você escreveu |> fn($x) => … sem parênteses.

Como consertar: |> (fn($x) => …).

ArgumentCountError: … expects at least 2 arguments, 1 given

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)).

Unsupported operand types: Closure + int / Object of class Closure could not be converted to string

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.

could not be passed by reference

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.

Parse error na linha do |>, 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

EscritaSignificado
$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(...)) + 1Parênteses antes de contas e de .

Exercícios

1. Traduzir para pipe

Complete a versão com pipe de strrev(trim($s)):

$s |> trim(...) |>

2. Traduzir de volta

Escreva sem pipe, aninhado, o mesmo que $nome |> trim(...) |> ucfirst(...):

$r = ;

3. Dois argumentos

Complete o passo que troca vírgulas por pontos com str_replace:

$valor = "19,90" |> ;

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ão strtoupper, para textos com acento.