PHP 早期版本無法在匿名函數內直接呼叫自身,因為函數尚未賦值給變數。PHP 5.3+ 引入閉包,配合 use (&$fn) 參考傳遞,即可實現匿名函數遞歸。PHP 7.1+ 更支援 static 匿名函數自動綁定 $this 與作用域,進一步簡化寫法。
<?php
// 經典寫法 (PHP 5.3+)
function closureRecursive(int $max): string
{
$fn = function (int $n, string $acc) use (&$fn, $max): string {
$n++;
$acc .= $n . '<br>';
if ($n < $max) {
return $fn($n, $acc); // 遞歸呼叫自身
}
return $acc;
};
return $fn(0, ''); // 初始呼叫
}
echo closureRecursive(10);
關鍵點:use (&$fn) 必須加 & 參考傳遞,否則 $fn 在 closure 定義時尚未賦值,會報錯 Undefined variable。
function staticClosureRecursive(int $max): string
{
// static 匿名函數不綁定外部作用域,可直接用 $fn 呼叫自身
$fn = static function (int $n, string $acc) use ($max): string {
$n++;
$acc .= $n . '<br>';
return $n < $max ? $fn($n, $acc) : $acc;
};
return $fn(0, '');
}
static 關鍵字使閉包不捕獲外部變數,避免 use (&$fn) 的迂迴寫法,效能略優且語意更清晰。
function buildTree(array $items, int $parentId = 0): array
{
$fn = static function (int $pid) use (&$fn, $items): array {
$branch = [];
foreach ($items as $item) {
if ($item['parent_id'] == $pid) {
$children = $fn($item['id']);
if ($children) $item['children'] = $children;
$branch[] = $item;
}
}
return $branch;
};
return $fn($parentId);
}
// 測試資料
$data = [
['id'=>1, 'parent_id'=>0, 'name'=>'頂層'],
['id'=>2, 'parent_id'=>1, 'name'=>'子分類A'],
['id'=>3, 'parent_id'=>1, 'name'=>'子分類B'],
['id'=>4, 'parent_id'=>2, 'name'=>'孫分類A1'],
];
print_r(buildTree($data));
function fibonacci(int $n): int
{
$memo = [];
$fn = static function (int $k) use (&$fn, &$memo): int {
if ($k <= 1) return $k;
if (isset($memo[$k])) return $memo[$k];
return $memo[$k] = $fn($k - 1) + $fn($k - 2);
};
return $fn($n);
}
echo fibonacci(30); // 832040,瞬間完成 (遞歸 + 備忘錄)
function scanDirRecursive(string $path): Generator
{
$fn = static function (string $dir) use (&$fn): Generator {
$dh = opendir($dir);
while (($file = readdir($dh)) !== false) {
if ($file === '.' || $file === '..') continue;
$full = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($full)) {
yield from $fn($full); // 委派生成器
} else {
yield $full;
}
}
closedir($dh);
};
return $fn($path);
}
foreach (scanDirRecursive(__DIR__) as $file) {
echo $file . PHP_EOL;
}
| 模式 | 適用場景 | 記憶體 | 速度 | 備註 |
|---|---|---|---|---|
| 閉包遞歸 | 邏輯封裝、回調、樹狀結構 | 中 (調用棧) | 快 | 注意最大遞歸深度 (xdebug 限制) |
| 生成器遞歸 | 大量資料流式處理 | 極低 (O(1)) | 快 | yield from 委派子生成器 |
| 迴圈+堆疊 | 極深層級、避免棧溢位 | 可控 | 中 | 手動模擬調用棧 |
&:use ($fn) → Undefined variable: fn。修正:use (&$fn)。use (&$fn):static function() use (&$fn) 語法錯誤。修正:移除 use 直接用 $fn。Maximum function nesting level reached。必須有明確 base case。use 的變數 (參考除外),大陣列/物件影響記憶體。僅捕獲必要變數。// 1. 普通函數 (全域汙染、難攜帶狀態)
function fact1($n) { return $n<=1?1:$n*fact1($n-1); }
// 2. 類靜態方法 (需類定義、支援靜態屬性快取)
class Math { static function fact($n) { return $n<=1?1:$n*self::fact($n-1); } }
// 3. 閉包 (內封裝、攜帶狀態、即用即建、無全域符號)
$fact = static function($n) use (&$fact) { return $n<=1?1:$n*$fact($n-1); };
PHP 閉包遞歸是函數式編程風格在 PHP 的實用體現。掌握 use (&$fn) (傳統) 與 static fn() (現代) 兩種模式,配合生成器、備忘錄、柯里化等技巧,能優雅解決樹狀結構遍歷、數學遞歸、流式處理等問題。優先使用 static 匿名函數,代碼更簡潔、效能更好、語意更明確。