Trong PHP, đóng cửa là gì và tại sao nó lại sử dụng định danh sử dụng của Wap?


407

Tôi đang kiểm tra một số PHP 5.3.0tính năng và chạy qua một số mã trên trang web trông khá buồn cười:

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

là một trong những ví dụ về các chức năng ẩn danh .

Có ai biết về điều này? Bất kỳ tài liệu? Và nó trông có vẻ xấu xa, nó có nên được sử dụng?

Câu trả lời:


362

Đây là cách PHP diễn tả một bao đóng . Đây không phải là xấu xa và thực tế nó khá mạnh mẽ và hữu ích.

Về cơ bản điều này có nghĩa là bạn đang cho phép hàm ẩn danh "nắm bắt" các biến cục bộ (trong trường hợp này $taxvà tham chiếu đến $total) bên ngoài phạm vi của nó và bảo toàn các giá trị của chúng (hoặc trong trường hợp $totaltham chiếu đến $totalchính nó) như trạng thái bên trong các chức năng ẩn danh chính nó.


1
Vì vậy, nó CHỈ được sử dụng cho đóng cửa? Cảm ơn bạn đã giải thích, tôi không biết sự khác biệt giữa chức năng ẩn danh và đóng cửa
SeanDowney

136
Các usetừ khóa cũng được sử dụng cho không gian tên răng cưa . Thật đáng kinh ngạc rằng, hơn 3 năm sau khi phát hành PHP 5.3.0, cú pháp function ... usevẫn chính thức không có giấy tờ, điều này làm cho việc đóng một tính năng không có giấy tờ. Các tài liệu thậm chí nhầm lẫn các chức năng ẩn danh và đóng cửa . Tài liệu duy nhất (beta và không chính thức) trên use ()tôi có thể tìm thấy trên php.net là RFC cho các lần đóng cửa .

2
Vì vậy, khi nào hàm sử dụng đóng được thực hiện trong PHP? Tôi đoán sau đó là trong PHP 5.3? Bây giờ nó được ghi lại trong tài liệu hướng dẫn PHP?
rubo77

@Mytskine Vâng, theo tài liệu, các hàm ẩn danh sử dụng lớp Đóng
Manny Fleurmond

1
Bây giờ usecũng được sử dụng để bao gồm traita class!
CJ Dennis

477

Một câu trả lời đơn giản hơn.

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

  1. Hàm đóng là một hàm được gán cho một biến, vì vậy bạn có thể chuyển nó xung quanh
  2. Một bao đóng là một không gian tên riêng biệt, thông thường, bạn không thể truy cập các biến được xác định bên ngoài không gian tên này. Có từ khóa sử dụng :
  3. sử dụng cho phép bạn truy cập (sử dụng) các biến tiếp theo bên trong bao đóng.
  4. sử dụng là ràng buộc sớm. Điều đó có nghĩa là các giá trị biến được SAO CHÉP khi xác định đóng. Vì vậy, sửa đổi$taxbên trong bao đóng không có hiệu ứng bên ngoài, trừ khi nó là một con trỏ, giống như một đối tượng.
  5. Bạn có thể truyền vào các biến như con trỏ như trong trường hợp &$total. Bằng cách này, sửa đổi giá trị của $totalDOES có hiệu ứng bên ngoài, giá trị của biến ban đầu sẽ thay đổi.
  6. Các biến được định nghĩa bên trong bao đóng cũng không thể truy cập được từ bên ngoài bao đóng.
  7. Đóng cửa và chức năng có cùng tốc độ. Có, bạn có thể sử dụng chúng trên tất cả các tập lệnh của bạn.

Như @Mytskine đã chỉ ra có lẽ lời giải thích sâu sắc nhất là RFC cho việc đóng cửa . (Upvote anh ấy cho điều này.)


