PHP: Ketik mengisyaratkan - Perbedaan antara `Penutupan` dan` Callable`

128

Saya perhatikan bahwa saya dapat menggunakan salah satu Closureatau Callablesebagai petunjuk jenis jika kami mengharapkan beberapa fungsi panggilan balik untuk berjalan. Sebagai contoh:

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

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

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

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

Pertanyaan:

Apa bedanya di sini? Dengan kata lain kapan digunakan Closuredan kapan digunakan CallableATAU mereka melayani tujuan yang sama?

Dev01
sumber

Jawaban:

173

Perbedaannya adalah, bahwa Closureharus merupakan fungsi anonim, di mana callablejuga bisa menjadi fungsi normal.

Anda dapat melihat / menguji ini dengan contoh di bawah ini dan Anda akan melihat bahwa Anda akan mendapatkan kesalahan untuk yang pertama:

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!

Jadi jika Anda hanya ingin mengetik petunjuk gunakan fungsi anonim: Closuredan jika Anda juga ingin mengizinkan fungsi normal gunakan callablesebagai petunjuk jenis.

Rizier123
sumber
5
Anda juga dapat menggunakan metode kelas dengan callable dengan melewatkan array, misalnya ["Foo", "bar"]untuk Foo::baratau [$foo, "bar"]untuk $foo->bar.
Andrea
17
Offtopic, tapi terkait: sejak PHP 7.1, kini Anda dapat dengan mudah dikonversi ke Penutupan: callFunc1(Closure::fromCallable("xy")). wiki.php.net/rfc/closurefromcallable
nevvermind
Saya masih tidak mengerti mengapa saya ingin memanggil fungsi anonim saja. Jika saya membagikan kode, saya seharusnya tidak peduli dari mana fungsi itu berasal. Saya menganggap bahwa salah satu kebiasaan PHP, mereka harus menghapus satu atau cara lain untuk menghindari kebingungan. Tapi saya jujur ​​suka pendekatan Closure+ Closure::fromCallable, karena string atau array seperti callableselalu aneh.
Robo Robok
2
@RoboRobok satu alasan untuk hanya membutuhkan Closure(fungsi anonim) sebagai lawan callable, adalah untuk mencegah akses di luar ruang lingkup fungsi yang disebut. Misalnya ketika Anda memiliki private methodAnda tidak ingin diakses oleh seseorang yang menyalahgunakan callable. Lihat: 3v4l.org/7TSf2
fyrye
58

Perbedaan utama di antara mereka adalah bahwa a closureadalah kelas dan callablesatu jenis .

Itu callable Tipe menerima apa pun yang bisa disebut :

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

Di mana closureakan hanyamenerima fungsi anonim. Perhatikan bahwa dalam PHP versi 7.1 Anda dapat mengkonversi fungsi untuk penutupan seperti: Closure::fromCallable('functionName').


Contoh:

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
}

Jadi mengapa menggunakan closureover callable?

Ketatnya karena closuremerupakan objek yang memiliki beberapa metode tambahan: call(), bind()danbindto() . Mereka memungkinkan Anda untuk menggunakan fungsi yang dideklarasikan di luar kelas dan menjalankannya seolah-olah itu di dalam kelas.

$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

Anda tidak ingin memanggil metode pada fungsi normal karena itu akan memunculkan kesalahan fatal. Jadi untuk menghindari itu Anda harus menulis sesuatu seperti:

if($cb instanceof \Closure){}

Untuk melakukan ini, periksa setiap saat tidak ada gunanya. Jadi jika Anda ingin menggunakan metode-metode tersebut nyatakan bahwa argumennya adalah aclosure . Kalau tidak, gunakan saja yang normal callback. Cara ini; Kesalahan muncul pada pemanggilan fungsi alih-alih kode Anda sehingga membuatnya lebih mudah untuk didiagnosis.

Di samping catatan: The closureclass tidak dapat diperpanjang sebagai yang akhir .

Xorifelse
sumber
1
Anda dapat menggunakan kembali callable di lingkup lain juga.
Bimal Poudel
Ini berarti Anda tidak harus memenuhi syarat callabledi namespace apa pun.
Jeff Puckett
0

Perlu disebutkan bahwa ini tidak akan berfungsi untuk versi PHP 5.3.21 hingga 5.3.29.

Di salah satu versi itu Anda akan mendapatkan output seperti:

Halo Dunia! Kesalahan fatal yang dapat ditangkap: Argumen 1 yang dilewatkan ke callFunc2 () harus merupakan instance dari> Callable, instance dari Closure yang diberikan, disebut di / in / kqeYD pada saluran 16 dan didefinisikan dalam / in / kqeYD pada saluran 7

Proses keluar dengan kode 255.

Seseorang dapat mencobanya menggunakan https://3v4l.org/kqeYD#v5321

Salam Hormat,

Rod Elias
sumber
2
Alih-alih memposting tautan ke kode, Anda harus memposting kode di sini kalau-kalau ada orang lain yang mengalami masalah ini dan tautan yang Anda berikan istirahat. Anda juga dapat memberikan hasil di pos Anda untuk membantu.
Vedda
5
Ini karena callablediperkenalkan di PHP 5.4. Sebelum itu PHP mengharapkan sebuah instance dari kelas bernama callable, sama seperti jika Anda sudah ditentukan petunjuk untuk PDO, DateTime, atau \My\Random\ClassName.
Davey Shafik