PHP: Type hinting - ความแตกต่างระหว่าง "Closure" และ "Callable"


128

ฉันสังเกตเห็นว่าฉันสามารถใช้คำใบ้ประเภทClosureหรือCallableคำใบ้ได้หากเราคาดว่าจะมีการเรียกใช้ฟังก์ชันการโทรกลับ ตัวอย่างเช่น:

function callFunc1(Closure $closure) {
    $closure();
}

function callFunc2(Callable $callback) {
    $callback();
}

$function = function() {
    echo 'Hello, World!';
};

callFunc1($function); // Hello, World!
callFunc2($function); // Hello, World!

คำถาม:

ความแตกต่างที่นี่คืออะไร? กล่าวอีกนัยหนึ่งว่าควรใช้Closureเมื่อใดและควรใช้เมื่อใดCallableหรือใช้เพื่อวัตถุประสงค์เดียวกัน?

คำตอบ:


174

ความแตกต่างคือClosureต้องเป็นฟังก์ชันที่ไม่ระบุชื่อโดยที่callableสามารถเป็นฟังก์ชันปกติได้

คุณสามารถดู / ทดสอบสิ่งนี้ด้วยตัวอย่างด้านล่างและคุณจะเห็นข้อผิดพลาดสำหรับข้อผิดพลาดแรก:

function callFunc1(Closure $closure) {
    $closure();
}

function callFunc2(Callable $callback) {
    $callback();
}

function xy() {
    echo 'Hello, World!';
}

callFunc1("xy"); // Catchable fatal error: Argument 1 passed to callFunc1() must be an instance of Closure, string given
callFunc2("xy"); // Hello, World!

ดังนั้นหากคุณต้องการเพียงพิมพ์ hint anonymous function ให้ใช้: Closureและถ้าคุณต้องการอนุญาตให้ใช้ function ปกติcallableเป็น type hint


5
นอกจากนี้คุณยังสามารถใช้วิธีการเรียนที่มี callable โดยผ่านอาร์เรย์เช่น["Foo", "bar"]สำหรับFoo::barหรือสำหรับ[$foo, "bar"] $foo->bar
Andrea

17
offtopic แต่ที่เกี่ยวข้อง: ตั้งแต่ PHP 7.1 callFunc1(Closure::fromCallable("xy"))ขณะนี้คุณสามารถแปลงไปปิด: wiki.php.net/rfc/closurefromcallable
nevvermind

ฉันยังไม่เห็นว่าทำไมฉันถึงต้องการเรียกใช้ฟังก์ชันนิรนามเท่านั้น ถ้าฉันแชร์รหัสฉันไม่ควรสนใจว่าฟังก์ชันมาจากไหน ฉันคิดว่าหนึ่งในนิสัยใจคอของ PHP ควรลบออกอย่างใดอย่างหนึ่งเพื่อหลีกเลี่ยงความสับสน แต่ฉันชอบวิธีการClosure+ Closure::fromCallableเพราะสตริงหรืออาร์เรย์เป็นcallableเรื่องแปลกมาโดยตลอด
Robo Robok

2
@RoboRobok เหตุผลหนึ่งที่ต้องการเฉพาะClosure(ฟังก์ชันที่ไม่ระบุตัวตน) ตรงข้ามcallableคือเพื่อป้องกันการเข้าถึงเกินขอบเขตของฟังก์ชันที่เรียกว่า ตัวอย่างเช่นเมื่อคุณมีข้อมูลที่private methodคุณไม่ต้องการให้บุคคลอื่นเข้าถึงไฟล์callable. ดู: 3v4l.org/7TSf2
fyrye

59

แตกต่างที่สำคัญระหว่างพวกเขาคือการที่closureเป็นระดับและประเภทcallable

callableประเภทยอมรับอะไรที่สามารถเรียกว่า :

var_dump(
  is_callable('functionName'),
  is_callable([$myClass, 'methodName']),
  is_callable(function(){})
);

