ใน PHP การปิดคืออะไรและเหตุใดจึงใช้ตัวระบุ“ ใช้”


407

ฉันกำลังตรวจสอบPHP 5.3.0คุณสมบัติบางอย่างและพบรหัสบางส่วนในเว็บไซต์ที่ดูค่อนข้างตลก:

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

เป็นหนึ่งในตัวอย่างในฟังก์ชั่นที่ไม่ระบุชื่อ

มีใครรู้เรื่องนี้บ้างไหม? เอกสารอะไรบ้าง? และมันดูชั่วร้ายมันควรจะถูกนำมาใช้?

คำตอบ:


362

นี่คือวิธีที่ PHP แสดงการปิดตัว นี่ไม่ใช่ความชั่วร้ายเลยและที่จริงแล้วมันค่อนข้างทรงพลังและมีประโยชน์

โดยทั่วไปสิ่งนี้หมายความว่าคุณอนุญาตให้ฟังก์ชั่นที่ไม่ระบุชื่อ "จับ" ตัวแปรท้องถิ่น (ในกรณีนี้$taxและการอ้างอิงถึง$total) นอกขอบเขตและรักษาคุณค่าของมัน (หรือในกรณีของ$totalการอ้างอิงถึง$totalตัวเอง) เป็นสถานะภายใน ฟังก์ชั่นที่ไม่ระบุชื่อตัวเอง


1
ดังนั้นมันจึงใช้สำหรับการปิดเท่านั้น ขอขอบคุณที่คุณอธิบายผมไม่ทราบความแตกต่างระหว่างฟังก์ชั่นที่ไม่ระบุชื่อและปิด
SeanDowney

136
useคำหลักที่ใช้สำหรับnamespaces aliasing เป็นเรื่องที่น่าประหลาดใจว่าหลังจากเปิดตัว PHP 5.3.0 มานานกว่า 3 ปีไวยากรณ์function ... useยังคงไม่มีเอกสารอย่างเป็นทางการซึ่งทำให้การปิดฟีเจอร์นั้นไม่มีเอกสาร เอกสารแม้สับสนฟังก์ชั่นที่ไม่ระบุชื่อและฝาปิด เท่านั้น (เบต้าและไม่เป็นทางการ) เอกสารเกี่ยวกับuse ()ฉันจะได้พบใน php.net เป็นRFC สำหรับการปิด

2
ดังนั้นเมื่อใดที่มีการปิดการใช้ฟังก์ชั่นใน PHP ฉันเดาว่ามันเป็น PHP 5.3 ใช่ไหม ตอนนี้มีการบันทึกไว้ในคู่มือ PHP หรือไม่?
rubo77

@ Mytskine ดีตามเอกสารฟังก์ชั่นที่ไม่ระบุชื่อใช้คลาสปิด
แมนนี่เฟลอร์มอนด์

1
ตอนนี้useยังใช้สำหรับการรวมtraitเป็นclass!
CJ Dennis

477

คำตอบที่ง่ายขึ้น

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

  1. การปิดเป็นฟังก์ชันที่กำหนดให้กับตัวแปรดังนั้นคุณสามารถผ่านมันไปได้
  2. การปิดเป็นเนมสเปซแยกต่างหากโดยปกติแล้วคุณไม่สามารถเข้าถึงตัวแปรที่กำหนดไว้ภายนอกเนมสเปซนี้ได้ มีการใช้คำสำคัญ:
  3. ใช้ช่วยให้คุณสามารถเข้าถึง (ใช้) ตัวแปรที่ประสบความสำเร็จภายในการปิด
  4. การใช้เป็นการผูกต้น นั่นหมายความว่าค่าตัวแปรจะถูกคัดลอกเมื่อทำการกำหนดค่าปิด ดังนั้นการแก้ไข$taxภายในการปิดจึงไม่มีผลกระทบภายนอกเว้นแต่จะเป็นตัวชี้เช่นเดียวกับวัตถุ
  5. &$totalคุณสามารถส่งผ่านในตัวแปรที่เป็นตัวชี้ชอบในกรณีของ ด้วยวิธีนี้การปรับเปลี่ยนค่าของ$totalDOES มีผลกระทบภายนอกการเปลี่ยนแปลงค่าของตัวแปรดั้งเดิม
  6. ตัวแปรที่กำหนดภายในการปิดไม่สามารถเข้าถึงได้จากภายนอกการปิดเช่นกัน
  7. การปิดและฟังก์ชั่นมีความเร็วเท่ากัน ใช่คุณสามารถใช้มันกับสคริปต์ของคุณได้

