PHP: Gợi ý nhập - Sự khác biệt giữa `Đóng` và` Có thể gọi được '


128

Tôi nhận thấy rằng tôi có thể sử dụng một trong hai Closurehoặc Callablenhư gợi ý loại nếu chúng tôi mong đợi một số chức năng gọi lại sẽ chạy. Ví dụ:

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

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

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

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

Câu hỏi:

Sự khác biệt ở đây là gì? Nói cách khác khi nào nên sử dụng Closurevà khi nào sử dụng CallableHOẶC chúng có cùng mục đích không?

Câu trả lời:


173

Sự khác biệt là, đó Closurephải là một hàm ẩn danh, trong đó callablecũng có thể là một hàm bình thường.

Bạn có thể xem / kiểm tra điều này với ví dụ bên dưới và bạn sẽ thấy rằng bạn sẽ gặp lỗi cho lần đầu tiên:

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!

Vì vậy, nếu bạn chỉ muốn gõ hàm ẩn danh, hãy sử dụng: Closurevà nếu bạn cũng muốn cho phép các hàm bình thường sử dụng callablelàm gợi ý kiểu.


5
Bạn cũng có thể sử dụng các phương thức lớp với khả năng gọi bằng cách truyền một mảng, ví dụ ["Foo", "bar"]cho Foo::barhoặc [$foo, "bar"]cho $foo->bar.
Andrea

17
Offtopic, nhưng có liên quan: kể từ PHP 7.1, giờ đây bạn có thể dễ dàng chuyển đổi sang Đóng : callFunc1(Closure::fromCallable("xy")). wiki.php.net/rfc/clensesfromcallable
nevvermind 20/03/2017

Tôi vẫn không thấy lý do tại sao tôi chỉ muốn gọi chức năng ẩn danh. Nếu tôi chia sẻ mã, tôi không nên quan tâm chức năng đến từ đâu. Tôi nghĩ rằng một trong những điều kỳ quặc của PHP, họ nên loại bỏ một hoặc một cách khác để tránh nhầm lẫn. Nhưng tôi thực sự thích cách tiếp cận Closure+ Closure::fromCallable, bởi vì chuỗi hoặc mảng callableluôn luôn là lạ.
Robo Robok

2
@RoboRobok một lý do chỉ yêu cầu Closure(chức năng ẩn danh) trái ngược với callable, sẽ là ngăn chặn truy cập vượt quá phạm vi của chức năng được gọi. Ví dụ: khi bạn có một người private methodbạn không muốn bị ai đó lạm dụngcallable . Xem: 3v4l.org/7TSf2
fyrye

58

Sự khác biệt chính giữa chúng closurelà một lớpcallablemột loại .

Các callableloại chấp nhận bất cứ điều gì có thể được gọi là :

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

Trường hợp closuresẽ chỉ chấp nhận một chức năng ẩn danh. Lưu ý rằng trong phiên bản PHP 7.1, bạn có thể chuyển đổi các hàm thành một bao đóng như vậy : Closure::fromCallable('functionName').


Thí dụ:

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
}

Vậy tại sao nên sử dụng một closure hơncallable ?

Nghiêm khắc vì một closurelà một đối tượng mà có một số phương pháp bổ sung: call(), bind()bindto(). Chúng cho phép bạn sử dụng một hàm được khai báo bên ngoài một lớp và thực thi nó như thể nó nằm trong một lớp.

$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

Bạn không muốn gọi các phương thức trên một hàm bình thường vì điều đó sẽ gây ra các lỗi nghiêm trọng. Vì vậy, để tránh né, bạn sẽ phải viết một cái gì đó như:

if($cb instanceof \Closure){}

Để làm điều này kiểm tra mỗi lần là vô nghĩa. Vì vậy, nếu bạn muốn sử dụng các phương thức đó nói rằng đối số là a closure. Nếu không thì chỉ sử dụng bình thường.callback . Cách này; Một lỗi được đưa ra khi gọi hàm thay vì mã của bạn khiến cho việc chẩn đoán dễ dàng hơn nhiều.

Trên một mặt lưu ý: Các closurelớp không thể được mở rộng như nó thức .


1
Bạn cũng có thể sử dụng lại một cuộc gọi trong các phạm vi khác.
Bimal Poudel

Điều này có nghĩa là bạn không phải đủ điều kiện callabletrong bất kỳ không gian tên nào.
Jeff Puckett

0

Điều đáng nói là điều này sẽ không hoạt động đối với các phiên bản PHP 5.3,21 đến 5,3,29.

Trong bất kỳ phiên bản nào, bạn sẽ nhận được một đầu ra như:

Chào thế giới! Lỗi nghiêm trọng có thể bắt được: Đối số 1 được truyền cho callFunc2 () phải là một thể hiện của> Có thể gọi được, thể hiện của Đóng được đưa ra, được gọi trong / in / kqeYD trên dòng 16 và được xác định trong / in / kqeYD trên dòng 7

Quá trình thoát với mã 255.

Mọi người có thể dùng thử bằng cách sử dụng https://3v4l.org/kqeYD#v5321

Trân trọng,


2
Thay vì đăng một liên kết đến mã, bạn nên đăng mã ở đây trong trường hợp người khác gặp phải vấn đề này và liên kết bạn đã cung cấp bị hỏng. Bạn cũng có thể cung cấp đầu ra trong bài viết của bạn để giúp đỡ.
Vedda

5
Điều này là do callableđã được giới thiệu trong PHP 5.4. Trước đó PHP được mong đợi một thể hiện của một lớp có tên callable, cũng giống như khi bạn đã chỉ định một gợi ý cho PDO, DateTimehoặc \My\Random\ClassName.
Davey Shafik
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.