클로저 – 04.callable 타입

callable 타입 지원

preg_replace_callback, preg_replace_callback_array, array_map, array_filter, array_reduce 또는 array_walk와 같은 PHP 내장 함수에 콜백 함수를 매개변수(parameter)로 전달할 수 있습니다.

콜백 함수를 매개변수(parameter)로 받는 PHP 내장 함수가 거의 없기 때문에 PHP 5.4부터 사용자 정의 함수에서 callable 타입 지원합니다. 매개변수를 callable 자료형으로 선언하게 되면 함수를 매개변수로 전달 받을 수 있습니다.

callable 타입 전달

callable 타입으로 지정할 수 있는 함수는 단순한 함수 뿐만 아니라 정적 클래스 메소드를 포함한 객체 메소드일 수 있으며, 매직 메소드 __invoke를 사용하면 객체와 클로저를 callable 타입으로 지정할 수 있습니다.

클로저는 기본적으로 __invoke 메소드가 정의되어 있으므로 별도의 조치가 필요 없으나 객체는 __invoke 메소드를 정의해주어야 callable 타입으로 객체를 호출할 수 있습니다.

callable 타입

callable 타입 중 클로저는 callable 타입 외에도 클로저 만의 타입인 Closure로 타입 힌팅 할 수 있습니다.

Closure로 타입 힌팅되었을 때 익명 함수가 아닌 일반 함수, 메소드, 객체를 지정하면 치명적인 타입 오류가 발생합니다.

callable 타입의 콜백 함수는 변수, 문자열, 배열, 객체 자료형(data type)에 할당되어 전달될 수 있으며, is_callable 함수를 사용하여 callable 타입인지 확인할 수 있습니다.

callable 타입 호출

callable 타입 콜백은 call_user_func, call_user_func_array 함수의 매개변수로 전달하여 호출할 수 있습니다.

또는 함수가 할당된 변수에 괄호를 붙여 호출할 수 있습니다. 그러나 이 호출 방법은 PHP 버전에 따라 지원되지 않을 수 있습니다.

callable 타입 함수

PHP 내장 함수를 콜백 함수로 전달될 수 있습니다.

단 내장 함수 중에 array(), echo, empty(), eval(), exit(), isset(), list(), print, unset(), include 또는 require와 같이 실제 함수가 아니고 언어 구조(language construct)인 경우는 호출 함수로 전달될 수 없습니다.

사용자 정의 함수에서도 callable 타입으로 매개변수를 타입 힌팅(type hinting)하게 되면 callable 함수를 전달할 수 있습니다.

callable 타입 객체 메소드

인스턴스화 된 객체의 메소드는 배열에 저장하여 전달합니다. 배열 첫 번째 요소에 객체를, 두 번째 요소에 메소드 이름을 저장하여 전달합니다.

callable 타입 클래스 메소드

정적 클래스 메소드는 배열 첫 번째 요소에 객체 대신 클래스 이름을 전달하거나 ‘ClassName::methodName’을 전달하여 해당 클래스의 객체를 인스턴스화 하지 않고도 전달할 수 있습니다.

부모 클래스의 정적 메소드를 ‘parent::methodName’로 전달할 수 있습니다. 그러나 부모 클래스의 정적 메소드를 호출할 때는 $fn() 방식으로 호출할 수 없으며 call_user_func, call_user_func_array 내장 함수를 사용하여 호출하여야 합니다.

callable 타입 객체

PHP 5.3부터 지원되는 __invoke 매직 메소드는 일반적인 클래스에서 객체를 함수로 호출할 때 실행되어야 하는 메소드로 사용됩니다.

이에 따라 매직 메소드 __invoke를 클래스에 추가하여 정의하게 되면 객체 자체를 함수로 호출할 수 있으며, 함수로 호출하면 객체 대신에 추가된 매직 메소드 __invoke가 호출됩니다.

클래스 Example의 객체인 $foo를 객체가 아닌 함수 $foo()로 호출하게 되면 클래스 Example에 정의된 __invoke 메소드가 실행됩니다.

객체를 함수로 호출하였는데 해당 클래스에 __invoke 메소드가 정의되지 않았다면 치명적인 오류가 발생합니다.

클래스에 __invoke 메소드를 정의하게 되면 해당 클래스 객체는 non-callable 타입에서 callable 타입으로 변경됩니다. 따라서 __invoke 메소드를 구현하는 모든 객체는 콜백 매개변수에 전달할 수 있습니다.

callable 타입 클로저

클로저는 동적으로 호출 될 수 있는 새로운 유형의 변수인 클로저 객체를 구현하기 때문에 일반적인 callable 타입도 구현할 수 있습니다. 따라서 익명 함수와 화살표 함수로 콜백 매개변수에 전달할 수 있습니다.

클로저는 함수로 호출해야 하기 때문에 이러한 클로저 객체를 생성하는 Closure 클래스에는 __invoke 메소드가 포함되어 있지만, 사용자가 Closure 클래스에 포함된 __invoke 메소드를 직접 호출할 필요는 없으며, PHP 공식 문서에서는 클로저를 호출할 때는 $closure()와 같이 함수로 호출하도록 하고 있습니다.

그러나 __invoke 메소드의 동작 원리에 따라 __invoke 메소드를 직접 호출하여도 함수로 호출할 때와 동일한 결과를 얻을 수 있습니다.

아래와 같이 객체 프로퍼티에 저장된 클로저를 호출할 때, PHP 이전 버전에서 함수로 호출할 수 없는 경우에 __invoke 메소드를 사용하면 PHP 버전에 구애 받지 않고 정상적으로 클로저를 호출할 수 있습니다.

그러나 __invoke 메소드를 직접 호출하는 것은 문서화 되지 않는 방법*1이니 피하시는 것이 좋을 듯 합니다. 대안으로 call_user_func() 함수를 사용하는 등 다른 방법을 고려해보아야 할 것으로 보이며 이에 대한 자세한 방법은 여기를 방문해 보시기 바랍니다.

*1  (PHP manual) This(__invoke method) is for consistency with other classes that implement calling magic, as this method is not used for calling the function.

*1  (PHP 매뉴얼) __invoke 메소드는 이 메소드를 호출하도록 구현된 다른 클래스들과의 일관성을 유지하기 위해 Closure 클래스에 포함시킨 것이며, 클로저를 호출하기 위해 사용되지는 않습니다.

클로저는 __invoke 메소드 호출로 인해 callable 타입으로 힌팅(type hinting)되어 함수의 매개변수로 전달될 수 있습니다.

답글 남기기