引用返回
概述
引用返回(Return by Reference)允许函数返回一个变量的引用,使得调用者可以直接通过返回值修改函数内部的变量。在函数定义时,在函数名前添加 & 前缀即可声明引用返回。这是一个相对少用但有时非常有用的特性,常见于实现链式调用、单例模式的存取器,以及需要直接操作类内部状态的场景。
使用频率
引用返回在现代 PHP 开发中使用频率较低。大多数场景可以用其他模式替代。只在确实需要通过返回值修改内部状态时才考虑使用。
基础概念
引用返回的语法
在函数定义中,函数名前加 & 表示该函数返回引用。调用时,调用者也必须使用 & 来接收引用。
引用返回与引用传递的区别
- 引用传递:通过参数修改外部变量
- 引用返回:通过返回值让外部修改内部变量
引用返回的典型场景
- 单例模式中的静态变量存取
- 实现链式调用中对内部状态的操作
- 构建流畅接口(Fluent Interface)
语法与代码
基本引用返回
php
<?php
declare(strict_types=1);
class ConfigStore
{
private array $values = [];
/**
* 返回配置值的引用,允许外部直接修改。
*
* @param string $key 配置键名
* @return mixed 配置值的引用
*/
public function &getValue(string $key): mixed
{
if (!array_key_exists($key, $this->values)) {
$this->values[$key] = null;
}
return $this->values[$key]; // 返回引用
}
}
$config = new ConfigStore();
$config->getValue('timeout') = 30; // 直接修改内部值
$config->getValue('debug') = true;
// 获取引用后修改
$timeout = &$config->getValue('timeout');
$timeout = 60; // 通过引用修改引用返回的完整声明
php
<?php
declare(strict_types=1);
// 函数名前加 & 声明引用返回
function &findValue(array &$data, string $key): mixed
{
if (!isset($data[$key])) {
$data[$key] = 'default';
}
return $data[$key];
}
$records = ['name' => 'Alice', 'age' => 30];
// 调用时也需要使用 & 来接收引用
$value = &findValue($records, 'name');
$value = 'Bob'; // 直接修改 $records['name']
echo $records['name']; // 输出: Bob
// 如果不使用 & 接收,则只是获得值副本
$copy = findValue($records, 'name');
$copy = 'Charlie';
echo $records['name']; // 输出: Bob — 原值不变注意
如果调用者不使用 & 来接收引用返回的函数结果,则只会获得一个值的副本,修改不会影响原始数据。
详细说明
引用返回的规则
- 定义时加
&:函数定义时在函数名前加&(function &myFunc()) - 返回的必须是变量:引用返回必须返回一个变量,不能返回表达式或常量
- 调用时加
&:调用者使用&接收返回值才能真正获得引用 - 返回值的引用绑定:如果接收时不加
&,PHP 会创建副本
什么可以引用返回
php
<?php
declare(strict_types=1);
class Container
{
private static ?self $instance = null;
private array $bindings = [];
// 正确:返回静态属性引用
public static function &getInstance(): static
{
if (self::$instance === null) {
self::$instance = new static();
}
return self::$instance;
}
// 正确:返回数组元素引用
public function &getBinding(string $key): mixed
{
$this->bindings[$key] ??= null;
return $this->bindings[$key];
}
// 错误:不能返回表达式
// public function &getComputed(): int
// {
// return 1 + 1; // 编译错误:不能返回表达式
// }
}引用返回在静态方法中的应用
php
<?php
declare(strict_types=1);
class Registry
{
private static array $store = [];
public static function &get(string $key): mixed
{
if (!isset(self::$store[$key])) {
self::$store[$key] = null;
}
return self::$store[$key];
}
public static function getAll(): array
{
return self::$store;
}
}
// 通过引用返回直接写入注册表
Registry::get('db_host') = 'localhost';
Registry::get('db_port') = 3306;
Registry::get('db_name') = 'myapp';
print_r(Registry::getAll());
// Array ( [db_host] => localhost [db_port] => 3306 [db_name] => myapp )实战示例
链式调用中的引用操作
php
<?php
declare(strict_types=1);
class FluentArray
{
private array $data = [];
/**
* 获取数组元素的引用,用于直接修改。
*/
public function &offset(string $key): mixed
{
if (!array_key_exists($key, $this->data)) {
$this->data[$key] = null;
}
return $this->data[$key];
}
/**
* 链式调用修改多个值。
*/
public function set(string $key, mixed $value): self
{
$this->data[$key] = $value;
return $this;
}
public function toArray(): array
{
return $this->data;
}
}
$bag = new FluentArray();
// 方式一:通过引用直接赋值
$bag->offset('name') = 'Alice';
$bag->offset('age') = 30;
// 方式二:通过链式调用赋值
$bag->set('city', 'Beijing')->set('role', 'developer');
print_r($bag->toArray());
// Array ( [name] => Alice [age] => 30 [city] => Beijing [role] => developer )实战:懒加载缓存管理器
php
<?php
declare(strict_types=1);
class LazyCache
{
/** @var array<string, mixed> */
private array $cache = [];
/**
* 引用返回缓存项,首次访问时计算并缓存。
*/
public function &get(string $key, callable $factory): mixed
{
if (!isset($this->cache[$key])) {
$this->cache[$key] = $factory();
}
return $this->cache[$key];
}
public function has(string $key): bool
{
return isset($this->cache[$key]);
}
}
$cache = new LazyCache();
// 首次访问:调用工厂函数计算
$expensiveData = &$cache->get('report', function (): array {
// 模拟耗时计算
return ['total' => 1000, 'avg' => 50.5];
});
// 直接通过引用修改缓存值
$expensiveData['total'] = 2000;
// 再次访问:直接返回缓存(引用)
$cached = &$cache->get('report', fn() => []);
echo $cached['total']; // 输出: 2000 — 之前通过引用修改了值实战:嵌套数组操作器
php
<?php
declare(strict_types=1);
class NestedArrayAccessor
{
public function __construct(
private array $data = []
) {}
/**
* 使用点号路径访问和修改嵌套数组。
*/
public function &dot(string $path): mixed
{
$keys = explode('.', $path);
$current = &$this->data;
foreach ($keys as $key) {
if (!is_array($current)) {
$current = [];
}
if (!array_key_exists($key, $current)) {
$current[$key] = null;
}
$current = &$current[$key];
}
return $current;
}
public function all(): array
{
return $this->data;
}
}
$accessor = new NestedArrayAccessor();
$accessor->dot('app.name') = 'MyApp';
$accessor->dot('app.version') = '2.0';
$accessor->dot('database.host') = 'localhost';
$accessor->dot('database.port') = 3306;
print_r($accessor->all());
// Array (
// [app] => Array ( [name] => MyApp [version] => 2.0 )
// [database] => Array ( [host] => localhost [port] => 3306 )
// )注意事项
引用返回必须返回变量
编译错误
引用返回的函数必须返回一个变量引用。返回常量、表达式或函数调用结果会导致编译错误。
php
<?php
declare(strict_types=1);
class Example
{
private int $count = 0;
// 错误:返回表达式
// public function &getCountPlusOne(): int
// {
// return $this->count + 1; // Fatal Error
// }
// 正确:返回变量
public function &getCount(): int
{
return $this->count;
}
}引用返回与 PHP 8.0+ 只读属性
PHP 8.1+ 引入了只读属性(readonly)。只读属性只能在构造函数中初始化,因此不能通过引用返回来修改。
php
<?php
declare(strict_types=1);
class ReadOnlyExample
{
// readonly 属性不能通过引用返回修改
public function __construct(
public readonly int $id = 0
) {}
}
// 不能通过引用修改 readonly 属性
// readonly 属性只能在构造函数中赋值引用返回的调试困难
引用返回会让代码行为变得难以追踪。当一个函数返回引用时,调用者可能在不知不觉中修改了函数内部的私有状态。
最佳实践
- 谨慎使用引用返回:在大多数情况下,使用 getter/setter 方法比引用返回更清晰、更安全。
- 文档清晰标注:引用返回的函数必须有清晰的文档说明其行为。
- 考虑替代方案:使用 setter 方法或 Builder 模式通常比引用返回更易维护。
- 限制作用域:只在类内部或明确的 API 边界使用引用返回,避免跨模块使用。
- 类型声明一致:引用返回的函数也必须有正确的返回类型声明。
- 单元测试覆盖:引用返回的行为需要在单元测试中验证引用关系是否正确。
php
<?php
declare(strict_types=1);
// 推荐:使用明确的 getter/setter
class UserPreferences
{
private array $prefs = [];
public function set(string $key, mixed $value): void
{
$this->prefs[$key] = $value;
}
public function get(string $key, mixed $default = null): mixed
{
return $this->prefs[$key] ?? $default;
}
}
// 仅在性能敏感且模式明确时使用引用返回
class PerformanceCriticalStore
{
private array $data = [];
public function &access(string $key): mixed
{
$this->data[$key] ??= null;
return $this->data[$key];
}
}