应设计源适配器+聚合器中间层,用Generator流式读取MySQL(PDO::FETCH_ASSOC)、API(带timeout与状态码校验)、CSV(fgetcsv),统一归一化字段类型并添加source标识,通过AppendIterator合并避免内存溢出。

怎么安全地把 MySQL、API、CSV 数据一起读出来
不能直接拼 SQL 或硬编码 URL,得先统一数据结构。PHP 本身不提供跨源聚合的内置函数,得靠自己设计中间层——核心是把不同来源都转成 array 或 Traversable,再用 array_merge 或 IteratorIterator 合并。
常见错误是直接 file_get_contents 读 CSV 后用 explode 分割,结果遇到换行、逗号转义就崩;或者调 API 不设超时,一个慢接口拖垮整页。
- MySQL 用
PDO::FETCH_ASSOC拿关联数组,避免数字键冲突 - API 请求必须加
timeout和http_response_code检查,别只看200,429或503也要处理 - CSV 用
fgetcsv而不是字符串分割,它自动处理引号包裹和换行 - 所有来源返回前做字段对齐:比如统一用
id、name、source字段,source填'mysql'/'api_v1'/'csv_legacy'
合并时怎么避免 key 冲突和内存爆掉
array_merge 看似简单,但三万条 MySQL + 两万条 API + 五万行 CSV 一合并就是十万个 array,PHP 默认内存限制(128M)很可能报 Fatal error: Allowed memory size exhausted。
真正该用的是流式合并:不全加载进内存,而是一边读一边 yield。
立即学习“PHP免费学习笔记(深入)”;
- 用
Generator封装每个数据源,比如function readMysqlRows(): Generator - 合并时用
new AppendIterator把多个Iterator串起来,而不是array_merge - 如果要排序去重,别用
array_unique,改用临时表或分批sort()+ 手动比对 - 注意
AppendIterator不支持count(),需要总数就得单独统计或加计数器
字段类型不一致怎么办(比如 MySQL 的 INT 和 CSV 的字符串 ID)
合并后查 $data[0]['id'] 是 int,下一条却是 string,后续用 === 判断或传给 PDO 参数就会出错。
不能靠 PHP 自动转换,得在读取阶段就归一化。
- MySQL:PDO 绑定时用
PDO::ATTR_EMULATE_PREPARES => false,让数字保持原类型;读出来后用filter_var($val, FILTER_VALIDATE_INT) !== false显式判断 - API:JSON 解码后检查
is_numeric($val) && (int)$val == $val再转整型,避免"123.0"变成123 - CSV:用
fgetcsv后对关键列跑ctype_digit($val) ? (int)$val : $val,别无脑(int) - 统一加个
normalizeType()工具函数,只处理已知字段,别全局遍历
怎么让聚合逻辑可维护、不散落在 foreach 里
最常踩的坑是把数据库查询、cURL 调用、fopen 全塞在一个大函数里,改 CSV 路径得翻 200 行,加个新 API 又要复制粘贴一堆 header 设置。
核心是拆成「源适配器」+「聚合器」两层,每个源对应一个类或闭包,实现统一接口。
- 定义一个
DataSourceInterface,只含fetch(): Iterator方法 - 写
MysqlSource、ApiSource、CsvSource三个实现,各自管连接、错误重试、字段映射 - 聚合器只接收
DataSourceInterface[],循环yield from $source->fetch() - 配置走
config/sources.php数组,而不是硬编码 host/port/path
复杂点不在语法,而在字段语义对齐——比如 MySQL 的 updated_at 是 datetime,API 返回的是 Unix timestamp,CSV 里可能是 "2024/03/15"。这种不统一,光靠 type cast 解决不了,得配映射规则,而且得留日志记录哪一行被强制转换过。











