Closure / Generator / Fiber 类
概述
PHP 提供了三种特殊的内部类来支持高级控制流和函数式编程特性:
- Closure — 匿名函数的内部类,提供
bind()、call()等方法来操作闭包的绑定上下文 - Generator — 生成器的内部类,是
yield关键字产生的对象,提供getReturn()、getCurrent()等方法 - Fiber — 协程/纤程(PHP 8.1+),提供轻量级的并发执行能力,可暂停和恢复执行
这三者虽然各自服务于不同的目的,但都是 PHP 中控制流抽象的重要组成部分。
版本说明
Closure类从 PHP 5.3 起可用(随匿名函数引入)Generator类从 PHP 5.5 起可用(随yield关键字引入)Fiber类在 PHP 8.1 中引入,PHP 8.2+ 支持在 Fiber 中使用set_exception_handler
基础概念
Closure 类
Closure 类是所有匿名函数(闭包)的实例类型。它提供了操作闭包绑定上下文($this 和作用域)的方法。
Generator 类
Generator 实现了 Iterator 接口,是包含 yield 的函数返回的对象。生成器允许惰性求值和协程式编程。
Fiber 类
Fiber 是 PHP 8.1 引入的协程原语,允许在函数执行过程中暂停(Fiber::suspend()),并在外部恢复(Fiber->resume()),实现协作式多任务。
语法与代码
Closure 类常用方法
php
<?php
declare(strict_types=1);
class User
{
private string $name = 'Alice';
public function getName(): string
{
return $this->name;
}
}
// Closure::bind() — 绑定闭包到指定对象
$closure = function (): string {
return $this->name; // 访问私有属性
};
$user = new User();
// bind 绑定闭包到 $user 对象,并允许访问私有成员
$bound = Closure::bind($closure, $user, User::class);
echo $bound(); // Alice
// Closure::bind() 创建新闭包,原闭包不变
// Closure::call() — 绑定并立即调用(PHP 7.0+)
$result = $closure->call($user);
echo $result; // Alice
// call() 更简洁,且性能更好
// Closure::fromCallable() — 从可调用对象创建闭包
$func = Closure::fromCallable('strlen');
echo $func('hello'); // 5
$method = Closure::fromCallable([$user, 'getName']);
echo $method(); // AliceGenerator 类常用方法
php
<?php
declare(strict_types=1);
function rangeGenerator(int $start, int $end): Generator
{
for ($i = $start; $i <= $end; $i++) {
yield $i => $i * $i;
}
}
$gen = rangeGenerator(1, 5);
// Generator 实现了 Iterator 接口
// current() — 获取当前 yield 的值
echo $gen->current(); // 1(值)
echo $gen->key(); // 1(键)
// send() — 向生成器发送值(作为 yield 表达式的返回值)
function echoGenerator(): Generator
{
$input = yield '等待输入...';
echo "收到: {$input}\n";
$input2 = yield '再次等待...';
echo "又收到: {$input2}\n";
}
$echoGen = echoGenerator();
echo $echoGen->current(); // 等待输入...
$echoGen->send('Hello'); // 收到: Hello, 输出: 再次等待...
$echoGen->send('World'); // 又收到: World
// getReturn() — 获取生成器函数的返回值
function sumGenerator(array $numbers): Generator
{
$sum = 0;
foreach ($numbers as $num) {
$sum += $num;
yield $sum;
}
return $sum; // 最终返回值
}
$sumGen = sumGenerator([1, 2, 3, 4, 5]);
// 需要先遍历完毕
foreach ($sumGen as $partial) {
// partial: 1, 3, 6, 10, 15
}
echo $sumGen->getReturn(); // 15Fiber 类(PHP 8.1+)
php
<?php
declare(strict_types=1);
// 创建 Fiber
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('第一次暂停');
echo "恢复时收到的值: {$value}\n";
$value2 = Fiber::suspend('第二次暂停');
echo "再次恢复收到的值: {$value2}\n";
});
// 启动 Fiber,直到第一次 suspend
$result = $fiber->start();
echo "Fiber 返回: {$result}\n"; // Fiber 返回: 第一次暂停
// 恢复 Fiber,传入值
$result = $fiber->resume('Hello');
echo "Fiber 返回: {$result}\n"; // Fiber 返回: 第二次暂停
// 再次恢复
$fiber->resume('World');
// 输出: 恢复时收到的值: Hello
// 再次恢复收到的值: World
// Fiber::throw() — 向 Fiber 抛出异常
$fiber2 = new Fiber(function (): void {
try {
Fiber::suspend('暂停');
} catch (RuntimeException $e) {
echo "捕获到异常: {$e->getMessage()}\n";
}
});
$fiber2->start();
$fiber2->throw(new RuntimeException('出错了'));
// 输出: 捕获到异常: 出错了详细说明
Closure::bind() 详解
php
<?php
declare(strict_types=1);
class Order
{
private string $status = 'pending';
public function getStatus(): string
{
return $this->status;
}
}
class OrderLogger
{
// 使用闭包访问 Order 的私有属性
public function getStatusExtractor(): Closure
{
$extractor = function (): string {
return $this->status;
};
return Closure::bind($extractor, null, Order::class);
// bind 参数:
// 1. 闭包
// 2. 绑定的 $this 对象(null 表示不绑定 $this)
// 3. 作用域类(决定可访问的可见性级别)
}
}
$logger = new OrderLogger();
$extractor = $logger->getStatusExtractor();
$order = new Order();
// 传入 $this 对象调用
$boundExtractor = Closure::bind($extractor, $order, Order::class);
echo $boundExtractor(); // pending
// 使用 call() 更简洁
echo $extractor->call($order); // pendingGenerator 高级用法
php
<?php
declare(strict_types=1);
// yield from — 委托生成器
function innerGenerator(): Generator
{
yield 'a';
yield 'b';
}
function outerGenerator(): Generator
{
yield 'start';
yield from innerGenerator(); // 委托给内部生成器
yield 'end';
}
foreach (outerGenerator() as $value) {
echo $value . ' '; // start a b end
}
// yield from 返回值
function innerWithReturn(): Generator
{
yield 'x';
return 'inner_result';
}
function outerWithReturn(): Generator
{
$result = yield from innerWithReturn();
yield "inner returned: {$result}";
}
foreach (outerWithReturn() as $val) {
echo $val . "\n"; // x, inner returned: inner_result
}Fiber 的状态管理
php
<?php
declare(strict_types=1);
$fiber = new Fiber(function (): void {
echo "Fiber 开始\n";
Fiber::suspend();
echo "Fiber 恢复\n";
});
// Fiber 状态检查
echo $fiber->getStatus(); // FiberState::Unstarted
$fiber->start();
echo $fiber->getStatus(); // FiberState::Suspended
$fiber->resume();
echo $fiber->getStatus(); // FiberState::Terminated
// Fiber 状态枚举:
// FiberState::Unstarted — 未启动
// FiberState::Running — 运行中
// FiberState::Suspended — 已暂停
// FiberState::Terminated — 已结束Fiber 实现简单任务调度
php
<?php
declare(strict_types=1);
class SimpleScheduler
{
/** @var array<int, Fiber> */
private array $fibers = [];
private int $currentId = 0;
public function spawn(callable $task): int
{
$id = $this->currentId++;
$scheduler = $this;
$this->fibers[$id] = new Fiber(function () use ($task, $scheduler, $id): void {
try {
$task($id);
} finally {
$scheduler->remove($id);
}
});
return $id;
}
public function remove(int $id): void
{
unset($this->fibers[$id]);
}
public function run(): void
{
while (!empty($this->fibers)) {
foreach ($this->fibers as $id => $fiber) {
if ($fiber->getStatus() === FiberStatus::Suspended) {
$fiber->resume();
} elseif ($fiber->getStatus() === FiberStatus::Unstarted) {
$fiber->start();
}
}
}
}
}
// 注意:PHP 8.1 中 FiberStatus 是枚举类
// 使用 Fiber->isStarted()/isSuspended()/isTerminated() 检查状态实战示例
使用 Closure 实现中间件
php
<?php
declare(strict_types=1);
class MiddlewarePipeline
{
/** @var array<int, Closure> */
private array $middlewares = [];
public function add(Closure $middleware): self
{
$this->middlewares[] = $middleware;
return $this;
}
public function run(Closure $core): mixed
{
// 从后向前构建中间件链
$pipeline = array_reduce(
array_reverse($this->middlewares),
function (Closure $next, Closure $middleware): Closure {
return fn (...$args): mixed => $middleware($next, ...$args);
},
$core
);
return $pipeline();
}
}
$pipe = new MiddlewarePipeline();
$pipe->add(function (Closure $next): mixed {
echo "[1] 请求前处理\n";
$result = $next();
echo "[1] 响应后处理\n";
return $result;
});
$pipe->add(function (Closure $next): mixed {
echo "[2] 认证检查\n";
$result = $next();
echo "[2] 日志记录\n";
return $result;
});
$result = $pipe->run(function (): string {
echo "[核心] 处理业务逻辑\n";
return '完成';
});
// 输出:
// [1] 请求前处理
// [2] 认证检查
// [核心] 处理业务逻辑
// [2] 日志记录
// [1] 响应后处理使用 Generator 实现管道
php
<?php
declare(strict_types=1);
function csvReaderGenerator(string $filePath): Generator
{
$handle = fopen($filePath, 'r');
if ($handle === false) {
return;
}
$headers = fgetcsv($handle);
if ($headers === false) {
fclose($handle);
return;
}
while (($row = fgetcsv($handle)) !== false) {
yield array_combine($headers, $row);
}
fclose($handle);
}
function filterGenerator(Generator $source, Closure $filter): Generator
{
foreach ($source as $item) {
if ($filter($item)) {
yield $item;
}
}
}
function mapGenerator(Generator $source, Closure $mapper): Generator
{
foreach ($source as $item) {
yield $mapper($item);
}
}
function limitGenerator(Generator $source, int $limit): Generator
{
$count = 0;
foreach ($source as $item) {
if ($count >= $limit) {
break;
}
yield $item;
$count++;
}
}
// 惰性管道:数据按需处理,不会一次性加载到内存
// $pipeline = limitGenerator(
// mapGenerator(
// filterGenerator(csvReaderGenerator('data.csv'), fn($row) => $row['active']),
// fn($row) => ['name' => $row['name'], 'email' => $row['email']]
// ),
// 100
// );注意事项
Fiber 的限制
php
<?php
declare(strict_types=1);
// Fiber 不能跨越某些边界
// 1. Fiber 中的异常如果不捕获,会终止 Fiber
$fiber = new Fiber(function (): void {
throw new RuntimeException('未捕获的异常');
});
$fiber->start(); // 抛出 RuntimeException
// 2. Fiber 中不能使用 return 返回值(用 suspend 返回)
// Fiber 函数的返回值在 terminated 后不可获取
// 3. Fiber 的栈是独立的
$fiber = new Fiber(function (): void {
global $counter; // 不推荐,Fiber 有独立作用域
});Fiber 注意事项
- Fiber 不会自动并行执行,它是协作式多任务,需要手动调度
- 主线程 Fiber 不能被 suspend(
Fiber::suspend()只能在 Fiber 内调用) - Fiber 中的异常如果不处理会导致 Fiber 终止,异常通过
Fiber->getException()获取
Closure::bind() 的性能
php
<?php
declare(strict_types=1);
// Closure::call() 比 Closure::bind() + 调用 更快
// call() 是 PHP 7.0 引入的优化版本
$closure = function (): string {
return $this->name;
};
$user = new class {
private string $name = 'Test';
};
// 方式 1: bind + 调用(两步)
$bound = Closure::bind($closure, $user, get_class($user));
$result = $bound();
// 方式 2: call(一步,推荐)
$result = $closure->call($user);最佳实践
- 优先使用 Closure::call():比
Closure::bind()更简洁高效 - Generator 用于大数据集:避免一次性加载大量数据到内存
- Fiber 适用于 I/O 密集型任务:如并发 HTTP 请求、数据库查询等
- Closure::fromCallable() 类型安全:将可调用对象转为统一的
Closure类型 - Generator 注意资源释放:在
finally块中释放文件句柄等资源
php
<?php
declare(strict_types=1);
// Generator 资源安全模式
function safeGenerator(string $filePath): Generator
{
$handle = fopen($filePath, 'r');
if ($handle === false) {
return;
}
try {
while (($line = fgets($handle)) !== false) {
yield rtrim($line, "\r\n");
}
} finally {
// 无论生成器如何结束(遍历完成、抛异常、unset)
// finally 块确保资源被释放
fclose($handle);
}
}