PHP Funções Anônimas e Arrow Functions

Até aqui, toda função tinha um nome: function soma(…). Em PHP você também pode criar uma função sem nome e guardá-la numa variável, como se fosse um número ou um texto. Isso parece estranho até você ver para que serve: entregar um "pedaço de comportamento" para outra função usar, como "dobre cada preço" ou "fique só com as notas acima de 7".

Você vai aprender
  • O que é uma função anônima (closure) e como guardá-la numa variável e chamá-la.
  • Por que ela não enxerga as variáveis de fora, e como trazê-las com use ($x) e use (&$x).
  • As arrow functions fn($x) => $x * 2, curtas e com captura automática.
  • Como passar funções como argumento para array_map e array_filter, e como receber uma função com o tipo callable.
  • Os erros mais comuns, com as mensagens reais do PHP.

Esta aula usa funções, arrays e o que você viu em Escopo (uma função não enxerga as variáveis de fora). Os exemplos rodam na máquina da Ana, na pasta ~/site.


A ideia antes do código: o bilhete com instruções

A Ana vai viajar e deixa a lojinha com o Bruno. Ela não sabe ainda quais produtos vão chegar, mas sabe o que fazer com cada um: "cole uma etiqueta com o preço em dobro". Ela escreve essa instrução num bilhete e entrega ao Bruno. O Bruno não precisa saber de onde veio a regra: ele pega cada produto e aplica o que está no bilhete.

Uma função anônima é esse bilhete: um conjunto de instruções que você guarda e entrega para outra parte do programa executar quando precisar. O PHP tem funções prontas (como array_map, o "Bruno") que recebem esse bilhete e o aplicam a cada item de um array.


Função anônima: uma função guardada numa variável

<?php
$dobro = function ($x) {
    return $x * 2;
};

echo $dobro(5), "\n";
echo $dobro(21), "\n";
$dobro =
Uma variável comum, que vai guardar a função.
function ($x)
A palavra function sem nome logo depois: por isso "anônima". O resto é igual a uma função normal: parâmetros entre parênteses…
{ return $x * 2; }
…e o corpo entre chaves.
;
O ponto e vírgula depois da } é obrigatório. A linha inteira é uma atribuição ($dobro = …;), e toda atribuição termina com ;. Numa função com nome, não há ; depois da chave.
$dobro(5)
Para chamar, use a variável seguida de parênteses com os argumentos.
$ php anonima.php
10
42

O que tem dentro de $dobro? Um objeto da classe Closure (você vai ver objetos e classes no Nível 3). O var_dump mostra:

$ php -r '$dobro = function ($x) { return $x * 2; }; var_dump($dobro);'
object(Closure)#1 (4) {
  ["name"]=>
  string(29) "{closure:Command line code:1}"
  ["file"]=>
  string(17) "Command line code"
  ["line"]=>
  int(1)
  ["parameter"]=>
  array(1) {
    ["$x"]=>
    string(10) "<required>"
  }
}

Por isso as funções anônimas também são chamadas de closures (o nome da classe; em inglês, closure é "fechamento", porque a função pode "fechar" variáveis de fora dentro dela, como você vai ver no use). O nome que o PHP inventa para ela, {closure:arquivo:linha}, aparece nas mensagens de erro PHP 8.4+; nas versões antigas aparecia só {closure}.

Experimente: rode e depois crie uma função anônima $triplo logo abaixo da $dobro e mostre $triplo(7). Tente também echo $dobro($dobro(3)); (a função usada duas vezes).

<?php
$dobro = function ($x) {
    return $x * 2;
};

echo $dobro(5), "\n";
echo $dobro(21), "\n";

Passar uma função para outra: array_map

Guardar uma função numa variável só fica útil quando você a entrega a alguém. O exemplo mais comum é array_map ("mapear o array"): ele recebe uma função e um array, chama a função para cada elemento e devolve um array novo com os resultados.

<?php
$precos = [10, 25, 4.5];

$dobrados = array_map(function ($p) {
    return $p * 2;
}, $precos);

print_r($dobrados);
array_map(
A função pronta do PHP. O primeiro argumento é a função a aplicar; o segundo, o array.
function ($p) { return $p * 2; }
A função anônima, escrita direto no lugar do argumento, sem guardar numa variável. O array_map vai chamá-la três vezes: com 10, com 25 e com 4.5.
, $precos)
O array original. Ele não muda: o resultado vem num array novo, guardado em $dobrados.
$ php map.php
Array
(
    [0] => 20
    [1] => 50
    [2] => 9
)