4
Từ khóa as trong câu lệnh use đang cho tôi một lỗi cú pháp trong php 5.5: $closure = function ($value) use ($localVar as $alias) { //stuff};Lỗi được đưa ra là:Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
Kal Zekdor

1
@KalZekdor, cũng được xác nhận với php5.3, dường như không được dùng nữa. Tôi cập nhật câu trả lời, cảm ơn bạn đã nỗ lực.
zupa

4
Tôi sẽ thêm vào điểm số 5 theo cách này, sửa đổi giá trị mà một con trỏ giống như &$totalcó hiệu ứng bên trong. Nói cách khác, nếu bạn thay đổi giá trị $total bên ngoài của bao đóng sau khi được xác định, giá trị mới chỉ được truyền vào nếu đó là một con trỏ.
billynoah

2
@ AndyD273 mục đích thực sự rất giống nhau, ngoại trừ globalchỉ cho phép truy cập vào không gian tên toàn cầu trong khi usecho phép truy cập các biến trong không gian tên cha. Biến toàn cầu thường được coi là xấu xa. Truy cập phạm vi cha mẹ thường là mục đích của việc tạo một bao đóng. Nó không phải là "xấu xa" vì phạm vi của nó rất hạn chế. Các ngôn ngữ khác như JS sử dụng ngầm định các biến của phạm vi cha (như một con trỏ, không phải là giá trị được sao chép).
zupa

1
Dòng này đã dừng hai giờ tìm kiếm vô ích của tôiYou 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

function () use () {}giống như đóng cửa cho PHP.

Không có use, hàm không thể truy cập biến phạm vi cha

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

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

$f(); // hello

Các usegiá trị của biến là từ khi chức năng được xác định, không phải khi gọi

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

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

use thay đổi theo tham chiếu với &

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

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

4
Sau khi đọc xong, tôi không hối hận thêm một chút khi cuộn nhưng đoán cần một chỉnh sửa nhỏ cho lỗi đánh máy trong khối thứ ba. Nên có $ s thay vì $ obj.
Xếp chồng người dùng

53

Đóng cửa thật đẹp! họ giải quyết rất nhiều vấn đề đi kèm với các hàm ẩn danh và tạo ra mã thực sự thanh lịch (ít nhất là miễn là chúng ta nói về php).

Các lập trình viên javascript sử dụng các bao đóng mọi lúc, đôi khi thậm chí không biết điều đó, bởi vì các biến bị ràng buộc không được xác định rõ ràng - đó là những gì "sử dụng" dành cho php.

có những ví dụ thực tế tốt hơn so với ví dụ trên. giả sử bạn phải sắp xếp một mảng nhiều chiều theo một giá trị phụ, nhưng khóa thay đổi.

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

cảnh báo: mã chưa được kiểm tra (tôi không cài đặt atm php5.3), nhưng nó sẽ trông giống như thế.

Có một nhược điểm: rất nhiều nhà phát triển php có thể hơi bất lực nếu bạn đối đầu với họ bằng việc đóng cửa.

để hiểu rõ hơn về cách đóng cửa nhiều hơn, tôi sẽ cho bạn một ví dụ khác - lần này là trong javascript. một trong những vấn đề là phạm vi và sự không đồng bộ vốn có của trình duyệt. đặc biệt, nếu nói đến window.setTimeout();(hoặc -interval). vì vậy, bạn chuyển một hàm cho setTimeout, nhưng bạn thực sự không thể đưa ra bất kỳ tham số nào, bởi vì việc cung cấp các tham số thực thi mã!

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 trả về một hàm với một loại tham số được xác định trước!

Thành thật mà nói, tôi thích php hơn rất nhiều kể từ 5.3 và các hàm / đóng ẩn danh. không gian tên có thể quan trọng hơn, nhưng chúng ít gợi cảm hơn .


4
ohhhhhhhh, vì vậy các Sử dụng được sử dụng để vượt qua các biến phụ , tôi nghĩ đó là một nhiệm vụ buồn cười. Cảm ơn!
SeanDowney

38
hãy cẩn thận. tham số được sử dụng để truyền giá trị khi hàm được GỌI. bao đóng được sử dụng để "vượt qua" các giá trị khi hàm được xác định.
stefs

Trong Javascript, người ta có thể sử dụng bind () để chỉ định các đối số ban đầu cho các hàm - xem Các hàm được áp dụng một phần .
Sᴀᴍ Onᴇᴌᴀ

17

Zupa đã làm rất tốt khi giải thích các lần đóng với 'sử dụng' và sự khác biệt giữa EarlyBinding và Tham chiếu các biến được 'sử dụng'.

Vì vậy, tôi đã tạo một ví dụ mã với ràng buộc sớm của một biến (= sao chép):

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

?>

Ví dụ với tham chiếu một biến (chú ý ký tự '&' trước biến);

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

?>

2

Cho đến những năm gần đây, PHP đã định nghĩa trình thông dịch AST và PHP của nó đã tách trình phân tích cú pháp khỏi phần đánh giá. Trong thời gian đóng cửa được giới thiệu, trình phân tích cú pháp của PHP được kết hợp chặt chẽ với đánh giá.

Do đó, khi lần đóng đầu tiên được giới thiệu với PHP, trình thông dịch không có phương thức để biết biến nào sẽ được sử dụng trong bao đóng, bởi vì nó chưa được phân tích cú pháp. Vì vậy, người dùng phải làm hài lòng công cụ zend bằng cách nhập rõ ràng, làm bài tập về nhà mà zend nên làm.

Đây là cách gọi đơn giản trong PHP.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.