>  기사  >  백엔드 개발  >  PHP 클로저 함수

PHP 클로저 함수

巴扎黑
巴扎黑원래의
2016-11-11 13:41:151469검색

익명함수는 프로그래밍 언어에서 비교적 초기에 등장했는데, 처음에는 Lisp 언어에서 등장했고 이후 많은 프로그래밍 언어가 이 기능을 갖기 시작했습니다.

현재 Javascript와 C#을 사용하던 PHP는 5.3까지만 본격화되기 시작했습니다. 익명 함수를 지원하며 새로운 C++ 표준 C++0x도 이를 지원하기 시작했습니다.

익명 함수는 식별자를 지정하지 않고 호출할 수 있는 함수 또는 서브루틴 유형입니다. 익명 함수는 다른 함수에 매개변수로 편리하게 전달할 수 있습니다.

클로저
익명 함수에 관해서라면 꼭 언급해야 할 클로저는 Lexical Closure의 약자로 자유 변수를 참조하는 함수입니다. 이 함수에는 적용된 자유 변수가 존재하게 됩니다. 클로저가 생성된 환경을 떠나더라도 클로저는 함수와 관련 참조로 구성된 엔터티로 간주될 수도 있습니다. 일부 언어에서는 함수 내에서 다른 함수를 정의할 때 내부 함수가 외부 함수의 변수를 참조하면 클로저가 발생할 수 있습니다. 외부 함수를 실행하면 클로저가 형성됩니다.

이라는 단어는 익명 함수와 혼동되기 쉽습니다. 실제로는 두 가지 다른 개념입니다. 이는 많은 언어에서 익명 함수를 구현할 때 클로저 형성을 허용하기 때문일 수 있습니다.

"익명" 함수를 생성하려면 create_function()을 사용하세요
앞서 언급했듯이 익명 함수는 PHP5.3에서만 공식적으로 지원됩니다. 이 시점에서 주의 깊은 독자 중 일부는 의견이 있을 수 있습니다. 익명 함수 생성 가능: create_function 함수, 이 함수는 설명서의 PHP4.1 및 PHP5에서 찾을 수 있습니다. 이 함수는 일반적으로 다음과 같이 익명 콜백 함수로 사용할 수도 있습니다.

[php ] 일반 사본 보기

$array = array(1, 2, 3, 4)
array_walk($array, create_function('$value', 'echo $value') );