O mesmo resultado com um foreach exigiria criar o array vazio, percorrer e acrescentar. Com array_map, você diz só o que fazer com cada item. Funções de array que recebem outras funções aparecem muito: array_map (transformar), array_filter (filtrar, mais abaixo), usort (ordenar com uma regra sua, veja Ordenar Arrays).

Funções com nome também podem ser entregues

No lugar da função anônima, você pode passar o nome de uma função existente, entre aspas: array_map('ucfirst', ["ana", "bruno"]) devolve ["Ana", "Bruno"]. Tudo o que pode ser chamado assim (uma closure, o nome de uma função…) se chama callable ("chamável"). A forma moderna de fazer isso, ucfirst(...), está em Callables de Primeira Classe.


A closure não enxerga as variáveis de fora

A Ana quer acrescentar uma taxa de 10% em cada preço. A taxa está numa variável, fora da função:

<?php
$taxa = 0.1;

$comTaxa = function ($preco) {
    return $preco + $preco * $taxa;
};

echo $comTaxa(50), "\n";
$ php semuse.php

Warning: Undefined variable $taxa in /home/ana/site/semuse.php on line 5
50                      « a taxa valeu null, e 50 + 50 × null = 50

"Variável indefinida $taxa". Como qualquer função (veja Escopo), a closure tem o seu próprio "mundo" de variáveis: o $taxa lá de fora não existe aqui dentro. O PHP avisa, usa null no lugar e o programa continua com o resultado errado.

use ($taxa): levar uma cópia para dentro

A palavra use ("usar"), escrita depois dos parâmetros, diz quais variáveis de fora a closure deve levar junto:

<?php
$taxa = 0.1;

$comTaxa = function ($preco) use ($taxa) {
    return $preco + $preco * $taxa;
};

echo $comTaxa(50), "\n";

$taxa = 0.5;
echo $comTaxa(50), "\n";
function ($preco)
O parâmetro: o valor que muda a cada chamada.
use ($taxa)
A variável de fora que a closure leva. Várias, separadas por vírgula: use ($taxa, $frete).
$ php use.php
55
55              « mudar $taxa para 0.5 depois NÃO mudou a closure

Repare na segunda linha. O use copia o valor da variável no momento em que a closure é criada (a linha 4), como tirar uma foto. Mudar $taxa depois não altera a foto que a closure guardou.

Missão: a função anônima usa $desconto, mas não o enxerga: rode e veja os três avisos (um por preço). Acrescente o use certo para a saída ser 40, 24, 10, sem avisos.

<?php
$desconto = 0.2;
$precos = [50, 30, 12.5];

$comDesconto = array_map(function ($p) {
    return $p - $p * $desconto;
}, $precos);

echo implode(", ", $comDesconto), "\n";
<?php
$desconto = 0.2;
$precos = [50, 30, 12.5];

$comDesconto = array_map(function ($p) use ($desconto) {
    return $p - $p * $desconto;
}, $precos);

echo implode(", ", $comDesconto), "\n";

Sem o use, o PHP real mostra o aviso três vezes, porque a função foi chamada uma vez para cada preço:

$ php desconto.php

Warning: Undefined variable $desconto in /home/ana/site/desconto.php on line 6

Warning: Undefined variable $desconto in /home/ana/site/desconto.php on line 6

Warning: Undefined variable $desconto in /home/ana/site/desconto.php on line 6
50, 30, 12.5

use (&$total): mexer na variável de fora

Como o use leva uma cópia, mudar a variável dentro da closure também não afeta a de fora. Quando você precisa disso (um contador, um total acumulado), coloque & antes do nome: a closure passa a usar a própria variável, e não uma cópia. Isso se chama passagem por referência.

<?php
$total = 0;

$somar = function ($valor) use ($total) {
    $total += $valor;
};
$somar(10);
$somar(5);
echo "Sem &: $total\n";

$somarDeVerdade = function ($valor) use (&$total) {
    $total += $valor;
};
$somarDeVerdade(10);
$somarDeVerdade(5);
echo "Com &: $total\n";
$ php useref.php
Sem &: 0         « cada chamada somou na sua cópia, que foi jogada fora
Com &: 15        « a closure mexeu no $total de verdade
FormaO que a closure recebeMudanças de fora, depois de criadaMudanças de dentro
use ($x)Uma cópia do valor (a "foto")Não aparecem dentroNão saem para fora
use (&$x)A própria variávelAparecem dentroSaem para fora
Use o & com moderação

Uma closure que muda variáveis de fora é mais difícil de entender: quem lê o código precisa procurar onde ela é chamada para saber o valor final. Quando der, prefira que a função devolva um valor com return e você mesmo o guarde. O mesmo conselho vale para o global, que você viu em Escopo.


