Skip to content

引用返回

概述

引用返回(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 — 原值不变

注意

如果调用者不使用 & 来接收引用返回的函数结果,则只会获得一个值的副本,修改不会影响原始数据。

详细说明

引用返回的规则

  1. 定义时加 &:函数定义时在函数名前加 &function &myFunc()
  2. 返回的必须是变量:引用返回必须返回一个变量,不能返回表达式或常量
  3. 调用时加 &:调用者使用 & 接收返回值才能真正获得引用
  4. 返回值的引用绑定:如果接收时不加 &,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 属性只能在构造函数中赋值

引用返回的调试困难

引用返回会让代码行为变得难以追踪。当一个函数返回引用时,调用者可能在不知不觉中修改了函数内部的私有状态。

最佳实践

  1. 谨慎使用引用返回:在大多数情况下,使用 getter/setter 方法比引用返回更清晰、更安全。
  2. 文档清晰标注:引用返回的函数必须有清晰的文档说明其行为。
  3. 考虑替代方案:使用 setter 方法或 Builder 模式通常比引用返回更易维护。
  4. 限制作用域:只在类内部或明确的 API 边界使用引用返回,避免跨模块使用。
  5. 类型声明一致:引用返回的函数也必须有正确的返回类型声明。
  6. 单元测试覆盖:引用返回的行为需要在单元测试中验证引用关系是否正确。
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];
    }
}

参考链接