Closures e funções anônimas em PHP
1. Introdução às funções anônimas
Funções anônimas, também conhecidas como lambdas, são funções sem um nome definido. Em PHP, elas foram introduzidas na versão 5.3 e revolucionaram a forma como trabalhamos com callbacks e programação funcional.
A sintaxe básica de uma função anônima é simples:
<?php
$saudacao = function($nome) {
return "Olá, $nome!";
};
echo $saudacao("Maria"); // Olá, Maria!
A principal diferença entre funções nomeadas e anônimas está na declaração e no escopo. Enquanto funções nomeadas são declaradas com function nome(), as anônimas são expressões que podem ser atribuídas a variáveis, passadas como argumentos ou retornadas de outras funções.
<?php
// Função nomeada
function quadrado($x) {
return $x * $x;
}
// Função anônima equivalente
$quadrado = function($x) {
return $x * $x;
};
echo $quadrado(5); // 25
2. Closures: o que são e como funcionam
Toda função anônima em PHP é, na verdade, uma instância da classe Closure. Uma closure é uma função que "captura" variáveis do escopo onde foi criada, mantendo acesso a elas mesmo quando executada em um contexto diferente.
<?php
$closure = function() {
return "Sou uma closure!";
};
var_dump($closure instanceof Closure); // bool(true)
O objeto closure possui métodos especiais como bind(), bindTo() e call(), que permitem manipular seu escopo e vinculação a objetos.
3. Capturando variáveis do escopo pai com use
A palavra-chave use permite que closures acessem variáveis do escopo externo. Sem ela, a closure só enxerga variáveis definidas em seu próprio escopo ou variáveis globais.
<?php
$mensagem = "Bem-vindo";
$saudacao = function($nome) use ($mensagem) {
return "$mensagem, $nome!";
};
echo $saudacao("João"); // Bem-vindo, João!
Captura por valor vs. por referência
Por padrão, as variáveis são capturadas por valor (cópia). Para capturar por referência, use o operador &:
<?php
$contador = 0;
$incrementar = function() use (&$contador) {
$contador++;
};
$incrementar();
$incrementar();
echo $contador; // 2
Limitações e boas práticas
- Evite capturar grandes arrays por valor, pois eles são copiados
- Use referências com cuidado para evitar efeitos colaterais
- Não abuse do
usepara muitas variáveis - isso prejudica a legibilidade
4. Passando closures como argumentos (callbacks)
Closures são ideais para uso com funções nativas do PHP que aceitam callbacks:
<?php
$numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// array_filter com closure
$pares = array_filter($numeros, function($n) {
return $n % 2 === 0;
});
print_r($pares); // [2, 4, 6, 8, 10]
// array_map com closure
$quadrados = array_map(function($n) {
return $n * $n;
}, $numeros);
print_r($quadrados); // [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
// array_reduce com closure
$soma = array_reduce($numeros, function($carry, $item) {
return $carry + $item;
}, 0);
echo $soma; // 55
Tipos declarados: callable vs. Closure
<?php
function executarCallback(callable $callback) {
return $callback();
}
function executarClosure(Closure $closure) {
return $closure();
}
// Ambos aceitam closures
executarCallback(function() { return "OK"; });
executarClosure(function() { return "OK"; });
// callable aceita funções nomeadas, Closure não
function minhaFuncao() { return "OK"; }
executarCallback('minhaFuncao'); // OK
// executarClosure('minhaFuncao'); // Erro!
5. Retornando closures de funções (high-order functions)
Podemos criar fábricas de funções que retornam closures personalizadas:
<?php
function criarSaudacao($saudacao) {
return function($nome) use ($saudacao) {
return "$saudacao, $nome!";
};
}
$digaOla = criarSaudacao("Olá");
$digaTchau = criarSaudacao("Tchau");
echo $digaOla("Ana"); // Olá, Ana!
echo $digaTchau("Pedro"); // Tchau, Pedro!
Este padrão permite encapsular estado de forma elegante:
<?php
function criarContador($inicial = 0) {
$contagem = $inicial;
return [
'incrementar' => function() use (&$contagem) {
return ++$contagem;
},
'resetar' => function() use (&$contagem) {
$contagem = 0;
},
'valor' => function() use (&$contagem) {
return $contagem;
}
];
}
$contador = criarContador();
echo $contador['incrementar'](); // 1
echo $contador['incrementar'](); // 2
$contador['resetar']();
echo $contador['valor'](); // 0
6. Closures e métodos de objetos: Closure::bind e bindTo
Closures podem ser vinculadas a objetos específicos, permitindo acesso a propriedades e métodos privados/protegidos:
<?php
class Usuario {
private $nome = "João";
protected $email = "joao@email.com";
}
$closure = function() {
return $this->nome . " - " . $this->email;
};
// Vincula a closure ao objeto com acesso total
$bound = $closure->bindTo(new Usuario(), 'Usuario');
echo $bound(); // João - joao@email.com
// Usando Closure::bind estaticamente
$resultado = Closure::bind($closure, new Usuario(), 'Usuario');
echo $resultado(); // João - joao@email.com
7. Closures estáticas com static
Closures estáticas não capturam automaticamente $this e são úteis para evitar vazamento de contexto:
<?php
class Logger {
private $prefixo = "[LOG]";
public function getLogger() {
// Closure normal - captura $this
$normal = function($msg) {
return $this->prefixo . " $msg";
};
// Closure estática - não captura $this
$estatica = static function($msg) {
// $this não está disponível aqui
return "[LOG] $msg";
};
return [$normal, $estatica];
}
}
$logger = new Logger();
[$normal, $estatica] = $logger->getLogger();
echo $normal("teste"); // [LOG] teste
echo $estatica("teste"); // [LOG] teste
Closures estáticas consomem menos memória e são mais seguras em contextos onde $this não é necessário.
8. Boas práticas e armadilhas comuns
Evitando efeitos colaterais com referências
<?php
// PROBLEMA: referência inesperada
$valores = [1, 2, 3];
$closures = [];
foreach ($valores as &$valor) {
$closures[] = function() use (&$valor) {
return $valor;
};
}
foreach ($closures as $c) {
echo $c() . " "; // 3 3 3 (todos apontam para a última referência)
}
// SOLUÇÃO: capturar por valor ou criar nova variável
$closures = [];
foreach ($valores as $valor) {
$closures[] = function() use ($valor) {
return $valor;
};
}
foreach ($closures as $c) {
echo $c() . " "; // 1 2 3
}
Gerenciamento de memória
<?php
// Evite capturar grandes estruturas
$grandeArray = range(1, 1000000);
// RUIM: captura o array inteiro
$closureRuim = function() use ($grandeArray) {
return count($grandeArray);
};
// BOM: captura apenas o necessário
$tamanho = count($grandeArray);
$closureBoa = function() use ($tamanho) {
return $tamanho;
};
unset($grandeArray); // Libera memória
echo $closureBoa(); // 1000000
Depuração de closures
<?php
$closure = function($x) {
return $x * 2;
};
// Usando debug_backtrace
function rastrearClosure(Closure $c) {
$reflection = new ReflectionFunction($c);
echo "Arquivo: " . $reflection->getFileName() . "\n";
echo "Linha inicial: " . $reflection->getStartLine() . "\n";
echo "Linha final: " . $reflection->getEndLine() . "\n";
echo "Parâmetros: " . $reflection->getNumberOfParameters() . "\n";
}
rastrearClosure($closure);
// var_dump mostra informações limitadas
var_dump($closure);
// object(Closure)#1 (1) { ["parameter"]=> array(1) { ... } }
Closures são ferramentas poderosas no PHP moderno. Quando usadas corretamente, permitem escrever código mais expressivo, modular e reutilizável. Lembre-se sempre de considerar o escopo de variáveis, gerenciamento de memória e evitar efeitos colaterais indesejados.
Referências
- PHP Manual: Funções Anônimas — Documentação oficial do PHP sobre funções anônimas, incluindo sintaxe e exemplos básicos.
- PHP Manual: Classe Closure — Documentação oficial da classe Closure, com métodos como bind, bindTo e call.
- PHP The Right Way: Closures e Funções Anônimas — Guia prático sobre closures e programação funcional em PHP.
- Laravel News: Understanding PHP Closures — Artigo técnico detalhado sobre closures em PHP com exemplos avançados.
- PHPInternals: Closures Internals — Explicação técnica de como closures funcionam internamente no motor PHP.
- SitePoint: Mastering PHP Closures — Tutorial completo sobre closures em PHP, incluindo padrões de design e boas práticas.
- PHP Watch: Static Closures — Artigo específico sobre closures estáticas e suas vantagens de performance e segurança.