PHP 匿名函數與閉包 (Anonymous Function & Closure)
匿名函數(又稱 Lambda 函數)是沒有名稱的函數,可以儲存在變數中或作為參數傳遞。閉包 (Closure) 是可以存取其定義範圍外變數的匿名函數。
基本語法
<?php
// 匿名函數指派給變數
$greet = function($name) {
return "Hello, $name!";
};
// 呼叫匿名函數
echo $greet("Alice"); // Hello, Alice!
?>
作為回呼函數
匿名函數最常見的用途是作為回呼函數 (callback):
<?php
$numbers = [1, 2, 3, 4, 5];
// array_map 使用匿名函數
$doubled = array_map(function($n) {
return $n * 2;
}, $numbers);
print_r($doubled); // [2, 4, 6, 8, 10]
// array_filter 使用匿名函數
$evens = array_filter($numbers, function($n) {
return $n % 2 === 0;
});
print_r($evens); // [2, 4]
// usort 使用匿名函數
$names = ['Charlie', 'Alice', 'Bob'];
usort($names, function($a, $b) {
return strcmp($a, $b);
});
print_r($names); // ['Alice', 'Bob', 'Charlie']
?>
閉包與 use 關鍵字
使用 use 關鍵字可以讓匿名函數存取外部變數:
<?php
$message = "Hello";
// 使用 use 捕獲外部變數
$greet = function($name) use ($message) {
return "$message, $name!";
};
echo $greet("Alice"); // Hello, Alice!
?>
傳值捕獲(預設)
變數的值會被複製到閉包中:
<?php
$count = 0;
$increment = function() use ($count) {
$count++;
echo "閉包內:$count\n";
};
$increment(); // 閉包內:1
$increment(); // 閉包內:1(每次都是從 0 開始)
echo "閉包外:$count"; // 閉包外:0(原變數不受影響)
?>
傳址捕獲
使用 & 可以傳址捕獲,閉包內的修改會影響原變數:
<?php
$count = 0;
$increment = function() use (&$count) {
$count++;
echo "閉包內:$count\n";
};
$increment(); // 閉包內:1
$increment(); // 閉包內:2
$increment(); // 閉包內:3
echo "閉包外:$count"; // 閉包外:3(原變數被修改)
?>
捕獲多個變數
<?php
$greeting = "Hello";
$punctuation = "!";
$greet = function($name) use ($greeting, $punctuation) {
return "$greeting, $name$punctuation";
};
echo $greet("Alice"); // Hello, Alice!
?>
閉包作為回傳值
函數可以回傳閉包,實現工廠模式:
<?php
// 建立乘法器
function createMultiplier($factor) {
return function($number) use ($factor) {
return $number * $factor;
};
}
$double = createMultiplier(2);
$triple = createMultiplier(3);
echo $double(5); // 10
echo $triple(5); // 15
?>
函數產生器
<?php
// 建立計數器
function createCounter($start = 0) {
$count = $start;
return function() use (&$count) {
return $count++;
};
}
$counter = createCounter(1);
echo $counter(); // 1
echo $counter(); // 2
echo $counter(); // 3
?>
Closure 類別
所有匿名函數都是 Closure 類別的實例:
<?php
$fn = function() {};
var_dump($fn instanceof Closure); // bool(true)
echo get_class($fn); // Closure
?>
Closure::bind() 和 Closure::bindTo()
可以動態改變閉包的 $this 和作用域:
<?php
class User {
private string $name = "Alice";
}
$getName = function() {
return $this->name;
};
$user = new User();
// 綁定閉包到物件
$boundGetName = Closure::bind($getName, $user, User::class);
echo $boundGetName(); // Alice
// 或使用 bindTo
$boundGetName2 = $getName->bindTo($user, User::class);
echo $boundGetName2(); // Alice
?>
Closure::call() (PHP 7+)
臨時綁定並執行閉包:
<?php
class User {
private string $name = "Alice";
}
$getName = function() {
return $this->name;
};
$user = new User();
echo $getName->call($user); // Alice
?>
型別宣告
callable 型別
<?php
function apply(callable $func, $value) {
return $func($value);
}
echo apply(function($n) { return $n * 2; }, 5); // 10
echo apply('strtoupper', 'hello'); // HELLO
?>
Closure 型別
如果只接受匿名函數,可以使用 Closure 型別:
<?php
function process(Closure $callback) {
return $callback();
}
process(function() { echo "執行中"; });
// process('strlen'); // TypeError:只接受 Closure
?>
立即執行函數 (IIFE)
匿名函數可以定義後立即執行:
<?php
$result = (function() {
$a = 10;
$b = 20;
return $a + $b;
})();
echo $result; // 30
// 帶參數的 IIFE
$result = (function($x, $y) {
return $x * $y;
})(5, 3);
echo $result; // 15
?>
實際應用範例
資料轉換
<?php
$users = [
['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com'],
['id' => 2, 'name' => 'Bob', 'email' => 'bob@example.com'],
['id' => 3, 'name' => 'Charlie', 'email' => 'charlie@example.com'],
];
// 取出所有名稱
$names = array_map(function($user) {
return $user['name'];
}, $users);
// 過濾特定使用者
$filtered = array_filter($users, function($user) {
return $user['id'] > 1;
});
// 建立 id => name 的對應
$map = array_reduce($users, function($acc, $user) {
$acc[$user['id']] = $user['name'];
return $acc;
}, []);
?>
事件處理器
<?php
class EventEmitter {
private array $listeners = [];
public function on(string $event, callable $callback): void {
$this->listeners[$event][] = $callback;
}
public function emit(string $event, ...$args): void {
if (isset($this->listeners[$event])) {
foreach ($this->listeners[$event] as $callback) {
$callback(...$args);
}
}
}
}
$emitter = new EventEmitter();
$emitter->on('user.created', function($user) {
echo "發送歡迎郵件給 {$user['name']}\n";
});
$emitter->on('user.created', function($user) {
echo "記錄日誌:使用者 {$user['name']} 已建立\n";
});
$emitter->emit('user.created', ['name' => 'Alice']);
?>
中介軟體模式
<?php
class Pipeline {
private array $middlewares = [];
public function pipe(callable $middleware): self {
$this->middlewares[] = $middleware;
return $this;
}
public function process($input) {
return array_reduce(
$this->middlewares,
function($carry, $middleware) {
return $middleware($carry);
},
$input
);
}
}
$pipeline = new Pipeline();
$result = $pipeline
->pipe(function($value) {
return $value * 2;
})
->pipe(function($value) {
return $value + 10;
})
->pipe(function($value) {
return "結果:$value";
})
->process(5);
echo $result; // 結果:20
?>
延遲執行
<?php
function lazy(callable $factory): callable {
$value = null;
$evaluated = false;
return function() use ($factory, &$value, &$evaluated) {
if (!$evaluated) {
$value = $factory();
$evaluated = true;
}
return $value;
};
}
$expensive = lazy(function() {
echo "計算中...\n";
return array_sum(range(1, 1000000));
});
// 還沒執行
echo "準備取值\n";
// 第一次呼叫時才執行
echo $expensive(); // 計算中... 500000500000
// 後續呼叫使用快取的值
echo $expensive(); // 500000500000(不會再顯示「計算中」)
?>
記憶化 (Memoization)
<?php
function memoize(callable $fn): callable {
$cache = [];
return function(...$args) use ($fn, &$cache) {
$key = serialize($args);
if (!isset($cache[$key])) {
$cache[$key] = $fn(...$args);
}
return $cache[$key];
};
}
$fibonacci = memoize(function($n) use (&$fibonacci) {
if ($n <= 1) {
return $n;
}
return $fibonacci($n - 1) + $fibonacci($n - 2);
});
echo $fibonacci(40); // 快速計算
?>
匿名函數 vs 箭頭函數
PHP 7.4+ 引入了箭頭函數,是匿名函數的簡化寫法:
<?php
// 匿名函數
$double = function($n) {
return $n * 2;
};
// 箭頭函數
$double = fn($n) => $n * 2;
// 匿名函數需要 use
$factor = 2;
$multiply = function($n) use ($factor) {
return $n * $factor;
};
// 箭頭函數自動捕獲
$multiply = fn($n) => $n * $factor;
?>
更多關於箭頭函數的說明,請參考 箭頭函數。