익명 함수는 클로저라고도 하며, 이름이 없는 함수입니다. 값이 와야 하는 자리에 function ($param) { ... }를 씁니다. 변수에 대입하거나, 다른 함수에 넘기거나, 반환합니다. use ($var)로 주변 코드의 변수를 가지고 다닐 수 있습니다.
<?php
$greet = function (string $name): string {
return "Hello, $name";
}; // the semicolon ends the assignment
echo $greet('Ada'), "\n";
$greeting = 'Welcome';
$welcome = function (string $name) use ($greeting): string {
return "$greeting, $name";
};
echo $welcome('Linus'), "\n";
var_dump($greet instanceof Closure);Master PHP the hands-on way
Interactive lessons, real projects, and instant feedback.
START NOW
모든 익명 함수는 내장 클래스 Closure의 객체이므로 저장하고, 전달하고, 나중에 $greet(...)로 호출할 수 있습니다.
use 키워드는 값으로 캡처한다
클로저는 스스로 바깥 변수를 볼 수 없습니다. use ($x)는 클로저가 실행될 때가 아니라 만들어지는 순간에 $x의 값을 클로저 안으로 복사합니다.
<?php
$discount = 10;
$apply = function (float $price) use ($discount): float {
return $price - $discount;
};
$discount = 50; // changed after the closure was created
echo $apply(100), "\n"; // still uses 10
// Several variables
$currency = 'EUR';
$rate = 1.08;
$convert = function (float $usd) use ($rate, $currency): string {
return number_format($usd / $rate, 2) . " $currency";
};
echo $convert(54), "\n";
참조로 use: &$var
use (&$x)는 복사본이 아니라 변수 자체를 클로저에 줍니다. 클로저 안의 변경이 바깥에서 보이고, 바깥의 변경도 안에서 보입니다. 콜백이 합계를 더하거나 카운터를 유지하는 방법이 이것입니다.
<?php
$total = 0;
$addToTotal = function (int $amount) use (&$total): void {
$total += $amount;
};
$addToTotal(10);
$addToTotal(32);
echo $total, "\n";
// A counter that remembers its state between calls
function makeCounter(): Closure
{
$count = 0;
return function () use (&$count): int {
return ++$count;
};
}
$next = makeCounter();
echo $next(), $next(), $next(), "\n";
$other = makeCounter(); // its own $count
echo $other(), "\n";
makeCounter()를 호출할 때마다 새 $count가 만들어지고, 반환된 클로저가 함수가 반환된 뒤에도 그것을 살려 둡니다. "클로저"라는 이름이 바로 이것을 가리킵니다. 함수가 필요한 변수를 감싸 둡니다.
클로저를 콜백으로 넘기기
array_map(), array_filter(), usort(), array_walk() 같은 함수는 함수를 인수로 받습니다. 클로저를 쓰면 그 자리에서 필요한 만큼의 문장으로 작성할 수 있습니다.
<?php
$products = [
['name' => 'Laptop', 'price' => 1200, 'stock' => 3],
['name' => 'Mouse', 'price' => 25, 'stock' => 0],
['name' => 'Monitor', 'price' => 300, 'stock' => 7],
];
$minPrice = 100;
$available = array_filter($products, function ($p) use ($minPrice) {
return $p['stock'] > 0 && $p['price'] >= $minPrice;
});
usort($available, function ($a, $b) {
return $b['price'] <=> $a['price']; // most expensive first
});
$labels = array_map(function ($p) {
$label = strtoupper($p['name']);
return "$label ({$p['stock']} left)";
}, $available);
print_r($labels);
본문이 표현식 하나라면 fn($p) => $p['price'] 같은 화살표 함수 가 더 짧고 use도 필요 없습니다.
callable: 함수 이름, 메서드, strlen(...)
callable 타입의 매개변수는 클로저 이상을 받습니다. 함수 이름이 든 문자열, 객체와 메서드 이름이 든 배열, 클래스 이름과 정적 메서드 이름이 든 배열도 모두 해당합니다. PHP 8.1부터 일급 callable 문법 strlen(...)은 이 중 무엇이든 바로 Closure로 바꿉니다.
<?php
class Formatter
{
public function shout(string $s): string { return strtoupper($s) . '!'; }
public static function whisper(string $s): string { return strtolower($s) . '...'; }
}
$words = ['Hello', 'World'];
$f = new Formatter();
print_r(array_map('strtoupper', $words)); // function name
print_r(array_map([$f, 'shout'], $words)); // object method
print_r(array_map([Formatter::class, 'whisper'], $words)); // static method
// PHP 8.1 first-class callable syntax
$len = strlen(...);
$shout = $f->shout(...);
$whisper = Formatter::whisper(...);
echo $len('closure'), ' ', $shout('hey'), ' ', $whisper('PSST'), "\n";
function apply(callable $fn, string $value): string
{
return $fn($value);
}
echo apply('ucfirst', 'php'), "\n";
var_dump(is_callable('no_such_function'));
(...) 형태는 그 줄이 실행될 때 검사되므로 오타가 있으면 그 자리에서 Error를 던집니다. 'strtoupr' 같은 문자열은 누군가 호출하려 하기 전까지 그냥 문자열일 뿐입니다.
클래스 안의 클로저: $this와 static
메서드 안에서 만든 클로저는 $this가 자동으로 바인딩되므로 객체의 속성을 쓸 수 있습니다. $this가 필요 없다면 static function으로 표시하세요. 그러면 클로저가 객체를 붙잡아 두는 일도 막습니다.
<?php
class Cart
{
private array $items = [];
private float $taxRate = 0.2;
public function add(string $name, float $price): void
{
$this->items[$name] = $price;
}
public function pricesWithTax(): array
{
// $this is available inside the closure
return array_map(function (float $price): float {
return round($price * (1 + $this->taxRate), 2);
}, $this->items);
}
public function names(): array
{
return array_map(static function (string $name): string {
return ucfirst($name);
}, array_keys($this->items));
}
}
$cart = new Cart();
$cart->add('book', 12.5);
$cart->add('pen', 1.99);
print_r($cart->pricesWithTax());
print_r($cart->names());
use 빠뜨리기
가장 흔한 클로저 버그는 바깥 변수를 use에 나열하지 않고 쓰는 것입니다. 클로저 안에서는 그 변수가 존재하지 않습니다.
$minAge = 18;
$adults = array_filter($people, function ($p) {
return $p['age'] >= $minAge; // $minAge is not in use (...)
});
PHP prints: Warning: Undefined variable $minAge
안에서 $minAge는 null이고, 어떤 나이든 >= null 비교는 true이므로 모든 사람이 필터를 통과합니다. 경고를 숨기는 서버에서는 아무것도 알려 주지 않습니다. 변수를 use에 추가하세요.
<?php
$people = [['name' => 'Ada', 'age' => 36], ['name' => 'Tom', 'age' => 12]];
$minAge = 18;
$adults = array_filter($people, function ($p) use ($minAge) {
return $p['age'] >= $minAge;
});
print_r(array_column($adults, 'name'));
// Or an arrow function, which captures $minAge automatically
$adults2 = array_filter($people, fn($p) => $p['age'] >= $minAge);
print_r(array_column($adults2, 'name'));