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".
- 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)euse (&$x). - As arrow functions
fn($x) => $x * 2, curtas e com captura automática. - Como passar funções como argumento para
array_mapearray_filter, e como receber uma função com o tipocallable. - 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
functionsem 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_mapvai 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).
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
| Forma | O que a closure recebe | Mudanças de fora, depois de criada | Mudanças de dentro |
|---|---|---|---|
use ($x) | Uma cópia do valor (a "foto") | Não aparecem dentro | Não saem para fora |
use (&$x) | A própria variável | Aparecem dentro | Saem para fora |
& 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ção | Use |
|---|---|
| Função curta, uma expressão (transformar, comparar, filtrar) | Arrow function: fn($x) => $x * 2 |
Função com várias linhas, if, variáveis auxiliares | Função anônima: function ($x) use ($y) { … } |
| Precisa mudar uma variável de fora | Função anônima com use (&$x) (com moderação) |
| A função será usada em vários lugares do programa | Uma função com nome, passada como 'nome' ou nome(...) |
| Fazer a mesma coisa com cada item e guardar o resultado | array_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.
; 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).
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) { … };.
$ 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
| Forma | O 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 fora | function ($p) use ($taxa) { … } |
use (&$a) | Leva a própria variável (pode mudá-la) | function ($v) use (&$total) { $total += $v; } |
fn($x) => expr | Arrow 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 novo | array_map(fn($p) => $p * 2, [1, 2]) → [2, 4] |
array_filter($array, $f) | Fica com os elementos em que $f devolve true | array_filter($notas, fn($n) => $n >= 7) |
callable | Tipo de parâmetro: "algo que pode ser chamado" | function f(callable $g) |
Closure | A 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:
2. Levar a variável
Complete para a função anônima enxergar $frete:
3. Transformar todos
Complete para obter os preços com 10% de aumento:
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,returnautomá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(ouClosure). - Erros comuns:
Undefined variable(faltouuse),unexpected token(faltou;ou chaves numa arrow function),could not be converted to string(faltaram os parênteses).