
PHP 数组默认按值传递(实为写时复制),修改函数内副本不影响原始数组;对象则因存储句柄而表现类似引用。要使数组修改生效,必须显式使用 & 引用声明——这不是“坏习惯”,而是语义明确、可控、符合 PHP 7+ 内存模型的必要设计。
php 数组默认按值传递(实为写时复制),修改函数内副本不影响原始数组;对象则因存储句柄而表现类似引用。要使数组修改生效,必须显式使用 `&` 引用声明——这不是“坏习惯”,而是语义明确、可控、符合 php 7+ 内存模型的必要设计。
在 PHP 中,数组与对象的参数传递行为常被误解为“数组值传、对象引用传”,但真相更精细:数组是“写时复制的值传递”,对象是“句柄值传递”。二者均非真正意义上的 C 风格引用,却因底层机制差异导致表层行为迥异。
为什么 $array 不变,而 $object 的属性却变了?
看你的示例:
public function test1()
{
$array = ['test' => 1, 'foo' => 'bar'];
$this->test2($array); // 传的是数组副本(ZVAL 拷贝)
var_dump($array); // 仍为原样 → 正确行为
}
public function test2($array)
{
foreach (range(1, 10) as $step) {
$array['steps'][$step] = true; // 修改触发 Copy-on-Write,仅影响副本
}
}此时 $array 在 test2() 中被修改,PHP 7+ 会立即为该 ZVAL 创建独立副本,原始 $array 完全隔离——这是安全、可预测的设计。
而对象的行为源于其存储模型:
立即学习“PHP免费学习笔记(深入)”;
class Config { public $host = 'localhost'; }
public function test3()
{
$obj = new Config();
$this->test4($obj); // 传的是对象标识符(handle)的副本
var_dump($obj->host); // 输出 'updated' ✅
}
public function test4($obj)
{
$obj->host = 'updated'; // 同一对象实例,属性修改全局可见
}注意:这里 $obj 仍是值传递(句柄被复制),但两个句柄指向同一堆内存中的对象实例。因此修改属性生效;但若你执行 $obj = new Config();,则只会替换局部变量,不影响外部 $obj——这恰恰证明它不是“引用传递”,而是“句柄值传递”。
✅ 正确且推荐的数组引用传递方式
唯一合法、现代、无警告的方式是在函数定义中使用 & 声明形参:
public function test2(&$array) // ✅ 正确:定义时加 &
{
foreach (range(1, 10) as $step) {
$array['steps'][$step] = true;
}
}
// 调用时:$this->test2($array); —— 不加 &,否则 PHP 8+ 报致命错误⚠️ 错误写法(已废弃/禁止):
// ❌ PHP 5.4+ 已弃用,PHP 8.0+ 直接 Fatal error $this->test2(&$array); // ❌ 表达式不能传引用 $this->test2(&$config['database']['host']); // Parse error
✅ 正确处理嵌套数组元素:
// 先提取为可引用变量
$hostRef = &$config['database']['host'];
$this->updateHost($hostRef); // $hostRef 是左值,可安全传引用
function updateHost(&$value) { $value = '127.0.0.1'; }? 替代方案对比:何时该用哪种?
| 方案 | 代码示例 | 适用场景 | 注意事项 |
|---|---|---|---|
| 引用传递(推荐) | function process(&$arr) | 明确需就地批量修改(如 normalizeKeys(), filterInPlace())、性能敏感的大数组(>1MB) | 必须文档化副作用;调用方需知情;避免与默认参数混用(引用参数须前置) |
| 返回新数组 | function process($arr) { return array_merge($arr, [...]); } | 函数式风格、强调不可变性、单元测试友好、逻辑清晰 | 内存开销可控(PHP 7+ Copy-on-Write 优化后,未修改时不复制);适合大多数读写分离场景 |
| 封装为对象 | class MutableArray { private array $data; public function push($v) { $this->data[] = $v; } } | 需长期维护状态、模拟“可变容器”、统一接口 | 略增抽象层级,但语义最清晰,规避所有引用陷阱 |
⚠️ 关键注意事项(生产环境必查)
-
foreach 引用遍历后务必 unset()
foreach ($arr as &$item) { $item .= '!'; } unset($item); // ? 必须!否则 $item 仍引用最后一个元素,下次赋值将污染原数组 func_get_args() 返回普通数组,非引用
即使你传入的是引用变量,func_get_args() 获取的仍是副本,无法通过它反向修改原始参数。引用会延长内存生命周期
若 $bigArray 被 &$ref 引用,即使 $bigArray 作用域结束,只要 $ref 存在,GC 就不会回收——在 CLI/Swoole 长周期任务中易引发内存泄漏。-
对象无需 & 即可改属性,但 & 仅用于替换实例
function replaceObject(&$obj) { $obj = new Logger(); } // ✅ 替换整个对象引用 function updateObject($obj) { $obj->log('ok'); } // ✅ 改属性,不需 &
总结:别回避 &,要理解并善用它
认为“加 & 是代码发臭”是一种常见误解。PHP 的引用传递不是技术债,而是契约式编程工具:它用语法强制声明“此函数具有副作用”,比隐式返回再赋值($arr = $this->process($arr);)更直白、更不易遗漏、更利于静态分析。现代 PHP(7.4+)已彻底移除调用时传引用的歧义,& 仅存在于定义侧,语义高度统一。
因此,请放心在需要原地修改数组的场景中使用 &$array——前提是:
① 函数命名体现意图(如 sortInPlace(), stripTagsInPlace());
② 文档注明“参数将被修改”;
③ 避免在复杂逻辑中混合引用与非引用参数。
这才是专业、稳健、符合 PHP 当前最佳实践的写法。



















