O PHP 5.3 trouxe o conceito de funções anônimas e closures que, de forma simples, permitem a criação de funções que não têm um nome especificado.
Num primeiro momento, talvez seu uso possa não ser tão evidente e, não raramente, muitos programadores não entendem seu conceito e/ou desconhecem as potencialidades de funções anônimas no PHP. Se este é o seu caso, continue lendo e veja que, apesar de não terem tanto uso quanto as “tradicionais” funções do PHP, as funções anônimas são bastante úteis em determinados casos.
Explicação de funções anônimas
Comumente, a definição de uma função no PHP segue esta estrutura:
1 2 3 4 |
function myFunctionName() { // PSR-2: https://github.com/tarciozemel/PSR_PT-BR/blob/master/PSR-2.md } |
Quando você define uma função como esta, um nome lhe é atribuído (no caso, “myFunctionName”). O PHP então permite que, no código, seja possível se referir a esta função (o que é conhecido por “chamar a função”) usando seu nome. Por exemplo, para se chamar a função do exemplo acima, basta:
1 |
myFunctionName(); |
Funções anônimas no PHP são semelhantes a funções regulares que contêm um bloco de código que é executado quando são chamadas. Eles também podem aceitar argumentos e valores de retorno.
A principal diferença é que funções anônimas não têm nome. Aqui está um exemplo de código que cria uma função anônima simples:
1 2 3 4 5 |
// Declara uma função anônima básica // (sem muito uso do jeito que está) function($name, $timeOfDay) { return "Bom/boa $timeOfDay, $name!"; }; |
Há duas diferenças sutis, mas importantes, entre o exemplo acima e uma definição de função normal:
- Não há um nome entre a palavra-chave “function” e o parêntese de abertura. Isso informa ao PHP que uma função anônima está sendo criada.
- Há um ponto-e-vírgula após a definição da função. Isso ocorre porque as definições de funções anônimas são expressões, enquanto definições de funções regulares são construções de código.
O código acima é perfeitamente válido, mas não é muito útil… Uma vez que a função anônima não tem nome, você não a pode referenciar em qualquer outro ponto do código, então, ela nunca pode ser chamada!
No entanto, uma vez que uma função anônima é uma expressão (tal como um número ou uma string), é possível fazer várias coisas úteis/interessantes. Por exemplo:
- Atribuí-la a uma variável e, então, chamá-la mais tarde usando o nome dessa variável (é possível até guardar um monte de funções anônimas diferentes num array);
- Passá-la a outra função, que pode chamá-la mais tarde (isso é conhecido como callback);
- Retorná-la (
return
) de uma função externa, de modo que ela acesse variáveis da “função exterior”. Isto é conhecido como um closure.
Atribuindo funções anônimas à variáveis
Quando uma função anônima é definida, é possível armazená-la em uma variável, assim como qualquer outro valor:
1 2 3 4 |
// Atribuindo um funções anônima a uma variável $makeGreeting = function($name, $timeOfDay) { return "Bom/boa $timeOfDay, $name!"; }; |
Uma vez que isso tenha sido feito, é possível chamar a função usando o nome da variável, tal como seria chamada uma função normal:
1 2 3 |
// Chama a função anônima echo $makeGreeting("Fred", "dia") . "<br />"; echo $makeGreeting("Mary", "tarde") . "<br />"; |
É possível, inclusive, armazenar várias funções dentro de um array:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Guarda 3 funções anônimas num array $luckyDip = array( function() { echo "Você ganhou um saco de caramelos!"; }, function() { echo "Você ganhou um carro!"; }, function() { echo "Você ganhou balões!"; } ); |
Uma vez que isso tenha sido feito, o código pode decidir qual a função a ser chamada em tempo de execução. Por exemplo:
1 2 3 |
// Chama uma função aleatoriamente $choice = rand(0, 2); $luckyDip[$choice](); |
Usando funções anônimas como callbacks
Um uso comum de funções anônimas é para criar simples funções de callback inline. Uma função callback é uma função que foi criada e, em seguida, passada a outra função como um argumento. Uma vez que se tem acesso à sua função de retorno de chamada, a função “receptora” pode chamá-la sempre que seja preciso. Isto permite que, de uma maneira fácil, a customização do comportamento da função receptora.
Muitas funções nativas do PHP aceitam retornos de chamada e é possível, também, escrever suas próprias funções de aceitação de callbacks, como mostrado à frente.
Usando array_map()
para executar uma callback em cada elemento de um array
A função array_map()
do PHP aceita uma função de callback ou um array como argumento; então, ela caminha através dos elementos no array e, para cada elemento, chama a respectiva callback com o valor do elemento e a callback precisa retornar o novo valor a ser usado para o elemento; array_map()
, em seguida, substitui o valor do elemento com valor de retorno da callback. Uma vez que todo o processo é feito, array_map()
retorna o array modificado.
array_map()
trabalha em uma cópia do array passado. O array original não é alterado.
Aqui está um exemplo de como é possível usar array_map()
com uma função de callback:
1 2 3 4 5 6 7 8 9 10 |
// Cria uma função de callback ... function nameToGreeting($name) { return "Olá, " . ucfirst($name) . "!"; } // ... e mapeia a função de callback para elementos de um array. $names = array("João", "Maria", "Astolfinho"); print_r(array_map(nameToGreeting, $names)); // Em tela: Array ( [0] => Olá, João! [1] => Olá, Maria! [2] => Olá, Astolfinho! ) |
Embora esse código funcione, é um pouco incômodo criar uma função regular separada apenas para atuar como uma callback simples. Ao invés disso, é possível criar a callback como uma função anônima:
1 2 3 4 |
// Mapeia a função de callback para elementos de um array. print_r(array_map(function($name) { return "Olá, " . ucfirst($name) . "!"; }, $names)); |
Esta abordagem economiza uma linha de código, mas, mais importante, evita sobrecarregar o arquivo PHP com uma função separada que só é usada no contexto de callback.
Ordenação personalizada com usort()
Outro uso comum de callbacks é com a função usort()
do PHP. Esta função permite ordenar arrays usando uma função de callback personalizada. Isso é particularmente útil quando é preciso ordenar um array de objetos ou arrays associativos, já que só uma função personalizada num contexto de programação específico pode resolver/atuar em tais complexas estruturas.
Por exemplo:
1 2 3 4 5 |
$people = array( array("name" => "João", "age" => 39), array("name" => "Maria", "age" => 23), array("name" => "Astolfinho", "age" => 14) ); |
Agora, como exemplo, é preciso classificar o array em ordem de idade crescente. Não é possível usar funções nativas de ordenação de array, já que que estas não sabem nada sobre a chave “age”. Ao invés disso, é possível chamar usort()
e passar em uma função anônima de callback que ordena o array por “age”, desse jeito:
1 2 3 4 5 6 7 8 9 10 11 12 |
usort($people, function($personA, $personB) { return ($personA["age"] < $personB["age"]) ? -1 : 1; }); print_r($people); // Resultado: // Array ( // [0] => Array ( [name] => Astolfinho [age] => 14 ) // [1] => Array ( [name] => Maria [age] => 23 ) // [2] => Array ( [name] => João [age] => 39 ) // ) |
Criando closures com funções anônimas
Outro uso comum de funções anônimas é para criar closures. Closure é uma função que mantém o acesso às variáveis em seu escopo mesmo se este escopo não exista mais.
Por exemplo:
- Uma função,
myFunction()
, contém uma variável local (ou parâmetro) chamado$myVar
; - A função também define e retorna uma função anônima que acessa
$myVar
; - Algum outro código chama
myFunction()
e recebe a função anônima, a qual ele armazena em$anonFunction
. A essa altura, claro,myFunction()
terminou de executar; - Se o código agora chama
$anonFunction()
, a função anônima ainda pode acessar a variável local$myVar
que estava emmyFunction()
, emboramyFunction()
não esteja mais sendo executada! A função anônima, juntamente com a sua referência para a variável$myVar
, constituem um closure.
No início, pode ser difícil entender o conceito de closures, mas, uma vez que você tenha entendido, eles permitem que você escreva código limpo, poderoso e flexível. Alguns exemplos de closure podem tornar as coisas mais claras.
Um closure simples
Um exemplo simples de como criar um closure através de uma função anônima:
1 2 3 4 5 6 7 8 9 10 11 |
function getGreetingFunction() { $timeOfDay = "dia"; return (function($name) use (&$timeOfDay) { $timeOfDay = ucfirst($timeOfDay); return ("Bom/boa $timeOfDay, $name!"); }); }; $greetingFunction = getGreetingFunction(); echo $greetingFunction("Frederico"); // "Bom/boa Dia, Frederico!" |
getGreetingFunction()
getGreetingFunction()
inicializa uma variável local, $timeOfDay
, e define e retorna uma função anônima (descrita abaixo).
A função anônima
A função anônima manipula a variável local $timeOfDay
de getGreetingFunction()
, convertendo sua primeira letra em maiúscula e retornando uma string de saudação que contém o valor de $timeOfDay
.
- A palavra-chave
use
. Normalmente, a função anônima não teria acesso à variável$timeOfDay
, já que esta é local apenas no escopo degetGreetingFunction()
. No entanto, a palavra-chaveuse
diz ao PHP para deixar a função anônima acessar$timeOfDay
. Isso permite criar o closure. - Ampersand (“e comercial”). O ampersand (
&
) antes de$timeOfDay
diz ao PHP para passar por referência a variável$timeOfDay
na função anônima ao invés de apenas copiar o valor da variável – isso permite que a função anônima manipule$timeOfDay
diretamente. Estritamente falando, uma função de closure deve sempre acessar variáveis em seu escopo “fechado” por referência. Dito isso, se não for preciso alterar o valor de uma variável, é possível omitir o ampersand para passar a variável por valor. - Chamando
getGreetingFunction()
.getGreetingFunction()
é chamada e pega-se a função anônima de retorno, que é armazenada na variável$greetingFunction
. Nesse ponto,getGreetingFunction()
terminar sua execução. Em circunstâncias normais, sua variável local$timeOfDay
teria saído do escopo e desaparecido. No entanto, já que se criou um closure através de função anônima (agora em$greetingFunction
), a função anônima ainda pode acessar a variável$timeOfDay
. - Chamando a função anônima. A função anônima é chamada. Ela manipula o valor da variável
$timeofDay
dentro do closure, convertendo sua primeira letra em maiúscula e, em seguida, retornando uma saudação contendo o novo valor de$timeOfDay
.
Resumidamente, é assim que se cria um closure em PHP. Esse é um exemplo trivial, mas o importante a salientar é que a função anônima retornada ainda pode acessar a variável $timeOfDay
mesmo depois de a função “enclausurada” ter terminado de executar.
Usando closures para passar dados adicionais a callbacks
Quando você passar uma função de callback para usort()
e usort()
chama uma callback, esta callback recebe os 2 argumentos passados pela usort()
, ou seja, os 2 valores no array para comparar.
Mas e se for preciso que a callback receba dados extra? Por exemplo, tomando o exemplo com usort()
do início do artigo, seria possível passar um argumento adicional $sortKey
para especificar qual é a chave que deve usada para classificar o array (“name” ou “age”). No entanto, usort()
é que chama a callback. Uma vez que ela é chamada dentro do escopo de usort()
, não se tem a chance de passar argumentos adicionais para a callback no momento em que este é chamado.
Closures para o resgate! Ao retornar a callback de dentro de outra função e criando um closure, é possível obter a função exterior a aceitar $sortKey
como parâmetro e, em seguida, passar $sortKey
para a callback dentro do closure. Aqui está o código completo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
$people = array( array("name" => "João", "age" => 39 , array("name" => "Maria", "age" => 23), array("name" => "Astolfinho", "age" => 14) ); function getSortFunction($sortKey) { return function($personA, $personB) use ($sortKey) { return ($personA[$sortKey] < $personB[$sortKey]) ? -1 : 1; }; } echo "Ordenado por nome:<br /><br />"; usort($people, getSortFunction("name")); print_r($people); echo "<br />"; // Array ( // [0] => Array ( [name] => Astolfinho [age] => 14 ) // [1] => Array ( [name] => João [age] => 39 ) // [2] => Array ( [name] => Maria [age] => 23 ) // ) echo "Ordenado por idade:<br /><br />"; usort($people, getSortFunction("age")); print_r($people); echo "<br />"; // Resultado: // Array ( // [0] => Array ( [name] => Astolfinho [age] => 14 ) // [1] => Array ( [name] => Maria [age] => 23 ) // [2] => Array ( [name] => João [age] => 39 ) // ) |
Array de pessoas
Primeiro, a criação do array de pessoas. Cada pessoa é representada por um array associativo com duas chaves: name
e age
.
getSortFunction()
Em seguida, foi definida a função getSortFunction()
que aceita um parâmetro $SortKey
. Esta é a chave usada para ordenar o array.
Criação do closure
getSortFunction()
define e retorna uma função anônima, que é a função de ordenação que será passada para usort()
. A função anônima aceita os habituais 2 elementos de array para classificar, $personA
e $personB
, mas, também, usa a palavra-chave use
para acessar o parâmetro $sortKey
, que está dentro do escopo da função enclausurada getSortFunction()
. Isso cria o closure. Em seguida, $sortKey
é usada para determinar qual chave usar para a comparação (ou name
ou age
).
$sortKey
após getSortFunction()
terminar sua execução.
Ordenando o array
Finalmente, o código ordena o array, primeiro pelo nome e, em seguida, pela idade. Para fazer isso, ele chama getSortFunction()
passando a chave de classificação (nome
ou age
). getSortFunction()
retorna uma função anônima apropriada que usa a chave de ordenação fornecida. O código, em seguida, passa essa função anônima para usort()
, juntamente com o array para ordenar.
É possível usar esse truque a qualquer momento que seja preciso passar dados adicionais para uma callback.
Conclusão sobre closures e funções anônimas no PHP
Closures e funções anônimas em PHP são um tema amplo e sutil que, como você viu, demandam uma tenção especial no início do aprendizado, já que possuem algumas peculiaridades interessantes e bastante úteis.
Tome este artigo como uma base de referência para o aprendizado, leia quantas vezes forem necessárias até entender tudo e, depois, continue seus estudos sobre o tema, procurando exemplos e códigos que aplicam funções anônimas e/ou closures para se familiarizar ainda mais.
Depois disso, se tiver alguma dica interessante a respeito ou alguma dica ou macete importante de como usá-los em trechos de códigos específicos, coloque nos comentários para também ajudar quem está interessado em aprender mais sobre funções anônimas e closures em PHP.