Dalam PHP, apa itu penutupan dan mengapa itu menggunakan pengidentifikasi "gunakan"?

407

Saya memeriksa beberapa PHP 5.3.0fitur dan menemukan beberapa kode di situs yang terlihat cukup lucu:

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

sebagai salah satu contoh pada fungsi anonim .

Adakah yang tahu tentang ini? Dokumentasi apa saja? Dan itu terlihat jahat, haruskah itu digunakan?

SeanDowney
sumber

Jawaban:

362

Beginilah cara PHP menyatakan penutupan . Ini sama sekali tidak jahat dan sebenarnya cukup kuat dan berguna.

Pada dasarnya apa artinya ini adalah Anda mengizinkan fungsi anonim untuk "menangkap" variabel lokal (dalam hal ini, $taxdan referensi ke $total) di luar cakupannya dan mempertahankan nilainya (atau dalam kasus $totalreferensi ke $totaldirinya sendiri) sebagai keadaan di dalam fungsi anonim itu sendiri.

Andrew Hare
sumber
1
Jadi HANYA digunakan untuk penutupan? Terima kasih atas penjelasan Anda, saya tidak tahu perbedaan antara fungsi anonim dan penutupan
SeanDowney
136
Kata usekunci juga digunakan untuk alias ruang nama . Sungguh menakjubkan bahwa, lebih dari 3 tahun setelah rilis PHP 5.3.0, sintaksinya function ... usemasih resmi tidak berdokumen, yang membuat penutupan menjadi fitur yang tidak berdokumen. Dokumen itu bahkan membingungkan fungsi dan penutupan anonim . Satu-satunya dokumentasi (beta dan tidak resmi) yang use ()dapat saya temukan di php.net adalah RFC untuk penutupan .
2
Jadi Kapan fungsi penutupan digunakan di PHP? Saya kira itu di PHP 5.3? Apakah ini didokumentasikan dalam manual PHP sekarang entah bagaimana?
rubo77
@Mtskine Yah, menurut dokter, fungsi anonim menggunakan kelas Penutupan
Manny Fleurmond
1
Sekarang usejuga digunakan untuk memasukkan traita class!
CJ Dennis
477

Jawaban yang lebih sederhana.

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

  1. Penutupan adalah fungsi yang ditugaskan ke variabel, sehingga Anda bisa menyebarkannya
  2. Penutupan adalah namespace terpisah, biasanya, Anda tidak dapat mengakses variabel yang ditentukan di luar namespace ini. Ada kata kunci penggunaan :
  3. Penggunaan memungkinkan Anda untuk mengakses (menggunakan) variabel berikutnya di dalam penutupan.
  4. Penggunaan mengikat awal. Itu berarti nilai variabel COPIED setelah DEFINING penutupan. Jadi memodifikasi$taxdi dalam penutupan tidak memiliki efek eksternal, kecuali itu adalah pointer, seperti objek.
  5. Anda dapat mengirimkan variabel sebagai pointer seperti dalam kasus &$total. Dengan cara ini, memodifikasi nilai $totalDOES MEMILIKI efek eksternal, nilai variabel asli berubah.
  6. Variabel yang didefinisikan di dalam penutupan tidak dapat diakses dari luar penutupan juga.
  7. Penutupan dan fungsi memiliki kecepatan yang sama. Ya, Anda dapat menggunakannya di seluruh skrip Anda.

Seperti yang ditunjukkan @Mytskine, mungkin penjelasan terbaik terbaik adalah RFC untuk penutupan . (Beri dia dukungan untuk ini.)

