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

익명 함수 에 대한 예제 중 하나입니다 .

아무도 이것에 대해 알고 있습니까? 어떤 문서? 그리고 그것은 악하게 보입니다, 그것을 사용해야합니까?

답변:


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

@Mytskine 글에 따르면 익명 함수는 Closure 클래스를 사용합니다
Manny Fleurmond

1
이제 useA 이하에도 사용됩니다 trait로를 class!
CJ Dennis

477

더 간단한 대답.

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

  1. 클로저는 변수에 할당 된 함수이므로 전달할 수 있습니다
  2. 클로저는 별도의 네임 스페이스이므로 일반적으로이 네임 스페이스 외부에 정의 된 변수에 액세스 할 수 없습니다. 온다 사용 키워드 :
  3. use를 사용 하면 클로저 내부의 후속 변수에 액세스 (사용) 할 수 있습니다.
  4. 사용 은 초기 바인딩입니다. 이는 클로저 정의시 변수 값이 복사됨을 의미합니다. 따라서$tax클로저 내부를수정하면 객체와 같은 포인터가 아닌 한 외부 효과가 없습니다.
  5. 의 경우와 같이 변수를 포인터로 전달할 수 있습니다 &$total. 이 방법으로 $totalDOES 값을 수정하면 외부 효과 가 생겨 원래 변수의 값이 변경됩니다.
  6. 클로저 내부에 정의 된 변수는 클로저 외부에서도 액세스 할 수 없습니다.
  7. 클로저와 기능의 속도는 동일합니다. 예, 스크립트 전체에서 사용할 수 있습니다.

@Mytskine이 지적했듯이 아마도 가장 좋은 설명은 클로저에 대한 RFC입니다 . (이를 위해 그를 찬성하십시오.)


4
use 문의 as 키워드는 PHP 5.5에서 구문 오류를 나타냅니다. $closure = function ($value) use ($localVar as $alias) { //stuff};오류는 다음과 같습니다.Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
Kal Zekdor

1
php5.3으로 확인 된 @KalZekdor도 더 이상 사용되지 않는 것 같습니다. 노력해 주셔서 감사합니다.
zupa

4
이 방법으로 포인트 5를 추가하여 포인터와 같은 값을 수정 &$total하면 내부 효과도 있습니다. 즉, 정의 된 클로저 $total 외부 의 값을 변경 하면 새 값이 포인터 인 경우에만 전달됩니다.
billynoah

2
@ AndyD273 목적은 global전역 네임 스페이스에 대한 액세스 만 허용 use하고 부모 네임 스페이스의 변수에 액세스 할 수 있다는 점을 제외하면 실제로는 매우 유사 합니다. 전역 변수는 일반적으로 악으로 간주됩니다. 부모 범위에 액세스하는 것은 종종 클로저를 만드는 목적입니다. 범위가 매우 제한되어 있으므로 "악"이 아닙니다. JS와 같은 다른 언어는 암시 적으로 부모 범위의 변수를 사용 합니다 (복사 된 값이 아닌 포인터로).
zupa

1
이 라인은 내 두 시간 헛된 검색을 중지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
이것을 읽은 후 나는 조금 더 스크롤을 후회하지 않지만 세 번째 블록에서 오타를 약간 수정해야한다고 생각합니다. $ obj 대신 $ s가 있어야합니다.
Stack user

53

폐쇄는 아름답다! 그들은 익명 함수와 함께 제공되는 많은 문제를 해결하고 (최소한 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이 설치되어 있지 않습니다),하지만 그런 식이어야합니다.

한 가지 단점이 있습니다. 많은 PHP 개발자는 클로저에 직면하면 약간 무력 할 수 있습니다.

클로저의 멋진 점을 더 이해하기 위해 이번에는 자바 스크립트로 다른 예를 들어 보겠습니다. 문제 중 하나는 범위 지정과 브라우저 고유의 비동기 성입니다. 특히 window.setTimeout();(또는-간격) 경우. 따라서 함수를 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

38
조심해. 매개 변수는 함수가 호출 될 때 값을 전달하는 데 사용됩니다. 클로저는 함수가 정의 될 때 값을 "전달"하는 데 사용됩니다.
stefs

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.