No PHP, o que é um fechamento e por que ele usa o identificador "use"?

407

Estou verificando alguns PHP 5.3.0recursos e deparei com um código no site que parece bem engraçado:

public function getTotal($tax)
{
    $total = 0.00;

    $callback =
        /* This line here: */
        function ($quantity, $product) use ($tax, &$total)
        {
            $pricePerItem = constant(__CLASS__ . "::PRICE_" .
                strtoupper($product));
            $total += ($pricePerItem * $quantity) * ($tax + 1.0);
        };

    array_walk($this->products, $callback);
    return round($total, 2);
}

como um dos exemplos de funções anônimas .

Alguém sabe sobre isso? Alguma documentação? E parece mal, deveria ser usado?

SeanDowney
fonte

Respostas:

362

É assim que o PHP expressa um fechamento . Isso não é nada mau e, de fato, é bastante poderoso e útil.

Basicamente, o que isso significa é que você está permitindo que a função anônima "capture" variáveis ​​locais (neste caso, $taxe uma referência a $total) fora do escopo e preserve seus valores (ou no caso da $totalreferência a $totalsi mesma) como estado dentro a própria função anônima.

Andrew Hare
fonte
11
Então, é usado apenas para fechamentos? Obrigado pela sua explicação, eu não sabia a diferença entre função anônima e fechamento #
SeanDowney 30/06/09
136
A usepalavra-chave também é usada para aliasing namespaces . É incrível que, mais de três anos após o lançamento do PHP 5.3.0, a sintaxe function ... useainda não esteja documentada oficialmente, o que torna o fechamento um recurso não documentado. O documento até confunde funções e fechamentos anônimos . A única documentação (beta e não oficial) use ()que encontrei no php.net foi a RFC para fechamento .
2
Então, quando a função de uso de fechamentos foi implementada em PHP? Eu acho que era no PHP 5.3? Está documentado no manual do PHP agora de alguma forma?
rubo77
@Mytskine Bem, de acordo com o documento, funções anônimas usar a classe Closure
Manny Fleurmond
11
Agora usetambém é usado para incluir um traitem um class!
CJ Dennis
477

Uma resposta mais simples.

function ($quantity) use ($tax, &$total) { .. };

  1. O fechamento é uma função atribuída a uma variável, para que você possa passá-lo
  2. Um fechamento é um espaço para nome separado; normalmente, você não pode acessar variáveis ​​definidas fora deste espaço para nome. Aí vem opalavra-chave use :
  3. usar permite acessar (usar) as variáveis ​​seguintes dentro do fechamento.
  4. usar é obrigatório. Isso significa que os valores das variáveis ​​são COPIADOS após DEFINIR o fechamento. Portanto, modificar $taxdentro do fechamento não tem efeito externo, a menos que seja um ponteiro, como um objeto.
  5. Você pode passar variáveis ​​como ponteiros, como no caso de &$total . Dessa forma, modificando o valor de $totalTEM UM efeito externo, o valor da variável original muda.
  6. As variáveis ​​definidas dentro do fechamento também não são acessíveis de fora do fechamento.
  7. Fechamentos e funções têm a mesma velocidade. Sim, você pode usá-los em todos os seus scripts.

Como o @Mytskine apontou, provavelmente a melhor explicação detalhada é a RFC para fechamentos . (Vote nele por isso.)

