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 use para 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