โดยที่ a closureจะยอมรับเฉพาะฟังก์ชันที่ไม่ระบุตัวตน โปรดทราบว่าใน PHP เวอร์ชัน 7.1 คุณสามารถแปลงฟังก์ชันเป็นการปิดได้ดังนี้: Closure::fromCallable('functionName').


ตัวอย่าง:

namespace foo{
  class bar{
    private $val = 10;

    function myCallable(callable $cb){$cb()}
    function myClosure(\Closure $cb){$cb()} // type hint must refer to global namespace
  }

  function func(){}
  $cb = function(){};
  $fb = new bar;

  $fb->myCallable(function(){});
  $fb->myCallable($cb);
  $fb->myCallable('func');

  $fb->myClosure(function(){});
  $fb->myClosure($cb);
  $fb->myClosure(\Closure::fromCallable('func'));
  $fb->myClosure('func'); # TypeError
}

ทำไมต้องใช้closureover callable?

เข้มงวดเพราะclosureเป็นวัตถุที่มีวิธีการอื่น ๆ บาง: call(), และbind() bindto()ช่วยให้คุณสามารถใช้ฟังก์ชันที่ประกาศนอกคลาสและดำเนินการได้ราวกับว่าอยู่ในคลาส

$inject = function($i){return $this->val * $i;};
$cb1 = Closure::bind($inject, $fb);
$cb2 = $inject->bindTo($fb);

echo $cb1->call($fb, 2); // 20
echo $cb2(3);            // 30

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

if($cb instanceof \Closure){}

การตรวจสอบทุกครั้งนี้ไม่มีจุดหมาย ดังนั้นหากคุณต้องการใช้วิธีการเหล่านั้นให้ระบุว่าอาร์กิวเมนต์เป็นclosure. callbackมิฉะนั้นเพียงแค่ใช้ตามปกติ ทางนี้; เกิดข้อผิดพลาดขึ้นในการเรียกใช้ฟังก์ชันแทนที่จะเป็นรหัสของคุณทำให้ง่ายต่อการวินิจฉัย

สังเกตด้านบน:closureชั้นไม่สามารถขยายได้เป็นของสุดท้าย


1
คุณสามารถใช้การโทรซ้ำในขอบเขตอื่นได้เช่นกัน
Bimal Poudel

ซึ่งหมายความว่าคุณไม่จำเป็นต้องมีคุณสมบัติcallableในเนมสเปซใด ๆ
Jeff Puckett

0

เป็นมูลค่าการกล่าวขวัญว่าสิ่งนี้ใช้ไม่ได้กับ PHP เวอร์ชัน 5.3.21 ถึง 5.3.29

ในเวอร์ชันเหล่านั้นคุณจะได้รับผลลัพธ์เช่น:

สวัสดีชาวโลก! ข้อผิดพลาดร้ายแรงที่ตรวจจับได้: อาร์กิวเมนต์ 1 ที่ส่งไปยัง callFunc2 () ต้องเป็นอินสแตนซ์ของ> เรียกได้อินสแตนซ์ของการปิดที่กำหนดเรียกใน / in / kqeYD ในบรรทัดที่ 16 และกำหนดไว้ใน / in / kqeYD ในบรรทัดที่ 7

ออกจากกระบวนการด้วยรหัส 255

คุณสามารถลองใช้https://3v4l.org/kqeYD#v5321

ขอแสดงความนับถืออย่างสูง,


2
แทนที่จะโพสต์ลิงก์ไปยังโค้ดคุณควรโพสต์โค้ดที่นี่ในกรณีที่มีผู้อื่นพบปัญหานี้และลิงก์ที่คุณให้มาหยุดพัก คุณยังสามารถระบุผลลัพธ์ในโพสต์ของคุณเพื่อช่วยได้
Vedda

5
เนื่องจากcallableถูกนำมาใช้ใน PHP 5.4 ก่อนที่จะว่า PHP คาดว่าอินสแตนซ์ของชั้นชื่อcallableเช่นเดียวกับถ้าคุณต้องการที่ระบุคำแนะนำสำหรับPDO, หรือDateTime \My\Random\ClassName
Davey Shafik
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.