Arrow functions: fn($x) => …

Funções anônimas pequenas, de uma linha só, são tão comuns que o PHP 7.4 criou uma forma curta: a arrow function ("função seta", por causa do =>).

<?php
$dobro = fn($x) => $x * 2;
echo $dobro(5), "\n";

$taxa = 0.1;
$comTaxa = fn($preco) => $preco + $preco * $taxa;
echo $comTaxa(50), "\n";

$precos = [10, 25, 4.5];
print_r(array_map(fn($p) => $p * 2, $precos));
fn
Abreviação de function.
($x)
Os parâmetros, como sempre.
=>
A "seta": separa os parâmetros do resultado.
$x * 2
Uma única expressão, que é devolvida automaticamente. Não se escreve return, nem chaves.
$ php arrow.php
10
55                « a arrow function enxergou $taxa sozinha, sem use
Array
(
    [0] => 20
    [1] => 50
    [2] => 9
)

A grande vantagem aparece na segunda função: não precisou de use. A arrow function captura automaticamente, por valor, todas as variáveis de fora que ela usa. É como se o PHP escrevesse o use ($taxa) por você. Compare as duas versões do mesmo código:

<?php
// função anônima: return, chaves, ponto e vírgula e use
$comTaxa = function ($preco) use ($taxa) {
    return $preco + $preco * $taxa;
};

// arrow function: uma linha
$comTaxa = fn($preco) => $preco + $preco * $taxa;

Os limites da arrow function

Como a captura é por valor (uma cópia), uma arrow function não consegue mudar variáveis de fora:

<?php
$contador = 0;
$somaUm = fn() => $contador++;
$somaUm();
$somaUm();
echo "contador = $contador\n";
$ php arrowmuda.php
contador = 0       « cada chamada aumentou uma cópia

E ela só aceita uma expressão. Se precisar de várias linhas (um if, uma variável auxiliar, um foreach), use a função anônima completa. Tentar abrir chaves depois da seta dá erro de sintaxe (veja Quando dá errado).


Filtrar com array_filter

array_filter ("filtrar o array") recebe um array e uma função que responde true ou false para cada elemento. Ficam só os elementos com true. A Ana quer saber quem passou de ano:

<?php
$notas = ["Ana" => 9.5, "Bruno" => 5.0, "Carla" => 8.5, "Diego" => 6.0];

$aprovados = array_filter($notas, fn($nota) => $nota >= 7);
print_r($aprovados);

$minima = 6;
$recuperacao = array_filter($notas, fn($nota) => $nota < 7 && $nota >= $minima);
print_r($recuperacao);
$ php filtrar.php
Array
(
    [Ana] => 9.5
    [Carla] => 8.5
)
Array
(
    [Diego] => 6
)

Repare na ordem dos argumentos: array_filter($array, $funcao), ao contrário de array_map($funcao, $array). É uma confusão famosa do PHP; na dúvida, consulte Funções de Array. E repare que as chaves (os nomes) foram mantidas.

Experimente: mude a nota mínima de aprovação para 8.5 e veja quem sobra. Depois crie $nomes = array_map(fn($n) => strtoupper($n), array_keys($aprovados)); e mostre os nomes em maiúsculas com print_r.

<?php
$notas = ["Ana" => 9.5, "Bruno" => 5.0, "Carla" => 8.5, "Diego" => 6.0];

$aprovados = array_filter($notas, fn($nota) => $nota >= 7);
print_r($aprovados);

Outras formas de usar funções como valores

Guardar funções num array

Funções são valores; então podem ficar dentro de um array. Uma calculadora sem nenhum if:

<?php
$operacoes = [
    "+" => fn($a, $b) => $a + $b,
    "-" => fn($a, $b) => $a - $b,
    "*" => fn($a, $b) => $a * $b,
    "/" => fn($a, $b) => $a / $b,
];

foreach ($operacoes as $sinal => $conta) {
    echo "12 $sinal 4 = ", $conta(12, 4), "\n";
}

echo $operacoes["*"](6, 7), "\n";
$ php calculadora.php
12 + 4 = 16
12 - 4 = 8
12 * 4 = 48
12 / 4 = 3
42

$operacoes["*"] é a função de multiplicar; (6, 7) logo depois a chama. Você vai usar essa ideia no projeto da calculadora.

Receber uma função: o tipo callable

Você também pode escrever as suas próprias funções que recebem funções. O tipo callable garante que o argumento pode ser chamado:

<?php
function aplicarEmTodos(array $lista, callable $funcao): array
{
    $resultado = [];
    foreach ($lista as $item) {
        $resultado[] = $funcao($item);
    }
    return $resultado;
}