zupa
fonte
4
A palavra chave na declaração de uso está me dando um erro de sintaxe em PHP 5.5: $closure = function ($value) use ($localVar as $alias) { //stuff};determinado erro é:Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
Kal Zekdor
11
O @KalZekdor, confirmado com o php5.3, também parece obsoleto. Atualizei a resposta, obrigado pelo seu esforço.
zupa
4
Eu acrescentaria ao ponto 5 que dessa maneira, modificar o valor de um ponteiro também &$totaltem um efeito interno. Em outras palavras, se você alterar o valor de $total fora do fechamento depois que ele for definido, o novo valor somente será passado se for um ponteiro.
billynoah
2
@ AndyD273 a finalidade é realmente muito semelhante, exceto globalapenas permite o acesso ao espaço para nome global, ao passo que usepermite acessar variáveis ​​no espaço para nome pai. Variáveis ​​globais são geralmente consideradas más. Acessar o escopo pai geralmente é o próprio objetivo de criar um fechamento. Não é "mau", pois seu escopo é muito limitado. Outras linguagens como JS usam implicitamente as variáveis ​​do escopo pai (como ponteiro, não como valor copiado).
zupa
11
Esta linha parei de duas horas busca vãYou can pass in variables as pointers like in case of &$total. This way, modifying the value of $total DOES HAVE an external effect, the original variable's value changes.
BlackPearl
69

o function () use () {} fechamento é semelhante ao PHP.

Sem use, a função não pode acessar a variável de escopo pai

$s = "hello";
$f = function () {
    echo $s;
};

$f(); // Notice: Undefined variable: s
$s = "hello";
$f = function () use ($s) {
    echo $s;
};

$f(); // hello

O usevalor da variável é de quando a função é definida, não quando chamada

$s = "hello";
$f = function () use ($s) {
    echo $s;
};

$s = "how are you?";
$f(); // hello

use variável por referência com &

$s = "hello";
$f = function () use (&$s) {
    echo $s;
};

$s = "how are you?";
$f(); // how are you?
Asa de aço
fonte
4
Depois de ler isso, não estou me arrependendo um pouco mais da rolagem, mas acho que preciso de uma edição menor para erros de digitação no terceiro bloco. Deve haver $ s em vez de $ obj.
Stack user
53

fechamentos são lindos! eles resolvem muitos problemas que vêm com funções anônimas e tornam possíveis códigos realmente elegantes (pelo menos enquanto falamos sobre php).

programadores javascript usam fechamentos o tempo todo, às vezes até sem saber, porque as variáveis ​​vinculadas não são explicitamente definidas - é para isso que serve o "uso" no php.

existem melhores exemplos do mundo real do que o anterior. digamos que você precise classificar uma matriz multidimensional por um subvalor, mas a chave muda.

<?php
    function generateComparisonFunctionForKey($key) {
        return function ($left, $right) use ($key) {
            if ($left[$key] == $right[$key])
                return 0;
            else
                return ($left[$key] < $right[$key]) ? -1 : 1;
        };
    }

    $myArray = array(
        array('name' => 'Alex', 'age' => 70),
        array('name' => 'Enrico', 'age' => 25)
    );

    $sortByName = generateComparisonFunctionForKey('name');
    $sortByAge  = generateComparisonFunctionForKey('age');

    usort($myArray, $sortByName);

    usort($myArray, $sortByAge);
?>

aviso: código não testado (eu não tenho o php5.3 instalado no atm), mas deve se parecer com algo assim.

há uma desvantagem: muitos desenvolvedores de php podem ficar um pouco impotentes se você os confrontar com fechamentos.

para entender melhor a boa aparência dos fechamentos, darei outro exemplo - desta vez em javascript. um dos problemas é o escopo e a assincronia inerente ao navegador. especialmente, se for window.setTimeout();(ou -interval). então, você passa uma função para setTimeout, mas não pode realmente fornecer nenhum parâmetro, porque fornecer parâmetros executa o código!

function getFunctionTextInASecond(value) {
    return function () {
        document.getElementsByName('body')[0].innerHTML = value; // "value" is the bound variable!
    }
}

var textToDisplay = prompt('text to show in a second', 'foo bar');

// this returns a function that sets the bodys innerHTML to the prompted value
var myFunction = getFunctionTextInASecond(textToDisplay);

window.setTimeout(myFunction, 1000);

myFunction retorna uma função com um tipo de parâmetro predefinido!

para ser sincero, eu gosto muito mais de php desde 5.3 e funções / fechamentos anônimos. os namespaces podem ser mais importantes, mas são muito menos sexy .

stefs
fonte
4
ohhhhhhhh, então o Uses é usado para passar variáveis extras , eu pensei que era uma tarefa engraçada. Obrigado!
21430 SeanDowney
38
seja cuidadoso. parâmetros são usados ​​para passar valores quando a função é CHAMADA. fechamentos são usados ​​para "passar" valores quando a função é DEFINIDA.
Stefs
Em Javascript, pode-se usar bind () para especificar argumentos iniciais para funções - consulte Funções parcialmente aplicadas .
você precisa saber é o seguinte
17

O Zupa fez um ótimo trabalho explicando fechamentos com 'use' e a diferença entre EarlyBinding e Referencing as variáveis ​​que são 'usadas'.

Então, eu fiz um exemplo de código com ligação antecipada de uma variável (= cópia):

<?php

$a = 1;
$b = 2;

$closureExampleEarlyBinding = function() use ($a, $b){
    $a++;
    $b++;
    echo "Inside \$closureExampleEarlyBinding() \$a = ".$a."<br />";
    echo "Inside \$closureExampleEarlyBinding() \$b = ".$b."<br />";    
};

echo "Before executing \$closureExampleEarlyBinding() \$a = ".$a."<br />";
echo "Before executing \$closureExampleEarlyBinding() \$b = ".$b."<br />";  

$closureExampleEarlyBinding();

echo "After executing \$closureExampleEarlyBinding() \$a = ".$a."<br />";
echo "After executing \$closureExampleEarlyBinding() \$b = ".$b."<br />";

/* this will output:
Before executing $closureExampleEarlyBinding() $a = 1
Before executing $closureExampleEarlyBinding() $b = 2
Inside $closureExampleEarlyBinding() $a = 2
Inside $closureExampleEarlyBinding() $b = 3
After executing $closureExampleEarlyBinding() $a = 1
After executing $closureExampleEarlyBinding() $b = 2
*/

?>

Exemplo com referência a uma variável (observe o caractere '&' antes da variável);

<?php

$a = 1;
$b = 2;

$closureExampleReferencing = function() use (&$a, &$b){
    $a++;
    $b++;
    echo "Inside \$closureExampleReferencing() \$a = ".$a."<br />";
    echo "Inside \$closureExampleReferencing() \$b = ".$b."<br />"; 
};

echo "Before executing \$closureExampleReferencing() \$a = ".$a."<br />";
echo "Before executing \$closureExampleReferencing() \$b = ".$b."<br />";   

$closureExampleReferencing();

echo "After executing \$closureExampleReferencing() \$a = ".$a."<br />";
echo "After executing \$closureExampleReferencing() \$b = ".$b."<br />";    

/* this will output:
Before executing $closureExampleReferencing() $a = 1
Before executing $closureExampleReferencing() $b = 2
Inside $closureExampleReferencing() $a = 2
Inside $closureExampleReferencing() $b = 3
After executing $closureExampleReferencing() $a = 2
After executing $closureExampleReferencing() $b = 3
*/

?>
joronimo
fonte
2

Até anos muito recentes, o PHP definiu seu interpretador AST e PHP isolou o analisador da parte da avaliação. Durante o tempo em que o fechamento é introduzido, o analisador do PHP é altamente associado à avaliação.

Portanto, quando o fechamento foi introduzido pela primeira vez no PHP, o intérprete não tem método para saber quais variáveis ​​serão usadas no fechamento, porque ele ainda não foi analisado. Portanto, o usuário precisa agradar o mecanismo do zend por importação explícita, fazendo o dever de casa que o zend deve fazer.

Essa é a chamada maneira simples em PHP.

Zhu Jinxuan
fonte