PHP 함수
PHP 함수 (Functions)
PHP에서 함수란 이름 붙여진 코드 묶음이에요. 한 번 정의해 두면 이름만 불러서 언제든 같은 동작을 재사용할 수 있죠. 이 문서는 PHP 공식 매뉴얼의 언어 레퍼런스 중 "Functions" 장을 바탕으로, 함수를 선언하고 인자를 주고받고 결과를 돌려받는 전 과정을 하나씩 풀어서 설명해요.
PHP의 함수는 네 가지 성격으로 요약할 수 있어요. 코드를 묶어 재사용하는 사용자 정의 함수, 인자를 주고받는 방식과 규칙인 파라미터와 인자, 결과를 돌려주는 반환값, 그리고 이름 없이 값을 담아 다니는 익명 함수와 화살표 함수까지요. 하나씩 차근차근 볼게요.
출처: PHP Manual — Language Reference: Functions (영문 공식 매뉴얼)
본문
사용자 정의 함수 (User-defined functions)
함수는 function 키워드, 이름, 그리고 쉼표(,)로 구분된 파라미터 목록(비어 있을 수도 있어요)을 괄호로 감싼 뒤, 중괄호로 본문을 묶어서 정의해요. 가장 기본적인 모양은 이렇습니다.
<?php
function foo($arg_1, $arg_2, /* ..., */ $arg_n)
{
echo "Example function.\n";
return $retval;
}
[!NOTE] PHP 8.0.0부터는 파라미터 목록 끝에 후행 쉼표를 쓸 수 있어요.
<?php function foo($arg_1, $arg_2,) { }
함수 본문 안에는 유효한 PHP 코드라면 무엇이든 들어갈 수 있어요. 다른 함수나 클래스 정의도 마찬가지예요.
함수 이름은 PHP의 다른 라벨들과 같은 규칙을 따라요. 유효한 함수 이름은 영문자나 밑줄(_)로 시작하고, 그 뒤로 영문자·숫자·밑줄이 몇 개든 붙을 수 있어요. 정규식으로 표현하면 ^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$이에요.
[!TIP] 사용자 영역의 네이밍 규칙에 대해서는 Userland Naming Guide를 참고하세요.
함수는 참조되기 전에 정의되어 있지 않아도 괜찮아요. 다만 아래 예시처럼 조건부로 정의하는 경우는 예외예요.
함수를 조건부로 정의한다면(아래 두 예시처럼), 그 정의가 호출보다 먼저 처리되는 순서를 지켜야 해요.
예시 #2 — 조건부 함수
<?php
$makefoo = true;
/* We can't call foo() from here since it doesn't exist yet, but we can call bar() */
if (function_exists('foo')) {
foo();
} else {
echo "Function foo does not exist (yet).\n";
}
bar();
if ($makefoo) {
function foo()
{
echo "I don't exist until program execution reaches me.\n";
}
}
/* Now we can safely call foo() since $makefoo evaluated to true */
if ($makefoo) foo();
function bar()
{
echo "I exist immediately upon program start.\n";
}
여기서 foo()는 프로그램 실행이 $makefoo가 참인 지점에 도달하기 전까진 존재하지 않아요. 반면 bar()는 실행이 시작되는 순간부터 존재하죠.
예시 #3 — 함수 안의 함수
<?php
function foo()
{
function bar()
{
echo "I don't exist until foo() is called.\n";
}
}
/* We can't call bar() yet since it doesn't exist. */
if (function_exists('bar')) {
bar();
}
foo();
/* Now we can call bar(),
foo()'s processing has
made it accessible. */
bar();
함수 안에서 정의된 bar()는 바깥의 foo()가 호출된 뒤에야 존재하게 돼요.
PHP의 모든 함수와 클래스는 전역 스코프를 가져요. 함수 내부에서 정의했더라도 밖에서 호출할 수 있고, 그 반대도 마찬가지예요.
PHP는 함수 오버로딩을 지원하지 않으며, 이미 선언된 함수를 되돌리거나(undefine) 다시 정의하는 것도 불가능해요.
[!NOTE] 함수 이름은 ASCII 문자
A부터Z까지 대소문자를 구분하지 않아요. 다만 관례적으로 선언할 때 쓴 그대로 호출하는 게 좋아요.
함수는 가변 인자 목록(variable number of arguments)과 기본 인자(default arguments)를 모두 지원해요. 자세한 내용은 func_num_args(), func_get_arg(), func_get_args() 함수 레퍼런스를 함께 보세요.
PHP에서는 재귀 함수 호출도 가능해요.
예시 #4 — 재귀 함수
<?php
function recursion($a)
{
if ($a < 20) {
echo "$a\n";
recursion($a + 1);
}
}
recursion(17);
[!NOTE] 재귀 호출 수준이 100~200을 넘어가면 스택이 터져 현재 스크립트가 종료될 수 있어요. 특히 무한 재귀는 프로그래밍 오류로 간주돼요.
함수 파라미터와 인자 (Function parameters and arguments)
파라미터는 함수 시그니처 안에서 선언돼요. 함수에 정보를 전달할 때는 인자 목록(argument list)을 쓰는데, 이는 쉼표로 구분된 표현식들의 나열이에요. 인자는 왼쪽에서 오른쪽으로 평가되고, 그 결과가 실제로 함수가 호출되기 전에 파라미터에 할당돼요(즉시 평가, eager evaluation).
PHP는 기본 방식인 값 전달(by value), 참조 전달(by reference), 그리고 기본 인자 값을 지원해요. 가변 길이 인자 목록과 명명된 인자(Named Arguments)도 지원하고요.
[!NOTE] PHP 7.3.0부터는 함수 호출의 인자 목록 끝에도 후행 쉼표를 쓸 수 있어요.
<?php $v = foo($arg_1, $arg_2,);
PHP 8.0.0부터는 함수 파라미터 목록의 끝에도 후행 쉼표를 쓸 수 있고, 이는 무시돼요. 파라미터가 많거나 변수 이름이 길어서 세로로 나열하기 편할 때 특히 유용하죠.
예시 #1 — 후행 쉼표가 있는 파라미터 목록
<?php
function takes_many_args(
$first_arg,
$second_arg,
$a_very_long_argument_name,
$arg_with_default = 5,
$again = 'a default string', // This trailing comma was not permitted before 8.0.0.
)
{
// ...
}
참조로 인자 전달하기
기본적으로 함수 인자는 값으로 전달돼요. 함수 안에서 인자 값을 바꿔도 함수 바깥의 값은 변하지 않죠. 함수가 인자를 수정할 수 있게 하려면 참조로 전달해야 해요.
함수의 파라미터를 항상 참조로 전달받게 하려면, 함수 정의에서 파라미터 이름 앞에 앰퍼샌드(&)를 붙이면 돼요.
예시 #2 — 참조로 인자 전달
<?php
function add_some_extra(&$string)
{
$string .= 'and something extra.';
}
$str = 'This is a string, ';
add_some_extra($str);
echo $str; // outputs 'This is a string, and something extra.'
참조로 전달될 것으로 기대되는 파라미터에 상수 표현식을 인자로 넘기는 것은 오류예요.
기본 파라미터 값 (Default parameter values)
함수는 변수를 할당하는 것과 비슷한 문법으로 파라미터에 기본 값을 정의할 수 있어요. 기본 값은 해당 파라미터에 인자를 넘기지 않을 때만 사용돼요. 주의할 점은 null을 넘기면 기본 값이 사용되지 않는다는 것이에요.
예시 #3 — 기본 파라미터 사용
<?php
function makecoffee($type = "cappuccino")
{
return "Making a cup of $type.\n";
}
echo makecoffee();
echo makecoffee(null);
echo makecoffee("espresso");
기본 파라미터 값으로는 스칼라 값, 배열, 특수 타입 null, 그리고 PHP 8.1.0부터는 new ClassName() 문법을 사용한 객체까지 가능해요.
예시 #4 — 스칼라가 아닌 타입을 기본 값으로
<?php
function makecoffee($types = array("cappuccino"), $coffeeMaker = NULL)
{
$device = is_null($coffeeMaker) ? "hands" : $coffeeMaker;
return "Making a cup of ".join(", ", $types)." with $device.\n";
}
echo makecoffee();
echo makecoffee(array("cappuccino", "lavazza"), "teapot");
예시 #5 — 객체를 기본 값으로 (PHP 8.1.0부터)
<?php
class DefaultCoffeeMaker {
public function brew() {
return "Making coffee.\n";
}
}
class FancyCoffeeMaker {
public function brew() {
return "Crafting a beautiful coffee just for you.\n";
}
}
function makecoffee($coffeeMaker = new DefaultCoffeeMaker)
{
return $coffeeMaker->brew();
}
echo makecoffee();
echo makecoffee(new FancyCoffeeMaker);
기본 값은 반드시 상수 표현식이어야 해요. 예를 들어 변수나 클래스 멤버, 함수 호출은 쓸 수 없어요.
선택적 파라미터는 필수 파라미터 뒤에 와야 해요. 그렇지 않으면 호출할 때 앞의 파라미터를 생략할 수 없거든요. 아래 예시를 볼게요.
예시 #6 — 기본 파라미터의 잘못된 사용
<?php
function makeyogurt($container = "bowl", $flavour)
{
return "Making a $container of $flavour yogurt.\n";
}
echo makeyogurt("raspberry"); // "raspberry" is $container, not $flavour
여기선 $flavour가 필수라서 앞의 $container를 건너뛸 수 없어요. 그래서 "raspberry"가 $container로 들어가 버리죠. 순서를 바꾸면 제대로 동작해요.
예시 #7 — 기본 파라미터의 올바른 사용
<?php
function makeyogurt($flavour, $container = "bowl")
{
return "Making a $container of $flavour yogurt.\n";
}
echo makeyogurt("raspberry"); // "raspberry" is $flavour
PHP 8.0.0부터는 명명된 인자를 사용해서 여러 선택적 파라미터를 한 번에 건너뛸 수 있어요.
예시 #8 — 명명된 인자로 선택적 파라미터 건너뛰기
<?php
function makeyogurt($container = "bowl", $flavour = "raspberry", $style = "Greek")
{
return "Making a $container of $flavour $style yogurt.\n";
}
echo makeyogurt(style: "natural");
PHP 8.0.0부터는 선택적 파라미터 뒤에 필수 파라미터를 선언하는 것이 deprecated 처리돼요. 보통은 어차피 사용되지 않을 기본 값을 제거하면 해결돼요. 다만 Type $param = null처럼 기본 값 null이 타입을 암시적으로 nullable로 만드는 형태는 예외인데, 이 사용법은 PHP 8.4.0부터 deprecated이므로 명시적으로 nullable 타입을 쓰는 게 권장돼요.
예시 #9 — 필수 파라미터 뒤에 선택적 파라미터 선언
<?php
function foo($a = [], $b) {} // Default not used; deprecated as of PHP 8.0.0
function foo($a, $b) {} // Functionally equivalent, no deprecation notice
function bar(A $a = null, $b) {} // As of PHP 8.1.0, $a is implicitly required
// (because it comes before the required one),
// but implicitly nullable (deprecated as of PHP 8.4.0),
// because the default parameter value is null
function bar(?A $a, $b) {} // Recommended
[!NOTE] PHP 7.1.0부터 기본 값이 지정되지 않은 파라미터를 생략하면
ArgumentCountError가 발생해요. 이전 버전에서는 경고(Warning)만 났었죠.
[!NOTE] 인자를 참조로 받는 파라미터도 기본 값을 가질 수 있어요.
가변 길이 인자 목록 (Variable-length argument lists)
PHP는 사용자 정의 함수에서 ... 토큰을 이용해 가변 길이 인자 목록을 지원해요.
파라미터 목록에 ...를 넣으면 함수가 가변 개수의 인자를 받는다는 뜻이에요. 넘겨진 인자들은 배열로 해당 변수에 담겨요.
예시 #10 — ...로 가변 인자에 접근
<?php
function sum(...$numbers) {
$acc = 0;
foreach ($numbers as $n) {
$acc += $n;
}
return $acc;
}
echo sum(1, 2, 3, 4);
...는 함수를 호출할 때 배열이나 Traversable 변수(또는 리터럴)를 인자 목록으로 펼치는(unpack) 데도 쓸 수 있어요.
예시 #11 — ...로 인자 제공
<?php
function add($a, $b) {
return $a + $b;
}
echo add(...[1, 2])."\n";
$a = [1, 2];
echo add(...$a);
... 앞에는 일반적인 위치 파라미터를 둘 수 있어요. 이 경우 위치 파라미터와 맞지 않는 뒤쪽의 남은 인자들만 ...가 만든 배열에 들어가요.
또한 ... 앞에 타입 선언을 붙일 수도 있어요. 붙이면 ...로 받는 모든 인자가 그 타입을 만족해야 해요.
예시 #12 — 타입이 선언된 가변 인자
<?php
function total_intervals($unit, DateInterval ...$intervals) {
$time = 0;
foreach ($intervals as $interval) {
$time += $interval->$unit;
}
return $time;
}
$a = new DateInterval('P1D');
$b = new DateInterval('P2D');
echo total_intervals('d', $a, $b).' days';
// This will fail, since null isn't a DateInterval object.
echo total_intervals('d', null);
마지막으로, 가변 인자는 앞에 앰퍼샌드(&)를 붙여 참조로 전달할 수도 있어요.
명명된 인자 (Named Arguments)
PHP 8.0.0에서 기존 위치 파라미터의 확장으로 명명된 인자가 도입됐어요. 명명된 인자는 파라미터의 위치가 아니라 이름을 기준으로 인자를 전달해요. 덕분에 인자의 의미가 스스로 설명되고, 순서에 영향을 받지 않으며, 기본 값을 자유롭게 건너뛸 수 있어요.
명명된 인자는 값 앞에 파라미터 이름과 콜론(:)을 붙여서 전달해요. 예약 키워드도 파라미터 이름으로 쓸 수 있어요. 파라미터 이름은 반드시 식별자(identifier)여야 하며, 동적으로 지정하는 것은 허용되지 않아요.
예시 #13 — 명명된 인자 문법
<?php
myFunction(paramName: $value);
array_foobar(array: $value);
// NOT supported.
function_name($variableStoringParamName: $value);
예시 #14 — 위치 인자 대 명명된 인자
<?php
// Using positional arguments:
array_fill(0, 100, 50);
// Using named arguments:
array_fill(start_index: 0, count: 100, value: 50);
명명된 인자를 전달하는 순서는 상관없어요.
예시 #15 — 위 예시와 동일하지만 파라미터 순서가 다른 경우
<?php
array_fill(value: 50, count: 100, start_index: 0);
명명된 인자는 위치 인자와 함께 쓸 수 있어요. 이 경우 명명된 인자는 반드시 위치 인자 뒤에 와야 해요. 그리고 함수의 선택적 인자 중 일부만 순서와 무관하게 지정하는 것도 가능해요.
예시 #16 — 명명된 인자와 위치 인자 조합
<?php
htmlspecialchars($string, double_encode: false);
// Same as
htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8', false);
같은 명명된 파라미터에 인자를 여러 번 전달하면 Error 예외가 발생해요.
예시 #17 — 같은 명명된 파라미터에 인자를 여러 번 전달할 때의 오류
<?php
function foo($param) { ... }
foo(param: 1, param: 2);
// Error: Named parameter $param overwrites previous argument
foo(1, param: 2);
// Error: Named parameter $param overwrites previous argument
PHP 8.1.0부터는 인자를 펼친 뒤에도 명명된 인자를 사용할 수 있어요. 단, 명명된 인자가 이미 펼쳐진 인자를 덮어써서는 안 돼요.
예시 #18 — 펼친 뒤 명명된 인자 사용
<?php
function foo($a, $b, $c = 3, $d = 4) {
return $a + $b + $c + $d;
}
var_dump(foo(...[1, 2], d: 40)); // 46
var_dump(foo(...['b' => 2, 'a' => 1], d: 40)); // 46
var_dump(foo(...[1, 2], b: 20)); // Fatal error. Named parameter $b overwrites previous argument
반환값 (Returning values)
함수는 선택적인 return 문을 사용해 값을 반환해요. 배열이나 객체를 포함해 어떤 타입이든 반환할 수 있어요. return은 함수의 실행을 즉시 끝내고, 호출한 위치로 제어권을 돌려줘요. 자세한 내용은 return 문서를 참고하세요.
[!NOTE]
return을 생략하면 함수는null값을 반환해요.
예시 #1 — return의 사용
<?php
function square($num)
{
return $num * $num;
}
echo square(4); // outputs '16'.
함수는 여러 값을 한 번에 반환할 수는 없어요. 하지만 배열을 반환하면 비슷한 결과를 얻을 수 있죠.
예시 #2 — 여러 값을 얻기 위해 배열 반환
<?php
function small_numbers()
{
return [0, 1, 2];
}
// Array destructuring will collect each member of the array individually
[$zero, $one, $two] = small_numbers();
var_dump($zero, $one, $two);
// Prior to 7.1.0, the only equivalent alternative is using list() construct
list($zero, $one, $two) = small_numbers();
함수에서 참조를 반환하려면 함수 선언과 반환 값을 변수에 할당할 때 모두 참조 연산자 &를 써야 해요.
예시 #3 — 함수에서 참조 반환
<?php
function &returns_reference()
{
return $someref;
}
$newref =& returns_reference();
참조에 대한 자세한 내용은 References Explained 문서를 확인해 보세요.
가변 함수 (Variable functions)
PHP는 가변 함수 개념을 지원해요. 변수 이름 뒤에 괄호를 붙이면, PHP는 그 변수가 평가된 값과 같은 이름의 함수를 찾아 실행하려고 시도해요. 이를 이용해 콜백이나 함수 테이블 같은 것을 구현할 수 있어요.
가변 함수는 echo, print, unset(), isset(), empty(), include, require 같은 언어 구조와는 함께 동작하지 않아요. 이런 구조를 가변 함수로 쓰고 싶다면 래퍼 함수를 만들어 활용하세요.
예시 #1 — 가변 함수 예시
<?php
function foo() {
echo "In foo()\n";
}
function bar($arg = '')
{
echo "In bar(); argument was '$arg'.\n";
}
// This is a wrapper function around echo
function echoit($string)
{
echo $string;
}
$func = 'foo';
$func(); // This calls foo()
$func = 'bar';
$func('test'); // This calls bar()
$func = 'echoit';
$func('test'); // This calls echoit()
객체의 메서드도 가변 함수 문법으로 호출할 수 있어요.
예시 #2 — 가변 메서드 예시
<?php
class Foo
{
function Variable()
{
$name = 'Bar';
$this->$name(); // This calls the Bar() method
}
function Bar()
{
echo "This is Bar";
}
}
$foo = new Foo();
$funcname = "Variable";
$foo->$funcname(); // This calls $foo->Variable()
정적 메서드를 호출할 때는 함수 호출이 정적 프로퍼티 연산자보다 우선해요.
예시 #3 — 정적 프로퍼티가 있는 가변 메서드 예시
<?php
class Foo
{
static $variable = 'static property';
static function Variable()
{
echo "Method Variable called\n";
}
}
echo Foo::$variable ."\n"; // This prints 'static property'. It does need a $variable in this scope.
$variable = "Variable";
Foo::$variable(); // This calls $foo->Variable() reading $variable in this scope.
위 예시의 출력은 다음과 같아요.
static property
Method Variable called
예시 #4 — 복잡한 호출 가능(callable) 예시
<?php
class Foo
{
static function bar()
{
echo "bar\n";
}
function baz()
{
echo "baz\n";
}
}
$func = array("Foo", "bar");
$func(); // prints "bar"
$func = array(new Foo, "baz");
$func(); // prints "baz"
$func = "Foo::bar";
$func(); // prints "bar"
더 알아보기
is_callable()call_user_func()function_exists()- 가변 변수(variable variables)
내장 함수 (Internal functions)
PHP는 많은 함수와 구조를 기본으로 내장하고 있어요. 하지만 일부 함수는 특정 PHP 확장이 컴파일되어 있어야만 사용할 수 있어요. 그런 확장이 없으면 치명적인 "undefined function" 오류가 나요. 예를 들어 imagecreatetruecolor() 같은 이미지 함수를 쓰려면 PHP가 GD 지원으로 컴파일되어 있어야 하고, mysqli_connect()를 쓰려면 MySQLi 지원이 필요해요.
모든 PHP 버전에 포함되는 핵심 함수(core functions)도 많아요. 문자열·변수 함수가 그 예죠. phpinfo()나 get_loaded_extensions()를 호출하면 어떤 확장이 로드되어 있는지 알 수 있어요. 또한 많은 확장이 기본으로 활성화되어 있고, PHP 매뉴얼은 확장별로 나뉘어 있다는 점도 알아두면 좋아요. PHP 구성·설치 방법은 설정(configuration), 설치(installation), 그리고 각 확장 장(chapter)을 참고하세요.
함수의 프로토타입을 읽고 이해하는 방법에 대해서는 매뉴얼의 "how to read a function definition" 섹션에서 설명해요. 함수가 무엇을 반환하는지, 아니면 전달받은 값을 직접 수정하는지 아는 게 중요해요. 예를 들어 str_replace()는 수정된 문자열을 반환하고, usort()는 전달받은 변수 자체를 수정해요. 각 매뉴얼 페이지에는 함수 파라미터, 동작 변화, 성공·실패 시 반환값, 가용성 정보 같은 구체적인 내용이 담겨 있어요. 이런 미묘한 차이를 아는 게 올바른 PHP 코드를 쓰는 핵심이에요.
[!NOTE] 함수에 기대하지 않는 타입의 파라미터를 넘기면(예: 문자열이 기대되는 곳에 배열 전달) 함수의 반환값은 정의되지 않아요. 대개
null을 반환하지만 이는 관례일 뿐이라 믿을 수 없어요. PHP 8.0.0부터 이런 경우TypeError예외가 발생하도록 되어 있어요.
[!NOTE] 내장 함수의 스칼라 타입은 강제 모드(coercive mode)에서 기본적으로 nullable이에요. PHP 8.1.0부터는 nullable로 선언되지 않은 내장 함수 파라미터에
null을 전달하는 것이 권장되지 않고, 사용자 정의 함수와의 동작을 맞추기 위해 강제 모드에서도 폐기(deprecation) 공지가 발생해요. 예를 들어strlen()함수는 파라미터$string이 nullable이 아닌 문자열이기를 기대해요. 역사적인 이유로 PHP는 강제 모드에서 이 파라미터에null을 전달하는 것을 허용했고, 그 값은 문자열로 암시적으로 캐스팅되어""값이 됐어요. 반면 엄격 모드(strict mode)에서는TypeError가 발생해요.
<?php
var_dump(strlen(null));
// "Deprecated: Passing null to parameter #1 ($string) of type string is deprecated" as of PHP 8.1.0
// int(0)
var_dump(str_contains("foobar", null));
// "Deprecated: Passing null to parameter #2 ($needle) of type string is deprecated" as of PHP 8.1.0
// bool(true)
더 알아보기
function_exists()- 함수 레퍼런스(function reference)
get_extension_funcs()dl()
익명 함수 (Anonymous functions)
익명 함수는 클로저(closure)라고도 불리며, 이름이 없는 함수를 만들 수 있게 해줘요. 주로 callable 파라미터의 값으로 가장 유용하지만, 그 외에도 많은 용도가 있어요.
익명 함수는 Closure 클래스로 구현돼요.
예시 #1 — 익명 함수 예시
<?php
echo preg_replace_callback('~-([a-z])~', function ($match) {
return strtoupper($match[1]);
}, 'hello-world');
// outputs helloWorld
클로저는 변수의 값으로도 쓸 수 있어요. PHP는 그런 표현식을 자동으로 내장 Closure 클래스의 인스턴스로 변환해요. 클로저를 변수에 할당하는 문법은 다른 할당과 같고, 마지막 세미콜론도 포함해요.
예시 #2 — 익명 함수 변수 할당 예시
<?php
$greet = function($name) {
printf("Hello %s\r\n", $name);
};
$greet('World');
$greet('PHP');
클로저는 부모 스코프의 변수를 상속할 수도 있어요. 그런 변수는 반드시 use 언어 구조로 전달해야 해요. PHP 7.1부터 이 변수들에는 슈퍼글로벌, $this, 파라미터와 같은 이름의 변수가 포함되어선 안 돼요. 함수의 반환 타입 선언은 use 절 뒤에 와야 해요.
예시 #3 — 부모 스코프에서 변수 상속
<?php
// A variable defined in the parent scope
$message = 'hello';
// $message is not inherited and is undefined due to the
// lack of a "use" clause
$example = function () {
var_dump($message);
};
$example();
// $message is inherited from the parent scope due to the
// "use" clause
$example = function () use ($message) {
var_dump($message);
};
$example();
// Inherited variable's value is from when the function
// is defined, not when called
$message = 'world';
$example();
// Reset $message
$message = 'hello';
// Inheriting by reference: a change made in the parent scope
// after the closure is defined is visible inside it
$example = function () use (&$message) {
var_dump($message);
};
$example();
$message = 'world';
$example();
// Closures can also accept regular arguments
$example = function ($arg) use ($message) {
var_dump($arg . ' ' . $message);
};
$example("hello");
// Closures can return values like any other function; the
// return type declaration comes after the use clause
$example = function () use ($message): array {
return ["hello", $message];
};
var_dump($example());
위 예시의 출력은 다음과 같아요.
Warning: Undefined variable $message in script on line 8
NULL
string(5) "hello"
string(5) "hello"
string(5) "hello"
string(5) "world"
string(11) "hello world"
array(2) {
[0]=>
string(5) "hello"
[1]=>
string(5) "world"
}
PHP 8.0.0부터 스코프 상속 변수 목록의 끝에도 후행 쉼표를 쓸 수 있고, 이는 무시돼요.
부모 스코프의 변수를 상속받는 것은 전역 변수를 사용하는 것과 다르다는 점을 짚고 갈게요. 전역 변수는 어느 함수가 실행 중이든 상관없이 같은 전역 스코프에 존재해요. 반면 클로저의 부모 스코프는 클로저가 선언된 함수의 스코프예요(호출된 곳이 아니에요). 아래 예시를 볼게요.
예시 #4 — 클로저와 스코프
<?php
// A basic shopping cart which contains a list of added products
// and the quantity of each product. Includes a method which
// calculates the total price of the items in the cart using a
// closure as a callback.
class Cart
{
const PRICE_BUTTER = 1.00;
const PRICE_MILK = 3.00;
const PRICE_EGGS = 6.95;
protected $products = array();
public function add($product, $quantity)
{
$this->products[$product] = $quantity;
}
public function getQuantity($product)
{
return isset($this->products[$product]) ? $this->products[$product] :
FALSE;
}
public function getTotal($tax)
{
$total = 0.00;
$callback =
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);
}
}
$my_cart = new Cart;
// Add some items to the cart
$my_cart->add('butter', 1);
$my_cart->add('milk', 3);
$my_cart->add('eggs', 6);
// Print the total with a 5% sales tax.
print $my_cart->getTotal(0.05) . "\n";
// The result is 54.29
예시 #5 — $this의 자동 바인딩
<?php
class Test
{
public function testing()
{
return function() {
var_dump($this);
};
}
}
$object = new Test;
$function = $object->testing();
$function();
위 예시의 출력은 다음과 같아요.
object(Test)#1 (0) {
}
클래스의 컨텍스트 안에서 클로저를 선언하면 현재 클래스가 자동으로 바인딩되어, 함수의 스코프 안에서 $this를 쓸 수 있어요. 이 자동 바인딩을 원하지 않으면 정적 익명 함수를 사용하면 돼요.
정적 익명 함수 (Static anonymous functions)
익명 함수는 static으로 선언할 수 있어요. 이렇게 하면 현재 클래스가 자동으로 바인딩되지 않고, 런타임에 객체를 바인딩하는 것도 불가능해져요.
예시 #6 — 정적 익명 함수 안에서 $this 사용 시도
<?php
class Foo
{
function __construct()
{
$func = static function() {
var_dump($this);
};
$func();
}
};
new Foo();
위 예시의 출력은 다음과 같아요.
Fatal error: Uncaught Error: Using $this when not in object context in script:7
Stack trace:
#0 script(9): Foo::{closure:Foo::__construct():6}()
#1 script(12): Foo->__construct()
#2 {main}
thrown in script on line 7
예시 #7 — 정적 익명 함수에 객체 바인딩 시도
<?php
$func = static function() {
// function body
};
$func = $func->bindTo(new stdClass);
$func();
위 예시의 출력은 다음과 같아요.
Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in script on line 5
Fatal error: Uncaught Error: Value of type null is not callable in script:6
Stack trace:
#0 {main}
thrown in script on line 6
변경 사항 (Changelog)
| Version | Description |
|---|---|
| 8.5.0 | Closures can now be used in constant expressions (e.g. default property values, attribute parameters, constants). |
| 8.3.0 | Closures created from magic methods can accept named parameters. |
| 7.1.0 | Anonymous functions may not close over superglobals, $this, or any variable with the same name as a parameter. |
[!NOTE] 클로저 안에서도
func_num_args(),func_get_arg(),func_get_args()를 사용할 수 있어요.
화살표 함수 (Arrow Functions)
화살표 함수는 PHP 7.4에서 익명 함수를 더 간결하게 쓰는 문법으로 도입됐어요. 익명 함수와 화살표 함수는 모두 Closure 클래스로 구현돼요.
화살표 함수의 기본 형태는 fn (argument_list) => expr이에요.
화살표 함수는 익명 함수와 같은 기능을 지원하되, 부모 스코프의 변수를 사용하는 것이 항상 자동이라는 점만 달라요.
표현식에 쓰인 변수가 부모 스코프에 정의되어 있으면 그 변수는 암시적으로 값으로 캡처돼요. 아래 예시에서 $fn1과 $fn2는 같은 동작을 해요.
예시 #1 — 화살표 함수는 변수를 값으로 자동 캡처
<?php
$y = 1;
$fn1 = fn($x) => $x + $y;
// equivalent to using $y by value:
$fn2 = function ($x) use ($y) {
return $x + $y;
};
var_export($fn1(3));
위 예시의 출력은 다음과 같아요.
4
화살표 함수가 중첩되어 있어도 마찬가지로 동작해요.
예시 #2 — 중첩되어도 자동으로 값 캡처
<?php
$z = 1;
$fn = fn($x) => fn($y) => $x * $y + $z;
// Outputs 51
var_export($fn(5)(10));
익명 함수와 마찬가지로, 화살표 함수 문법은 임의의 함수 시그니처를 허용해요. 파라미터·반환 타입, 기본 값, 가변 인자, 그리고 참조 전달·반환까지 모두 가능하죠. 아래는 전부 유효한 화살표 함수 예시예요.
예시 #3 — 화살표 함수 예시들
<?php
fn(array $x) => $x;
static fn($x): int => $x;
fn($x = 42) => $x;
fn(&$x) => $x;
fn&($x) => $x;
fn($x, ...$rest) => $rest;
화살표 함수는 **값 바인딩(by-value binding)**을 사용해요. 대략적으로 화살표 함수 안에서 쓰인 모든 변수 $x에 대해 use($x)를 수행하는 것과 같아요. 값 바인딩이라 화살표 함수가 바깥 스코프의 값을 수정하는 것은 불가능해요. 참조 바인딩이 필요하면 익명 함수를 대신 사용하면 돼요.
예시 #4 — 바깥 스코프의 값은 화살표 함수로 수정 불가
<?php
$x = 1;
$fn = fn() => $x++; // Has no effect
$fn();
var_export($x); // Outputs 1
변경 사항 (Changelog)
| Version | Description |
|---|---|
| 7.4.0 | Arrow functions became available. |
[!NOTE] 화살표 함수 안에서도
func_num_args(),func_get_arg(),func_get_args()를 사용할 수 있어요.
일급 호출 가능 문법 (First class callable syntax)
일급 호출 가능 문법(first class callable syntax)은 PHP 8.1.0에서 도입됐어요. callable로부터 익명 함수를 만드는 한 가지 방법이에요. 기존에 문자열과 배열을 사용하던 callable 문법을 대체하죠. 이 문법의 장점은 정적 분석에 접근 가능하고, callable을 얻은 지점의 스코프를 사용한다는 점이에요.
CallableExpr(...) 문법으로 callable에서 Closure 객체를 만들 수 있어요. 여기서 CallableExpr는 PHP 문법에서 직접 호출할 수 있는 어떤 표현식이든 허용해요.
예시 #1 — 간단한 일급 호출 가능 문법
<?php
class Foo {
public function method() {}
public static function staticmethod() {}
public function __invoke() {}
}
$obj = new Foo();
$classStr = 'Foo';
$methodStr = 'method';
$staticmethodStr = 'staticmethod';
$f1 = strlen(...);
$f2 = $obj(...); // invokable object
$f3 = $obj->method(...);
$f4 = $obj->$methodStr(...);
$f5 = Foo::staticmethod(...);
$f6 = $classStr::$staticmethodStr(...);
// traditional callable using string, array
$f7 = 'strlen'(...);
$f8 = [$obj, 'method'](...);
$f9 = [Foo::class, 'staticmethod'](...);
[!NOTE] 여기서
...는 문법의 일부이지, 생략 기호가 아니에요.
CallableExpr(...)는 Closure::fromCallable()과 같은 의미를 가져요. 즉, 문자열·배열을 쓰는 callable과 달리 CallableExpr(...)는 생성된 지점의 스코프를 존중해요.
예시 #2 — CallableExpr(...)와 전통적인 callable의 스코프 비교
<?php
class Foo {
public function getPrivateMethod() {
return [$this, 'privateMethod'];
}
private function privateMethod() {
echo __METHOD__, "\n";
}
}
$foo = new Foo;
$privateMethod = $foo->getPrivateMethod();
$privateMethod();
// Fatal error: Call to private method Foo::privateMethod() from global scope
// This is because call is performed outside from Foo and visibility will be checked from this point.
전통적인 callable([$this, 'privateMethod'])은 호출이 전역 스코프에서 일어나기 때문에 private 메서드에 접근하지 못해요. 하지만 아래처럼 callable을 얻은 지점의 스코프를 기준으로 삼는 Closure::fromCallable() 방식은 접근할 수 있어요.
<?php
class Foo1 {
public function getPrivateMethod() {
// Uses the scope where the callable is acquired.
return $this->privateMethod(...); // identical to Closure::fromCallable([$this, 'privateMethod']);
}
private function privateMethod() {
echo __METHOD__, "\n";
}
}
$foo1 = new Foo1;
$privateMethod = $foo1->getPrivateMethod();
$privateMethod(); // Foo1::privateMethod
[!NOTE] 이 문법으로 객체를 생성(예:
new Foo(...))하는 것은 지원되지 않아요.new Foo()는 호출(call)로 간주되지 않기 때문이에요.
[!NOTE] 일급 호출 가능 문법은 nullsafe 연산자와는 함께 쓸 수 없어요. 아래 둘 다 컴파일 타임 오류가 발생해요.
<?php $obj?->method(...); $obj?->prop->method(...);
더 알아보기
- PHP 매뉴얼 — 사용자 정의 함수
- PHP 매뉴얼 — 함수 파라미터와 인자
- PHP 매뉴얼 — 반환값
- PHP 매뉴얼 — 가변 함수
- PHP 매뉴얼 — 내장 함수
- PHP 매뉴얼 — 익명 함수
- PHP 매뉴얼 — 화살표 함수
- PHP 매뉴얼 — 일급 호출 가능 문법
Closure::fromCallable()function_exists(),is_callable(),call_user_func()