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

無名関数の例の1つとして。

誰かこれについて知っていますか?ドキュメントはありますか?そして、それは悪そうに見えます、それを使うべきですか?

回答:


362

これがPHPがクロージャを表現する方法です。これはまったく邪悪ではなく、実際には非常に強力で便利です。

基本的にこれが意味することは、匿名関数がスコープ外のローカル変数(この場合、$taxおよびへの参照$total)を「キャプチャ」し、それらの値(またはそれ自体$totalへの参照の場合$total)を内部の状態として保持できることです。匿名関数自体。


1
それで、それはクロージャーにのみ使用されますか?説明ありがとうございます、匿名関数とクロージャーの違いはわかりませんでした
SeanDowney

136
このuseキーワードは、名前空間のエイリアスにも使用されます。PHP 5.3.0のリリースから3年以上経過しても、構文function ... useはまだ公式に文書化されていないため、クロージャーは文書化されていない機能です。ドキュメントは匿名関数とクロージャを混同しさえします。use ()私がphp.netで見つけた唯一の(ベータおよび非公式の)ドキュメントは、クロージャRFCでした

2
では、PHPで関数使用クロージャーが実装されたのはいつですか?私はそれがPHP 5.3にあったと思いますか?PHPマニュアルになんとなく記載されていますか?
rubo77 2013

@Mytskineまあ、ドキュメントによると、匿名関数はクロージャクラスを使用します
Manny Fleurmond

1
useを含むためにも使用されているtraitにをclass
CJデニス

477

より簡単な答え。

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

  1. クロージャは変数に割り当てられた関数なので、渡すことができます
  2. クロージャーは別の名前空間です。通常、この名前空間の外で定義された変数にはアクセスできません。useキーワードがあります:
  3. useを使用すると、クロージャー内の後続の変数にアクセス(使用)できます。
  4. 使用は事前バインディングです。つまり、変数値は、クロージャの定義時にコピーされます。その$taxため、オブジェクトのようにポインタでない限り、クロージャの内部を変更しても外部に影響はありません。
  5. の場合と同様に、変数をポインタとして渡すことができます&$total。このように、$total外部効果の値を変更すると、元の変数の値が変更されます。
  6. クロージャの内部で定義された変数は、クロージャの外部からもアクセスできません。
  7. クロージャと関数の速度は同じです。はい、スクリプト全体で使用できます。

@Mytskineが指摘したように、おそらく最も詳細な説明は、クロージャRFCです。(これに賛成票を投じてください。)


4
私は、PHP 5.5の構文エラーを与えているuse文でのキーワードとして:$closure = function ($value) use ($localVar as $alias) { //stuff};与えられたエラーがある:Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
カルZekdor

1
php5.3でも確認された@KalZekdorは非推奨のようです。回答を更新しました。ありがとうございました。
zupa 14

4
このようにポイント5に追加すると、ポインターのように値を変更すると、&$total内部的にも効果があります。つまり、定義にクロージャの$total 外側の値を変更した場合、新しい値はポインタである場合にのみ渡されます。
ビリーノア2015年

2
@ AndyD273目的は確かに非常によく似ていますが、親名前空間の変数へのアクセスを許可するglobal一方で、グローバル名前空間へのアクセスのみを許可しuseます。グローバル変数は一般的に悪と見なされます。多くの場合、親スコープへのアクセスは、クロージャーを作成するまさにその目的です。その範囲は非常に限られているため、「悪」ではありません。JSのような他の言語では、暗黙的に使用する(値がコピーされないよう、ポインタとして)親スコープの変数を。
zupa 2016年

1
この行は私の2時間の無駄な検索を止めました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
これを読んだ後、私はもう少しスクロールを後悔していませんが、第3ブロックのタイプミスのためのマイナーな編集が​​必要だと思います。$ objの代わりに$ sがあるはずです。
ユーザーをスタックする

53

閉鎖は美しいです!それらは匿名関数に付随する多くの問題を解決し、本当にエレガントなコードを可能にします(少なくともphpについて話す限り)。

バインドされた変数が明示的に定義されていないため、JavaScriptプログラマーはクロージャーを常に使用します。これは、知らないうちに行われる場合もあります。これが、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をインストールしていません)、しかしそれはそのようなもののように見えるはずです。

欠点が1つあります。多くのPHP開発者は、クロージャーに直面した場合、少し無力になる可能性があります。

クロージャーの便利さをさらに理解するために、別の例を挙げましょう。今回はJavaScriptです。問題の1つは、スコープとブラウザ固有の非同期です。特に、それがwindow.setTimeout();(または-interval)になる場合。そのため、関数を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は、一種の事前定義パラメーターを持つ関数を返します!

正直に言うと、5.3以降のPHPと匿名関数/クロージャーがもっと好きです。名前空間の方が重要かもしれませんが、それほどセクシーではありません


4
ohhhhhhhhなので、Usesは追加の変数を渡すために使用されるので、面白い割り当てだと思いました。ありがとう!
SeanDowney 2009

38
注意してください。パラメータは、関数がCALLEDのときに値を渡すために使用されます。クロージャは、関数が定義されているときに値を「渡す」ために使用されます。
2009

JavaScriptでは、bind()を使用して関数の初期引数を指定できます。「部分的に適用される関数」を参照してください。
SᴀᴍOnᴇᴌᴀ

17

Zupaは、「使用」によるクロージャの説明、およびEarlyBindingと「使用された」変数の参照の違いを説明する素晴らしい仕事をしました。

だから私は変数の事前バインディング(=コピー)でコード例を作りました:

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