应避免全局变量和裸函数:用参数传入依赖、封装只读配置、命名空间替代全局函数、闭包显式use值传递、函数归属类并由composer自动加载。

避免 global 和 $GLOBALS 直接修改
PHP 函数里伸手就写 global $user_id 或 $GLOBALS['config'] = [...],等于把函数变成全局状态的开关。一旦多个地方调用、并发或测试时顺序变化,值就不可控。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用参数显式传入依赖,比如
function formatTime($timestamp, $timezone),而不是在函数里读$GLOBALS['timezone'] - 若必须访问配置,封装成只读对象(如
Config::get('db.host')),内部禁止写操作 - 测试时能 mock 参数,而无法 mock 全局变量——这点直接影响可测性
函数命名带作用域前缀,不依赖文件加载顺序
写个 send_email() 看似干净,但项目一上规模,不同模块都定义同名函数,Fatal error: Cannot redeclare send_email() 就来了。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用命名空间 + 静态方法替代全局函数:比如
App\Notification\EmailSender::send() - 纯工具函数也加前缀:
str_slug()改成app_str_slug()或myproject_str_slug(),避免和未来扩展包冲突 - 不依赖
require_once的“防重载”逻辑——它不解决函数语义污染,只掩盖报错
闭包里用 use 显式捕获,别偷偷改外层变量
下面这段代码看着无害,实际是隐形污染源:
$filters = [];
array_map(function ($item) use (&$filters) {
$filters[] = $item['type'];
}, $data);
// $filters 现在被改了,但调用方完全不知情
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 闭包中需要读取外部变量,用
use ($var)(值传递);仅当明确需要修改时才加&,且函数名/注释要体现副作用,比如collectFilters() - 优先返回新数组,而非修改入参或外部变量:
return array_column($data, 'type'); - 静态分析工具(如 PHPStan)对
use (&$x)会标黄——这不是警告,是红线
autoload 机制下,函数声明位置比想象中更关键
很多团队以为“只要没执行,函数定义在哪都行”,但 PHP 的 autoload 不管函数,只管类。如果一个 helpers.php 被某些文件 require,另一些没 require,就会出现 Call to undefined function。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 所有函数必须归属到类中,通过 Composer autoloading 加载;拒绝裸
.php工具文件 - 实在要保留函数文件(如 legacy 迁移期),统一在
composer.json的"autoload": {"files": ["src/helpers.php"]}中声明 - 不要在条件块里定义函数(如
if (DEBUG) { function log_debug() {...} }),这会让 opcache 和 IDE 失去函数索引
真正难的不是写隔离的函数,而是让整个项目默认走“不可变输入 → 纯输出”路径。哪怕一个 trim() 调用,背后也可能连着全局 mb_internal_encoding() 设置——这种隐式耦合,比命名冲突更难 debug。