$precos = [10, 25, 4.5];
print_r(aplicarEmTodos($precos, fn($p) => $p * 2));
print_r(aplicarEmTodos(["ana", "bruno"], 'strtoupper'));
print_r(aplicarEmTodos($precos, "dobrar"));
$ php aplicar.php
Array
(
    [0] => 20
    [1] => 50
    [2] => 9
)
Array
(
    [0] => ANA
    [1] => BRUNO
)

Fatal error: Uncaught TypeError: aplicarEmTodos(): Argument #2 ($funcao) must be of type callable, string given, called in /home/ana/site/aplicar.php on line 14 and defined in /home/ana/site/aplicar.php:2
Stack trace:
#0 /home/ana/site/aplicar.php(14): aplicarEmTodos(Array, 'dobrar')
#1 {main}
  thrown in /home/ana/site/aplicar.php on line 2

Você acabou de escrever a sua própria versão do array_map. A última chamada falhou porque não existe função chamada dobrar: o texto "dobrar" não é "chamável", e o tipo callable barrou na entrada.

Uma função que devolve uma função

<?php
function multiplicador(int $vezes): Closure
{
    return fn($x) => $x * $vezes;
}

$dobro = multiplicador(2);
$triplo = multiplicador(3);
echo $dobro(10), " ", $triplo(10), "\n";
$ php fabrica.php
20 30

multiplicador é uma "fábrica de funções": cada chamada cria uma arrow function que guardou o seu próprio $vezes. É a captura por valor trabalhando a seu favor.

Qual usar?

SituaçãoUse
Função curta, uma expressão (transformar, comparar, filtrar)Arrow function: fn($x) => $x * 2
Função com várias linhas, if, variáveis auxiliaresFunção anônima: function ($x) use ($y) { … }
Precisa mudar uma variável de foraFunção anônima com use (&$x) (com moderação)
A função será usada em vários lugares do programaUma função com nome, passada como 'nome' ou nome(...)
Fazer a mesma coisa com cada item e guardar o resultadoarray_map (ou um foreach, se ficar mais claro)

Quando dá errado

Warning: Undefined variable $taxa dentro da closure
$ php semuse.php

Warning: Undefined variable $taxa in /home/ana/site/semuse.php on line 5
50

O que aconteceu: a função anônima usou uma variável de fora sem use. Ela não existe lá dentro, vale null e a conta sai errada.

Como consertar: function ($preco) use ($taxa) { … }, ou troque por uma arrow function, que captura sozinha: fn($preco) => $preco + $preco * $taxa.

Esqueceu o ; depois da }: unexpected token "echo"
<?php
$dobro = function ($x) {
    return $x * 2;
}

echo $dobro(5), "\n";
$ php semponto.php

Parse error: syntax error, unexpected token "echo" in /home/ana/site/semponto.php on line 6

O que significa: "erro de sintaxe, palavra echo inesperada na linha 6". O PHP ainda estava lendo a atribuição $dobro = … e esperava o ;; encontrou echo. Repare que a linha indicada é a seguinte ao erro de verdade: o erro está na linha 4, onde falta o ponto e vírgula. Nenhuma linha do programa rodou.

Como consertar: };. Lembre: a função anônima é um valor numa atribuição.

Object of class Closure could not be converted to string
<?php
$dobro = function ($x) {
    return $x * 2;
};

echo $dobro, "\n";
$ php echofunc.php

Fatal error: Uncaught Error: Object of class Closure could not be converted to string in /home/ana/site/echofunc.php:6
Stack trace:
#0 {main}
  thrown in /home/ana/site/echofunc.php on line 6

O que significa: "um objeto da classe Closure não pode ser convertido em texto". Sem os parênteses, $dobro é a função em si, e o echo não sabe escrever uma função.

Como consertar: chame a função: echo $dobro(5);.

Too few arguments to function {closure:…}()
$ php poucos.php      « echo $dobro(), "\n";

Fatal error: Uncaught ArgumentCountError: Too few arguments to function {closure:/home/ana/site/poucos.php:2}(), 0 passed in /home/ana/site/poucos.php on line 6 and exactly 1 expected in /home/ana/site/poucos.php:2
Stack trace:
#0 /home/ana/site/poucos.php(6): {closure:/home/ana/site/poucos.php:2}()
#1 {main}
  thrown in /home/ana/site/poucos.php on line 2

O que significa: a função anônima criada na linha 2 esperava 1 argumento e recebeu 0. Como ela não tem nome, o PHP a chama de {closure:arquivo:linha}: a linha indicada é onde ela foi criada, o que ajuda a achá-la.

