PHP:型ヒント-`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いつCallableOR を使用するかは同じ目的を果たしますか?

回答:


173

違いは、a 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!

したがって、ヒントを入力するだけの場合は匿名関数を使用します。Closure通常の関数callableをタイプヒントとして使用することも許可したい場合。


5
["Foo", "bar"]for Foo::barまたは[$foo, "bar"]for などの配列を渡すことにより、callableでクラスメソッドを使用することもでき$foo->barます。
Andrea

17
オフトピックだが関連:PHP 7.1以降、クロージャに簡単に変換できるようになりましたcallFunc1(Closure::fromCallable("xy"))wiki.php.net/rfc/closurefromcallable
nevvermind

それでも、なぜ無名関数のみを呼び出したいのかわかりません。コードを共有する場合、関数がどこから来たかは気にしないでください。PHPの癖の1つは、混乱を避けるために何らかの方法で削除する必要があると私は考えています。しかし、正直に言ってClosure+のClosure::fromCallableアプローチが好きcallableです。文字列や配列はいつも変なものでしたから。
Robo Robok

2
@RoboRobokではなくClosure(無名関数)のみを要求する1つの理由callableは、呼び出された関数のスコープを超えてアクセスできないようにするためです。たとえば、あなたがをprivate method乱用している誰かにアクセスさせたくない場合がありますcallable。参照:3v4l.org/7TSf2
fyrye

58

それらの間の主な違いは、ということで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クラスをfinalとして拡張することはできません。


1
呼び出し可能オブジェクトを他のスコープで再利用することもできます。
Bimal Poudel 2018年

つまりcallable、どの名前空間でも資格を得る必要はありません。
Jeff Puckett 2018

0

これは、PHPバージョン5.3.21から5.3.29では機能しないことに言及する価値があります。

これらのバージョンのいずれでも、次のような出力が得られます。

こんにちは世界!キャッチ可能な致命的なエラー:callFunc2()に渡される引数1は、> Callableのインスタンスでなければなりません。指定されたClosureのインスタンスは、16行目の/ in / kqeYDで呼び出され、7行目の/ in / kqeYDで定義されます

プロセスはコード255で終了しました。

https://3v4l.org/kqeYD#v5321を使用してそれを試すことができます

宜しくお願いします、


2
コードへのリンクを投稿する代わりに、誰かがこの問題に遭遇し、提供したリンクが壊れた場合に備えて、ここにコードを投稿する必要があります。また、投稿に出力を提供して支援することもできます。
Vedda、2015

5
これはcallable、PHP 5.4で導入されたためです。その前にPHPは、名前のクラスのインスタンスを期待しているcallableあなたがのためのヒントを指定したい場合と同様に、PDODateTime、または\My\Random\ClassName
Davey Shafik
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.