ในฐานะที่เป็น @Mytskine ชี้ให้เห็นสิ่งที่ดีที่สุดคำอธิบายในเชิงลึกน่าจะเป็นRFC สำหรับการปิด (โหวตให้เขาเพื่อสิ่งนี้)


4
คำหลักที่เป็นคำสั่งในการใช้งานคือให้ฉันข้อผิดพลาดทางไวยากรณ์ใน php 5.5: $closure = function ($value) use ($localVar as $alias) { //stuff};ข้อผิดพลาดที่ได้รับคือ:Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
Kal Zekdor

1
@KalZekdor ยืนยันด้วย php5.3 เช่นกันดูเหมือนว่าเลิกใช้แล้ว ฉันอัปเดตคำตอบขอบคุณสำหรับความพยายามของคุณ
zupa

4
ฉันจะเพิ่มไปยังจุด # 5 ด้วยวิธีนี้การปรับเปลี่ยนค่าที่ตัวชี้เช่น&$totalมีผลภายในเช่นกัน กล่าวอีกนัยหนึ่งถ้าคุณเปลี่ยนค่า$total ภายนอกของการปิดหลังจากที่กำหนดไว้ค่าใหม่จะถูกส่งผ่านถ้ามันเป็นตัวชี้
billynoah

2
@ AndyD273 จุดประสงค์นั้นคล้ายกันมากยกเว้นglobalอนุญาตให้เข้าถึงเนมสเปซส่วนกลางเท่านั้นในขณะที่useอนุญาตให้เข้าถึงตัวแปรในเนมสเปซหลัก ตัวแปรทั่วโลกโดยทั่วไปถือว่าชั่วร้าย การเข้าถึงขอบเขตพาเรนต์มักเป็นวัตถุประสงค์ในการสร้างการปิด ไม่ใช่ "ความชั่วร้าย" เนื่องจากขอบเขตมี จำกัด มาก ภาษาอื่นเช่น JS โดยปริยายใช้ตัวแปรของขอบเขตพาเรนต์ (เป็นตัวชี้ไม่ใช่ตามค่าที่คัดลอก)
zupa

1
บรรทัดนี้หยุดการค้นหาสองชั่วโมงอย่างไร้สาระ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

function () use () {}เป็นเหมือนการปิดสำหรับ PHP

หากไม่มีuseฟังก์ชันจะไม่สามารถเข้าถึงตัวแปรขอบเขตพาเรนต์ได้

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

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

$f(); // hello

useค่าของตัวแปรจากเมื่อฟังก์ชั่นที่มีการกำหนดไว้ไม่ได้เมื่อเรียกว่า

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

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

use อ้างอิงตัวแปรด้วย &

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

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

4
หลังจากอ่านข้อความนี้ฉันไม่เสียใจที่จะต้องเลื่อนอีกต่อไป แต่คาดว่าจะต้องมีการแก้ไขเล็กน้อยสำหรับการพิมพ์ผิดในบล็อกที่สาม ควรมี $ s แทน $ obj
ผู้ใช้งานสแต็

53

ปิดมีความสวยงาม! พวกเขาแก้ปัญหาจำนวนมากที่มาพร้อมกับฟังก์ชั่นที่ไม่ระบุชื่อและทำให้โค้ดที่สง่างามเป็นไปได้ (อย่างน้อยตราบใดที่เราพูดถึง php)

ผู้เขียนโปรแกรมจาวาสคริปต์ใช้การปิดตลอดเวลาบางครั้งถึงแม้จะไม่รู้เพราะตัวแปรที่ถูกผูกไว้ไม่ได้กำหนดไว้อย่างชัดเจนนั่นคือสิ่งที่ "ใช้" สำหรับ php

มีตัวอย่างในโลกแห่งความเป็นจริงที่ดีกว่าตัวอย่างข้างต้น สมมติว่าคุณต้องจัดเรียงอาร์เรย์หลายมิติด้วยค่าย่อย แต่การเปลี่ยนแปลงที่สำคัญ

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

คำเตือน: รหัสที่ยังไม่ทดลอง (ฉันไม่ได้ติดตั้ง php5.3 atm) แต่ควรมีลักษณะเช่นนั้น

มีข้อเสียอย่างหนึ่งคือผู้พัฒนา php จำนวนมากอาจไม่มีประโยชน์อะไรถ้าคุณเผชิญหน้ากับการปิด

เพื่อให้เข้าใจถึงการปิดที่ดียิ่งขึ้นฉันจะให้อีกตัวอย่างหนึ่ง - คราวนี้เป็นจาวาสคริปต์ หนึ่งในปัญหาคือการกำหนดขอบเขตและเบราว์เซอร์แบบอะซิงโครนัสโดยธรรมชาติ โดยเฉพาะอย่างยิ่งถ้ามันมาถึงwindow.setTimeout();(หรือ - ช่วง) ดังนั้นคุณส่งผ่านฟังก์ชั่นเพื่อ setTimeout แต่คุณไม่สามารถให้พารามิเตอร์ใด ๆ ได้จริง ๆ เพราะการให้พารามิเตอร์เรียกใช้งานโค้ด!

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 คืนค่าฟังก์ชันด้วยพารามิเตอร์ที่กำหนดไว้ล่วงหน้า!

พูดตามตรงฉันชอบ php มากขึ้นตั้งแต่ 5.3 และฟังก์ชั่น / ปิดที่ไม่ระบุชื่อ namespaces อาจมีความสำคัญมากขึ้นแต่พวกเขากำลังมากน้อยเซ็กซี่


4
ohhhhhhhh ดังนั้นการใช้จะถูกใช้เพื่อส่งผ่านตัวแปรพิเศษฉันคิดว่ามันเป็นงานที่ตลก ขอบคุณ!
SeanDowney

38
ระวัง. พารามิเตอร์ถูกใช้เพื่อส่งผ่านค่าเมื่อฟังก์ชันถูกเรียก การปิดจะใช้ในการ "ผ่าน" ค่าเมื่อฟังก์ชั่นได้รับการกำหนด
stefs

ใน Javascript หนึ่งสามารถใช้ผูก ()เพื่อระบุข้อโต้แย้งเริ่มต้นไปยังฟังก์ชั่น - ดูฟังก์ชั่นที่ใช้บางส่วน
SᴀᴍOnᴇᴌᴀ

17

Zupa อธิบายถึงการปิดการทำงานอย่างยอดเยี่ยมด้วย 'การใช้งาน' และความแตกต่างระหว่างการเชื่อมโยงล่วงหน้าและการอ้างอิงตัวแปรที่ใช้ '

ดังนั้นฉันทำตัวอย่างรหัสด้วยการผูกต้นของตัวแปร (= คัดลอก):

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

?>

ตัวอย่างที่มีการอ้างอิงตัวแปร (สังเกตเห็นอักขระ '&' ก่อนตัวแปร);

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

จนกระทั่งเมื่อไม่นานมานี้ PHP ได้กำหนดล่าม AST และ PHP แยกตัวแยกวิเคราะห์ออกจากส่วนการประเมิน ในช่วงเวลาที่มีการปิดตัวแยกวิเคราะห์ PHP ของคู่กับการประเมินผล

ดังนั้นเมื่อมีการแนะนำ PHP เป็นครั้งแรกล่ามจึงไม่มีวิธีรู้ว่าตัวแปรใดที่จะใช้ในการปิดเพราะยังไม่ได้แยกวิเคราะห์ ดังนั้นผู้ใช้จะต้องพอใจกับเครื่องมือ zend โดยการนำเข้าอย่างชัดเจนทำการบ้านที่ zend ควรทำ

นี่เป็นวิธีที่ง่ายใน PHP

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.