zupa
sumber
4
Kata kunci as dalam pernyataan penggunaan memberi saya kesalahan sintaks di php 5.5: $closure = function ($value) use ($localVar as $alias) { //stuff};Kesalahan yang diberikan adalah:Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
Kal Zekdor
1
@KalZekdor, dikonfirmasi dengan php5.3 juga, tampaknya sudah usang. Saya memperbarui jawabannya, terima kasih atas usaha Anda.
zupa
4
Saya akan menambahkan ke titik # 5 dengan cara ini, memodifikasi nilai pointer seperti &$totalmemiliki efek internal juga. Dengan kata lain, jika Anda mengubah nilai di $total luar penutupan setelah ditentukan, nilai baru hanya akan diteruskan jika itu adalah pointer.
billynoah
2
@ AndyD273 tujuannya memang sangat mirip, kecuali globalhanya memungkinkan akses ke namespace global sedangkan usememungkinkan mengakses variabel di namespace induk. Variabel global umumnya dianggap jahat. Mengakses ruang lingkup orang tua sering kali merupakan tujuan dari membuat penutupan. Ini bukan "jahat" karena cakupannya sangat terbatas. Bahasa lain seperti JS secara implisit menggunakan variabel lingkup induknya (sebagai penunjuk, bukan sebagai nilai yang disalin).
zupa
1
Baris ini menghentikan pencarian sia-sia dua jam sayaYou 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

Itu function () use () {}seperti penutupan untuk PHP.

Tanpa use, fungsi tidak dapat mengakses variabel lingkup induk

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

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

$f(); // hello

Nilai usevariabel berasal dari saat fungsi didefinisikan, bukan saat dipanggil

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

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

use referensi-variabel dengan &

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

$s = "how are you?";
$f(); // how are you?
Steely Wing
sumber
4
setelah membaca ini saya tidak menyesal sedikit lebih bergulir tetapi kira perlu edit kecil untuk kesalahan ketik di blok ketiga. Seharusnya ada $ s bukannya $ obj.
Pengguna stack
53

Penutupan itu indah! mereka memecahkan banyak masalah yang datang dengan fungsi anonim, dan membuat kode yang sangat elegan mungkin (setidaknya selama kita berbicara tentang php).

programmer javascript menggunakan closure sepanjang waktu, kadang-kadang bahkan tanpa menyadarinya, karena variabel terikat tidak didefinisikan secara eksplisit - itulah gunanya "digunakan" untuk di php.

ada contoh dunia nyata yang lebih baik daripada yang di atas. katakanlah Anda harus mengurutkan array multidimensi berdasarkan sub-nilai, tetapi kuncinya berubah.

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

peringatan: kode yang belum diuji (saya tidak memasang atm php5.3), tetapi seharusnya terlihat seperti itu.

ada satu kelemahan: banyak pengembang php mungkin sedikit tidak berdaya jika Anda menghadapi mereka dengan penutupan.

untuk memahami lebih banyak tentang penutupan, saya akan memberikan Anda contoh lain - kali ini dalam javascript. salah satu masalah adalah pelingkupan dan asinkronitas browser yang melekat. khususnya, jika menyangkut window.setTimeout();(atau -interval). jadi, Anda meneruskan fungsi ke setTimeout, tetapi Anda tidak dapat benar-benar memberikan parameter apa pun, karena memberikan parameter mengeksekusi kode!

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 mengembalikan fungsi dengan parameter yang sudah ditentukan sebelumnya!

jujur, saya lebih suka php lebih dari 5.3 dan fungsi / penutupan anonim. ruang nama mungkin lebih penting, tetapi mereka jauh kurang seksi .

STFs
sumber
4
ohhhhhhhh, jadi Penggunaannya digunakan untuk memberikan variabel tambahan , saya pikir itu adalah tugas yang lucu. Terima kasih!
SeanDowney
38
hati-hati. parameter digunakan untuk memberikan nilai saat fungsi DISEBUT. closure digunakan untuk "meneruskan" nilai ketika fungsi DITETAPKAN.
stefs
Dalam Javascript, seseorang dapat menggunakan bind () untuk menentukan argumen awal ke fungsi - lihat Fungsi yang diterapkan sebagian .
Sᴀᴍ Onᴇᴌᴀ
17

Zupa melakukan pekerjaan yang bagus menjelaskan penutupan dengan 'penggunaan' dan perbedaan antara EarlyBinding dan Referensi variabel yang 'digunakan'.

Jadi saya membuat contoh kode dengan pengikatan awal variabel (= menyalin):

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

?>

Contoh dengan mereferensikan variabel (perhatikan karakter '&' sebelum variabel);

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

Sampai beberapa tahun terakhir, PHP telah mendefinisikan AST dan interpreter PHPnya telah mengisolasi parser dari bagian evaluasi. Selama waktu penutupan diperkenalkan, parser PHP sangat digabungkan dengan evaluasi.

Oleh karena itu ketika penutupan pertama kali diperkenalkan ke PHP, penerjemah tidak memiliki metode untuk mengetahui variabel mana yang akan digunakan dalam penutupan, karena belum diurai. Jadi pengguna harus menyenangkan mesin zend dengan impor eksplisit, melakukan pekerjaan rumah yang harus dilakukan zend.

Inilah yang disebut cara sederhana dalam PHP.

Zhu Jinxuan
sumber