Was ist in PHP ein Abschluss und warum wird der Bezeichner "use" verwendet?

407

Ich habe einige PHP 5.3.0Funktionen überprüft und bin auf der Website auf Code gestoßen, der ziemlich lustig aussieht:

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);
}

als eines der Beispiele für anonyme Funktionen .

Weiß jemand davon? Irgendeine Dokumentation? Und es sieht böse aus, sollte es jemals benutzt werden?

SeanDowney
quelle

Antworten:

362

So drückt PHP einen Abschluss aus . Das ist überhaupt nicht böse und in der Tat ist es ziemlich mächtig und nützlich.

Grundsätzlich bedeutet dies, dass Sie der anonymen Funktion erlauben, lokale Variablen (in diesem Fall $taxund einen Verweis auf $total) außerhalb ihres Bereichs zu "erfassen" und ihre Werte (oder im Fall des $totalVerweises auf sich $totalselbst) als Status innerhalb zu behalten die anonyme Funktion selbst.

Andrew Hare
quelle
1
Also wird es NUR für Verschlüsse verwendet? Vielen Dank für
Ihre
136
Das useSchlüsselwort wird auch zum Aliasing von Namespaces verwendet . Es ist erstaunlich, dass die Syntax mehr als 3 Jahre nach der Veröffentlichung von PHP 5.3.0 function ... useimmer noch offiziell undokumentiert ist, was Schließungen zu einer undokumentierten Funktion macht. Das Dokument verwirrt sogar anonyme Funktionen und Schließungen . Die einzige (Beta und inoffizielle) Dokumentation, die use ()ich auf php.net finden konnte, war der RFC für Schließungen .
2
So Wann wurde Schließungen Funktion Verwendung in PHP implementiert? Ich denke dann war es in PHP 5.3? Ist es jetzt irgendwie im PHP-Handbuch dokumentiert?
rubo77
@ Mytskine Nun, laut dem Dokument verwenden anonyme Funktionen die Closure-Klasse
Manny Fleurmond
1
Jetzt usewird auch zum Einfügen eines traitin ein class!
CJ Dennis
477

Eine einfachere Antwort.

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

  1. Der Abschluss ist eine Funktion, die einer Variablen zugewiesen ist, sodass Sie sie weitergeben können
  2. Ein Abschluss ist ein separater Namespace. Normalerweise können Sie nicht auf Variablen zugreifen, die außerhalb dieses Namespace definiert sind. Es kommt das Schlüsselwort use :
  3. Mit use können Sie auf die nachfolgenden Variablen innerhalb des Abschlusses zugreifen (diese verwenden).
  4. Verwendung ist frühe Bindung. Das heißt, die variablen Werte werden beim Definieren des Abschlusses kopiert. Das Ändern$taxinnerhalb des Verschlusses hat also keine externen Auswirkungen, es sei denn, es ist ein Zeiger, wie es ein Objekt ist.
  5. Sie können Variablen als Zeiger übergeben, wie im Fall von &$total. Auf diese Weise $totaländert sich der Wert der ursprünglichen Variablen, wenn der Wert von DOES HAVE einen externen Effekt ändert.
  6. Innerhalb des Verschlusses definierte Variablen sind auch außerhalb des Verschlusses nicht zugänglich.
  7. Verschlüsse und Funktionen haben die gleiche Geschwindigkeit. Ja, Sie können sie überall in Ihren Skripten verwenden.

Wie @Mytskine hervorhob , ist der RFC für Schließungen wahrscheinlich die beste ausführliche Erklärung . (Stimmen Sie ihn dafür ab.)