Como consertar: passe os argumentos que ela pede, ou dê um valor padrão ao parâmetro: function ($x = 1).

Arrow function com chaves: unexpected token "{"
<?php
$comTaxa = fn($preco) => {
    $taxa = 0.1;
    return $preco + $preco * $taxa;
};
$ php arrowmulti.php

Parse error: syntax error, unexpected token "{" in /home/ana/site/arrowmulti.php on line 2

O que significa: depois do => o PHP espera uma expressão (um valor, uma conta), e não um bloco de código.

Como consertar: se cabe numa expressão, escreva só ela: fn($preco) => $preco * 1.1. Se precisa de várias linhas, use function ($preco) { … };.

A closure não mudou a variável (sem erro)
$ php arrowmuda.php
contador = 0

O que aconteceu: tanto use ($x) quanto a arrow function trabalham com uma cópia. O código é válido; ele só não faz o que você esperava.

Como consertar: faça a função devolver o novo valor ($contador = $somaUm($contador);) ou use uma função anônima com use (&$contador).


Referência

FormaO que éExemplo
function ($x) { … };Função anônima (closure)$f = function ($x) { return $x + 1; };
$f(…)Chamar a função guardada na variável$f(5) → 6
use ($a, $b)Leva cópias de variáveis de forafunction ($p) use ($taxa) { … }
use (&$a)Leva a própria variável (pode mudá-la)function ($v) use (&$total) { $total += $v; }
fn($x) => exprArrow function: uma expressão, captura automática por valor PHP 7.4+fn($x) => $x * 2
array_map($f, $array)Aplica $f a cada elemento; devolve um array novoarray_map(fn($p) => $p * 2, [1, 2]) → [2, 4]
array_filter($array, $f)Fica com os elementos em que $f devolve truearray_filter($notas, fn($n) => $n >= 7)
callableTipo de parâmetro: "algo que pode ser chamado"function f(callable $g)
ClosureA classe das funções anônimas (também usada como tipo)function f(): Closure

Exercícios

1. Arrow function

Complete a arrow function que devolve o quadrado de um número:

$quadrado = ;

2. Levar a variável

Complete para a função anônima enxergar $frete:

$total = function ($preco) { return $preco + $frete; };

3. Transformar todos

Complete para obter os preços com 10% de aumento:

$novos = (fn($p) => $p * 1.1, $precos);

4. O que este código mostra? $x = 1; $f = function () use ($x) { return $x; }; $x = 2; echo $f();

5. Qual destas afirmações sobre fn($x) => … é verdadeira?

6. Você escreveu echo $dobro; e apareceu Object of class Closure could not be converted to string. O que faltou?

Missão final

Missão: a lojinha da Ana vai dar destaque aos produtos baratos, com 10% de aumento. (1) Troque a primeira linha marcada por um array_filter com uma arrow function que deixe só os produtos com preço até $limite. (2) Troque a segunda por um array_map com uma arrow function que multiplique cada preço por 1.1. A saída deve ter só pudim: R$ 13,20 e brigadeiro: R$ 2,20.

<?php
$produtos = ["bolo" => 25, "pudim" => 12, "torta" => 40, "brigadeiro" => 2];
$limite = 20;

$baratos = $produtos;   // (1) troque por array_filter(…)
$novos = $baratos;      // (2) troque por array_map(…)

foreach ($novos as $nome => $preco) {
    echo "$nome: R$ ", number_format($preco, 2, ",", "."), "\n";
}
<?php
$produtos = ["bolo" => 25, "pudim" => 12, "torta" => 40, "brigadeiro" => 2];
$limite = 20;

$baratos = array_filter($produtos, fn($preco) => $preco <= $limite);
$novos = array_map(fn($preco) => $preco * 1.1, $baratos);

foreach ($novos as $nome => $preco) {
    echo "$nome: R$ ", number_format($preco, 2, ",", "."), "\n";
}

Resumo

  • Uma função anônima (closure) é uma função sem nome, guardada numa variável: $f = function ($x) { … }; (com ; no fim). Chame com $f(…).
  • Ela não enxerga variáveis de fora. use ($x) leva uma cópia do valor no momento da criação; use (&$x) leva a própria variável.
  • A arrow function fn($x) => expressão é a forma curta: uma expressão, return automático e captura automática por valor.
  • Funções são valores: podem ser passadas para array_map, array_filter, usort, guardadas em arrays e devolvidas por outras funções. O tipo do parâmetro é callable (ou Closure).
  • Erros comuns: Undefined variable (faltou use), unexpected token (faltou ; ou chaves numa arrow function), could not be converted to string (faltaram os parênteses).