2.4 高级特性:闭包、生成器与反射


文档摘要

2.4 高级特性:闭包、生成器与反射 本节摘要:这三件武器解决三类特定难题:闭包是"可携带的行为",配上绑定能力后能干事件回调与中间件的活;生成器把大结果集的内存占用从"全量装车"降到"逐件搬运",是 2.2 节大查询的标准搭档;反射让代码在运行期读出类与方法的结构,是依赖注入容器的底层机关。读完你应当知道每件武器该在什么场合掏出来,以及更重要的——什么场合别掏。 三件武器的共同气质 它们都不属于日常 CRUD 的主航道,却在框架与库里无处不在:框架路由把"路径加回调"存成表,用的是闭包;ORM 逐行流式取数十万行,用的是生成器;容器自动给控制器塞依赖,用的是反射。学它们的意义不在炫技,而在读懂现代 PHP 工程的"内部语言",并在恰当场合自己用上。 闭包进阶:从捕获到绑定 1.

2.4 高级特性:闭包、生成器与反射

本节摘要:这三件武器解决三类特定难题:闭包是"可携带的行为",配上绑定能力后能干事件回调与中间件的活;生成器把大结果集的内存占用从"全量装车"降到"逐件搬运",是 2.2 节大查询的标准搭档;反射让代码在运行期读出类与方法的结构,是依赖注入容器的底层机关。读完你应当知道每件武器该在什么场合掏出来,以及更重要的——什么场合别掏。

三件武器的共同气质

它们都不属于日常 CRUD 的主航道,却在框架与库里无处不在:框架路由把"路径加回调"存成表,用的是闭包;ORM 逐行流式取数十万行,用的是生成器;容器自动给控制器塞依赖,用的是反射。学它们的意义不在炫技,而在读懂现代 PHP 工程的"内部语言",并在恰当场合自己用上。

闭包进阶:从捕获到绑定

1.3 节讲过闭包捕获变量的基础,这里补两件进阶事。第一件是 PHP 8.1 起的一等可调用语法,把现成方法变成闭包不用再包一层:

<?php $names = [' 张三 ', '李四', ' 王五']; // 旧写法:fn($v) => trim($v) $clean = array_map(trim(...), $names); // 直接引用 trim 函数 print_r($clean); // [张三, 李四, 王五]

第二件是绑定:Closure::bind 能把闭包"挂"进别的对象作用域,甚至访问其私有属性。框架利用它做高层定制,业务代码里极少手写,但读懂它,框架源码里那些看似神秘的调用就透明了:

<?php class Safe { private string $secret = '库房暗号'; } $getSecret = Closure::bind(fn() => $this->secret, new Safe, Safe::class); echo $getSecret(), "\n"; // 库房暗号

克制提醒:业务代码能不碰私有就别碰,绑定是框架级手段,不是日常姿势。

生成器:大结果集的省内存方案

2.2 节留了个尾巴:几十万行的结果集,fetchAll 会把内存撑爆。生成器用 yield 逐个"吐"数据,函数看似一次执行,实际每取一次才算一步:

<?php function readBigCsv(string $path): Generator { $handle = fopen($path, 'r'); fgets($handle); // 跳过表头行 while (($row = fgetcsv($handle)) !== false) { yield [ 'name' => trim($row[0]), 'age' => (int) $row[1], ]; } fclose($handle); } $total = 0; foreach (readBigCsv(__DIR__ . '/passengers_full.csv') as $person) { $total += $person['age'] > 0 ? 1 : 0; // 逐条处理,内存里始终只有一行 } echo "有效记录 {$total} 条\n";

同样的循环换成 fetchAll 式实现,内存里得同时躺着全表。生成器的经济性来自"按需生产":调用方要一条,它算一条,算完即弃。另一个隐藏福利是"惰性",配合 break 用得特别舒服:

<?php function ids(): Generator { for ($i = 1; $i <= 1_000_000; $i++) { yield $i; // 号称能产一百万条,其实用多少产多少 } } foreach (ids() as $id) { if ($id > 5) break; // 只要前五个,第六个起根本没生产过 } var_dump($id); // int(6):循环结束时刚好停在这

适用边界也要说清:生成器只能向前,不能倒带、不能随机访问、不能数长度(除非走完一遍)。需要反复遍历或统计总数的数据集,老老实实取数组或查库计数。

反射:运行期读结构

反射让代码拿到"关于代码的信息":一个类有哪些方法、参数签名长什么样、注释里标了什么。听着玄,用处极实——依赖注入容器就是靠它自动new对象:看构造函数要什么类型,就递归地把依赖造好塞进去。手工实现一个迷你版:

<?php class PdoFactory {} class TripService { public function __construct(private PdoFactory $factory) {} } function build(string $class): object { $ref = new ReflectionClass($class); $ctor = $ref->getConstructor(); if ($ctor === null) return new $class; // 无构造函数直接建 $args = []; foreach ($ctor->getParameters() as $param) { $type = $param->getType()->getName(); // 参数声明的类名 $args[] = build($type); // 递归先建依赖 } return $ref->newInstanceArgs($args); } var_dump(build(TripService::class)); // TripService 对象,依赖已自动就位

十几行就是容器的雏形:TripService 要一个 PdoFactorybuild 读出类型、递归构造、注入实例。第 3 章框架里控制器方法能直接"要"一个 PDO 参数,底层正是这套机关。反射的性能不便宜,容器只在装配期用一次,配好就缓存,业务热路径上别裸用。

实战:三件武器各就各位

把三件武器收进同一条业务线:导出全部旅客为 JSON 行文件。生成器负责省内存地逐行产出,闭包负责字段清洗,反射(调试期)核对导出器的结构完整性:

<?php $format = fn(array $row): string => json_encode($row, JSON_UNESCAPED_UNICODE); $out = fopen(__DIR__ . '/exports/passengers.jsonl', 'w'); foreach (readBigCsv(__DIR__ . '/passengers_full.csv') as $person) { fwrite($out, $format($person) . "\n"); // 一行一客,内存恒定 } fclose($out); echo "导出完成\n";

(框架期复盘:3.2 节会看到框架的中间件本质上就是"层层包裹的闭包",模型层的大表遍历就是"数据库版的生成器"。)

常见坑

⚠️ 在生成器里抛异常没人接:生成器是惰性的,yield 出的值没被消费时函数体根本没执行,里面的 try/catch 自然没机会跑。

⚠️ 反射当常规武器:热路径上反复反射会拖垮性能,装配期用完就缓存。

本节要点回顾

  • 闭包的进阶身份:一等可调用语法简化引用,绑定是框架源码的通用机关。
  • 生成器管"大"yield 按需生产,内存恒定,配 break 惰性取用;不能倒带是它的边界。
  • 反射管"装":读结构、递归建依赖,容器与各种自动化装配的地基,热路径慎用。
  • 组合出活:生成器供料、闭包加工,是一条能落地的省内存流水线。

下一节看性能:月台运转起来了,怎么让旅客少排队。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U