zupa
quelle
4
Das as-Schlüsselwort in der use-Anweisung gibt mir einen Syntaxfehler in PHP 5.5: Der angegebene $closure = function ($value) use ($localVar as $alias) { //stuff};Fehler ist:Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
Kal Zekdor
1
@KalZekdor, ebenfalls mit php5.3 bestätigt, scheint veraltet zu sein. Ich habe die Antwort aktualisiert, danke für Ihre Bemühungen.
zupa
4
Ich würde zu Punkt 5 hinzufügen, dass auf diese Weise das Ändern des Werts eines Zeigers &$totalauch einen internen Effekt hat. Mit anderen Worten, wenn Sie den Wert $total außerhalb des Abschlusses nach seiner Definition ändern , wird der neue Wert nur übergeben, wenn es sich um einen Zeiger handelt.
Billynoah
2
@ AndyD273 Der Zweck ist in der Tat sehr ähnlich, außer dass globalnur der Zugriff auf den globalen Namespace möglich ist, während useder Zugriff auf Variablen im übergeordneten Namespace möglich ist. Globale Variablen werden im Allgemeinen als böse angesehen. Der Zugriff auf den übergeordneten Bereich ist häufig der eigentliche Zweck der Erstellung eines Abschlusses. Es ist nicht "böse", da sein Umfang sehr begrenzt ist. Andere Sprachen wie JS implizit verwenden die Variablen des übergeordneten Bereichs (als Zeiger, nicht als Wert kopiert).
zupa
1
Diese Zeile stoppte meine zwei Stunden vergebliche SucheYou 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

Das function () use () {}ist wie ein Abschluss für PHP.

Ohne usekann die Funktion nicht auf die übergeordnete Bereichsvariable zugreifen

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

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

$f(); // hello

Der useWert der Variablen stammt vom Zeitpunkt der Definition der Funktion und nicht vom Zeitpunkt des Aufrufs

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

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

use variable Referenz mit &

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

$s = "how are you?";
$f(); // how are you?
Steely Wing
quelle
4
Nachdem ich dies gelesen habe, bereue ich es nicht, ein bisschen mehr gescrollt zu haben, aber ich denke, ich brauche eine kleine Bearbeitung für Tippfehler im dritten Block. Es sollte $ s anstelle von $ obj geben.
Stapelbenutzer
53

Verschlüsse sind wunderschön! Sie lösen viele Probleme, die mit anonymen Funktionen einhergehen, und ermöglichen wirklich eleganten Code (zumindest solange wir über PHP sprechen).

Javascript-Programmierer verwenden ständig Closures, manchmal sogar ohne es zu wissen, da gebundene Variablen nicht explizit definiert sind - dafür ist "use" in PHP gedacht.

Es gibt bessere Beispiele aus der Praxis als die oben genannten. Angenommen, Sie müssen ein mehrdimensionales Array nach einem Unterwert sortieren, aber der Schlüssel ändert sich.

<?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);
?>

Warnung: ungetesteter Code (ich habe php5.3 atm nicht installiert), aber es sollte ungefähr so ​​aussehen.

Es gibt einen Nachteil: Viele PHP-Entwickler sind möglicherweise etwas hilflos, wenn Sie sie mit Schließungen konfrontieren.

Um die Schönheit von Verschlüssen besser zu verstehen, gebe ich Ihnen ein weiteres Beispiel - diesmal in Javascript. Eines der Probleme ist das Scoping und die dem Browser innewohnende Asynchronität. vor allem, wenn es um window.setTimeout();(oder -intervall) geht. Sie übergeben also eine Funktion an setTimeout, können jedoch keine Parameter angeben, da die Bereitstellung von Parametern den Code ausführt!

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 gibt eine Funktion mit einer Art vordefiniertem Parameter zurück!

um ehrlich zu sein, ich mag PHP seit 5.3 und anonymen Funktionen / Schließungen viel mehr. Namespaces mögen wichtiger sein, aber sie sind viel weniger sexy .

Stefs
quelle
4
ohhhhhhhh, also wird die Verwendung verwendet, um zusätzliche Variablen zu übergeben. Ich dachte, es wäre eine lustige Aufgabe. Vielen Dank!
SeanDowney
38
Achtung. Parameter werden verwendet, um Werte zu übergeben, wenn die Funktion aufgerufen wird. Verschlüsse werden verwendet, um Werte zu "übergeben", wenn die Funktion DEFINIERT ist.
Stefs
In Javascript kann man bind () verwenden , um Anfangsargumente für Funktionen anzugeben - siehe Teilweise angewendete Funktionen .
Am 18.
17

Zupa hat großartige Arbeit geleistet, um Abschlüsse mit 'use' und den Unterschied zwischen EarlyBinding und Referencing der Variablen, die 'used' sind, zu erklären.

Also habe ich ein Codebeispiel mit frühem Binden einer Variablen gemacht (= Kopieren):

<?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
*/

?>

Beispiel mit Referenzierung einer Variablen (beachten Sie das Zeichen '&' vor der Variablen);

<?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
quelle
2

Bis in die letzten Jahre hat PHP seinen AST definiert und der PHP-Interpreter hat den Parser vom Evaluierungsteil isoliert. Während der Zeit, in der der Abschluss eingeführt wird, ist der Parser von PHP stark an die Auswertung gekoppelt.

Daher hat der Interpreter bei der ersten Einführung des Abschlusses in PHP keine Methode, um zu wissen, welche Variablen im Abschluss verwendet werden, da er noch nicht analysiert wurde. Daher muss der Benutzer die Zend-Engine durch expliziten Import erfreuen und die Hausaufgaben machen, die Zend machen sollte.

Dies ist der sogenannte einfache Weg in PHP.

Zhu Jinxuan
quelle