이 코드는 배열의 값을 순서대로 출력할 뿐입니다. 물론 더 많은 작업을 수행할 수도 있습니다. 그렇다면 이것이 진정한 익명 함수가 아닌 이유는 무엇입니까? 먼저 이 함수의 반환 값을 살펴보겠습니다. 이 함수는 일반적으로 다음과 같은 함수를 호출할 수 있습니다:
[php] view plaincopy
php
함수 a() {
echo '함수 a';

$a = 'a'

🎜>콜백 함수를 구현할 때도 이 방법을 사용할 수 있습니다. 예:

[php] view plaincopy

function do_something($callback) { / /doing
# ...

// done
$callback()
}

이렇게 하면 do_something() 함수의 실행이 완료됩니다. $callback에 지정된 함수를 호출합니다. create_function 함수의 반환 값으로 돌아가기: 함수는 고유한 문자열 함수 이름을 반환하거나 오류가 발생하면 FALSE를 반환합니다. 따라서 이 함수는 동적으로 함수를 생성할 뿐이며 이 함수에는 함수 이름이 있으므로 실제로는 익명이 아닙니다. 단지 전역적으로 고유한 기능을 생성하는 것뿐입니다.

[php] view plaincopy
$func = create_function('', 'echo "동적으로 생성된 함수";')
echo $func; 🎜> $func(); // 동적 생성된 함수

$my_func = 'lambda_1';
$my_func(); // 이 함수는 존재하지 않습니다.
lambda_1(); // 이 함수는 존재하지 않습니다

위 코드의 첫 번째 부분은 create_function이 사용되는 방식입니다. 나중에 함수 이름을 통해 호출되지 않습니다. 이해하세요. PHP는 이를 어떻게 보장합니까? 함수는 전역적으로 고유합니까? Lambda_1이라는 함수를 먼저 정의하면 어떻게 될까요? function은 적절한 함수 이름을 찾는 방법을 알고 있지만, create_function 다음에 Lambda_1이라는 함수를 정의하면 어떻게 될까요? 이렇게 하면 함수를 반복적으로 정의하는 문제가 발생할 수 있습니다. 실제로는 이 구현이 최선의 방법이 아닐 수도 있습니다. Lambda_1이라는 함수를 정의해도 앞서 언급한 문제가 발생하지 않습니다. 무슨 일이 일어나고 있는 걸까요? 위 코드의 마지막 두 줄도 이 문제를 보여줍니다. 실제로 정의된 Lambda_1이라는 함수가 없습니다.
[php] 일반 복사 보기
$func = create_function('', 'echo "Hello";')

$my_func_name = 'lambda_1'
debug_zval_dump($ func); // string(9) "lambda_1" refcount(2)
debug_zval_dump($my_func_name); // string(8) "lambda_1" refcount(2)

보세요. 길이가 다르다는 것은 동일한 함수가 아니라는 것을 의미하므로 당연히 우리가 호출하는 함수는 존재하지 않습니다. create_function 함수가 무엇을 하는지 살펴보겠습니다. 구현 보기: $PHP_SRC/Zend/zend_builtin_functions.c

[php] 일반 복사 보기
#define LAMBDA_TEMP_FUNCNAME "__lambda_func"

ZEND_FUNCTION(create_function)
{
/ / ... 관련 없는 코드 저장
function_name = (char *) emalloc(sizeof("0lambda_")+MAX_LENGTH_OF_LONG)
function_name[0] = '이는 Javascript에서는 매우 일반적이지만 PHP에서는 불가능합니다. 객체의 속성을 복사하면 클래스가 클래스에 정의된 메서드를 찾게 됩니다. 이름은 반복될 수 있습니다. 이는 PHP의 클래스 모델에 따라 결정됩니다. 물론, 후속 버전에서는 이러한 호출을 허용하여 일부 기능을 보다 쉽게 ​​구현할 수 있습니다. 현재 이 효과를 얻을 수 있는 방법이 있습니다. 또 다른 마법 메서드인 __call()을 사용하는 것입니다. 이를 달성하는 방법은 독자들에게 연습으로 남겨두겠습니다.

클로저의 사용
PHP는 익명 함수를 구현하기 위해 클로저(Closure)를 사용합니다. 익명 함수의 가장 강력한 기능은 익명 함수가 제공하는 동적 기능과 클로저 효과 중 일부입니다. 정의 중에 범위 외부의 변수를 사용하려면 다음 구문을 사용해야 합니다:

[php] view plaincopy
$name = 'TIPI Team'
$func = function() use($name) {
echo "안녕하세요, $name";
}

$func() // 안녕하세요 TIPI 팀

사용 특히 Javascript에 비해 구문이 상당히 어색해 보이지만 Javascript의 범위와 달리 PHP 함수에 정의된 변수는 기본적으로 로컬 변수이므로 종합적으로 고려한 후에 PHP-Core에서 사용하는 구문이어야 합니다. 물론 명시적으로 정의된 지역 변수를 제외하고는 PHP는 해당 변수가 지역 변수인지 상위 범위의 변수인지 판단할 수 없습니다. 물론 컴파일 타임에 이를 확인할 수 있는 방법이 있을 수도 있지만 이는 좋지 않습니다. 언어의 경우 효율성과 복잡성이 큰 영향을 미칩니다.

이 구문은 비교적 간단합니다. 상위 범위의 변수에 액세스해야 하는 경우 use 문을 사용하여 선언해야 합니다. 이 시점에서도 실제로 읽을 수 있습니다. global 문과 비슷한 효과를 얻으려면 use를 사용하세요.

익명 함수는 실행될 때마다 상위 범위의 변수에 액세스할 수 있습니다. 이러한 변수는 다음 예와 같이 익명 함수가 소멸되기 전에 항상 자체 상태를 저장합니다.

[ php] view plaincopy
function getCounter() {
$i = 0
return function() use($i) { // 참조를 사용하여 여기에 변수를 사용하세요.
}

$counter = getCounter()
$counter(); 1
$counter(); // 1

여기서 두 함수 호출은 $i 변수를 증가시키지 않습니다. 기본적으로 PHP는 다음과 같이 상위 계층 변수를 익명 함수에 전달합니다. 상위 변수의 값을 변경해야 하는 경우에는 참조로 전달해야 합니다. 따라서 위 코드는 1, 2가 아닌 1, 1을 출력합니다.

클로저 구현
앞서 언급했듯이 익명 함수는 클로저를 통해 구현됩니다. 이제 클로저(클래스)가 어떻게 구현되는지 살펴보겠습니다. 클로저 구현 코드는 $PHP_SRC/Zend/zend_closure.c에 있습니다. 익명 함수의 '객체화' 문제는 Closure를 통해 구현되었으며, 익명 함수 생성 시 변수에 어떻게 접근하나요?

예를 들어 다음 코드는

[ php] view입니다. plaincopy
$i=100;
$counter = function() use($i) { debug_zval_dump($i)

$counter();

VLD를 사용하여 이 인코딩으로 어떤 종류의 opcode가 컴파일되는지 확인하세요.

[php] view plaincopy
$ php -dvld.active=1 closure .php

vars: !0 = $i, !1 = $counter
# * op -------------------------- ------------------------ ------
0 > 할당                                                                     '%00%7B클로저 
2      할당                                               !1, ~1  
3      INIT_FCALL_BY_NAME                              >             1  
   
함수 이름:  {closure}  
작업 수:  5  
컴파일된 변수:  !0 = $i  
line     # *  op                         가져오기          ext  return  피연산자  
---------------------------- ------------------------------------- --  
  3     0  >   FETCH_R                     정적             $0      'i'  
        1      할당            >      DO_FCALL                                    1          'debug_zval_dump'  
  5     4    > 반환                                               null  

상면根据情况去掉了一些无关的输流, 从上到下, 第1开始将100赋值给!0也就是变weight$i, ,저의 opcode는 中看看这里에서 작동합니다.是怎么执行, 这个opcode的处理函数位于$PHP_SRC/Zend/zend_vm_execute.h中: 

[php] 일반 사본 보기 
정적 정수 ZEND_FASTCALL  ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_CON ST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) 
{  
    zend_op *오프라인 = EX(오프라인);  
    zend_function *op_array;  
   
    if (zend_hash_quick_find(EG(function_table), Z_STRVAL(opline->op1.u.constant), Z_STRLEN(opline->op1.u.constant), Z_LVAL(opline->op2.u.constant) , (void *) &op_arra  
y) == 실패 ||  
        op_array->type != ZEND_USER_FUNCTION) {  
        zend_error_noreturn(E_ERROR, "클로저에 대한 기본 람다 함수를 찾을 수 없습니다.");  
    }  
   
zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array TSRMLS_CC);

ZEND_VM_NEXT_OPCODE() 함수를 사용하여 클로저 객체를 생성한 다음 zend_create_closure() 함수가 어디에 있는지 계속 살펴보겠습니다. $PHP_SRC/Zend/zend_closures.c에서는 그렇습니다.

[Php] 일반 사본 보기
Zend_api Void Zend_closure(Zval *Res, Zend_function *Func TSRMLS_DC)
{
Zend_Closure *CLOS URE

Object_init_ex(res, Zend_ce_Closure; ) ;

클로저 = (zend_closure *)zend_object_store_get_object(res TSRMLS_CC)

클로저->func = *func; ZEND_USER_FUNCTION ) { // 사용자 정의 익명 함수인 경우
if (closure->func.op_array.static_variables) {
HashTable *static_variables = closure->func.op_array.static_variables

함수에 대한 정적 변수를 저장하기 위한 해시 테이블 공간을 적용합니다.
, ZVAL_PTR_DTOR, 0 ); // 현재 정적 변수를 반복합니다. 이를 처리하려면 zval_copy_static_var 메소드를 사용하세요.
}  
(*closure->func.op_array.refcount)++; 🎜> closure->func.common.scope = NULL;
}

위 코드와 마찬가지로 주석에서 언급했듯이 zval_copy_static_var() 함수의 구현을 계속 살펴보세요.

[php] view plaincopy
static int zval_copy_static_var(zval **p TSRMLS_DC, int num_args, va_list args, zend_hash_key *key)
{
HashTable *target = va_arg(args, HashTable*); 🎜> zend_bool is_ref

// use 문 유형의 정적 변수에 대해서만 값 연산을 수행합니다. 그렇지 않으면 익명 함수 본문 정적 변수가 범위 밖의 변수에도 영향을 미칩니다.
if (Z_TYPE_PP(p) & ( IS_LEXICAL_VAR|IS_LEXICAL_REF)) {
is_ref = Z_TYPE_PP(p) & IS_LEXICAL_REF;

if ( !EG(active_symbol_table)) {
zend_rebuild_symbol_table(TSRMLS_C)
}
// 현재 범위에 해당 변수가 없는 경우
if (zend_hash_quick_find(EG(active_ Symbol_table), key->arKey, key- >nKeyLength, key->h, (void **) &p) == FAILURE) { create 임시 변수는 익명 함수가 정의된 후 변수에서 작동합니다. (active_symbol_table), key->arKey, key->nKeyLength, key->h, &tmp, sizeof(zval*), (void* *)&피);// 참조가 아니면 변수가 존재하지 않는다는 뜻입니다.

} else {
// 이 변수가 있으면 참조 여부에 따라 변수를 참조하거나 복사합니다.
if (is_ref) {
SEPARATE_ZVAL_TO_MAKE_IS_REF(p);
         } else if (Z_ISREF_PP(p )) {
SEPARATE_ZVAL(p) }
}
if (zend_hash_quick_add(target, key->arKey, key-> nKeyLength, key->h, p, sizeof (zval*), NULL) == SUCCESS) {
Z_ADDREF_PP(p)
}
return ZEND_HASH_APPLY_KEEP;
}

이 함수는 콜백 함수인 gumments( ) 함수로 zend_hash_apply_with_ar에 전달되며, 해시 테이블의 값을 읽을 때마다 이 함수에 의해 처리되고 이 함수는 할당합니다. 모든 use 문에 의해 정의된 변수 값을 이 익명 함수의 정적 변수에 연결하여 익명 함수가 use 변수에 액세스할 수 있도록 합니다.

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.