You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于__call魔法方法实现嵌套数组链式调用的复用问题

问题:ArrayCollection实例多次链式调用返回空值的解决办法

我在试验PHP的__call魔法方法,想把嵌套数组转成类似集合的结构,用数组键作为集合方法,链式调用后通过get()返回数组结果。但发现首次调用能得到正确结果,第二次调用因为实例内的$data被修改返回空值,希望实现多次调用都能获取相同结果。

原代码

declare(strict_types=1);

class ArrayCollection
{
    private const GET_LAST_ITEM = 'get';
    private const SINGLE_KEY = '_';

    protected array $data = [];

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    /**
     * @return self|array
     */
    public function __call(string $name, array $args)
    {
        if ($name === self::GET_LAST_ITEM) {
            return array_key_exists(self::SINGLE_KEY, $this->data)
                ? array_pop($this->data)
                : $this->data;
        }

        $offset = array_shift($args);
        $current = $this->data[$name] ?? null;

        $this->data = is_array($current)
            ? $current[$offset] ?? $current
            : (
                is_string($current) || is_int($current)
                    ? [self::SINGLE_KEY => $current]
                    : $this->data
            );

        return $this;
    }
}

$collection = (new ArrayCollection(
    [
        'a' => [
            'nested' => [
                ['item' => 100],
                ['item' => 200],
                ['item' => 300],
                ['children' => [
                    'first' => 325,
                    'last' => [350, 375]
                ]]
            ]
        ],
        'b' => [
            'nested' => [
                ['item' => 400],
                ['item' => 500],
            ],
            'children' => [
                'xp' => PHP_INT_MAX
            ]
        ]
    ]
));

print_r($collection->b()->nested(1)->item()->get());

问题示例

print_r($collection->b()->children()->xp()->get());
/*
Array
(
    [0] => 350
    [1] => 375
)
*/
print_r($collection->b()->children()->xp()->get());
/*
Array
(
)
*/

解决方案

核心问题是每次链式调用都直接修改了原实例的$data属性,导致后续调用基于已修改的数据而非初始数据。解决思路是让每次链式调用返回新的ArrayCollection实例,保留原实例的初始数据。

修改后的代码

declare(strict_types=1);

class ArrayCollection
{
    private const GET_LAST_ITEM = 'get';
    private const SINGLE_KEY = '_';

    protected array $data = [];

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    /**
     * @return self|array
     */
    public function __call(string $name, array $args)
    {
        if ($name === self::GET_LAST_ITEM) {
            // 直接读取值,不再修改原数据
            return array_key_exists(self::SINGLE_KEY, $this->data)
                ? $this->data[self::SINGLE_KEY]
                : $this->data;
        }

        $offset = array_shift($args);
        $current = $this->data[$name] ?? null;

        // 计算新数据,但不修改当前实例的$data
        $newData = is_array($current)
            ? $current[$offset] ?? $current
            : (
                is_string($current) || is_int($current)
                    ? [self::SINGLE_KEY => $current]
                    : $this->data
            );

        // 返回新的ArrayCollection实例,而非当前实例
        return new self($newData);
    }
}

$collection = (new ArrayCollection(
    [
        'a' => [
            'nested' => [
                ['item' => 100],
                ['item' => 200],
                ['item' => 300],
                ['children' => [
                    'first' => 325,
                    'last' => [350, 375]
                ]]
            ]
        ],
        'b' => [
            'nested' => [
                ['item' => 400],
                ['item' => 500],
            ],
            'children' => [
                'xp' => PHP_INT_MAX
            ]
        ]
    ]
));

// 测试多次调用
print_r($collection->b()->nested(1)->item()->get()); // 输出500
print_r($collection->b()->children()->xp()->get()); // 输出PHP_INT_MAX
print_r($collection->b()->children()->xp()->get()); // 再次调用仍输出PHP_INT_MAX

关键改动说明

  1. 不修改原实例数据:将原代码中覆盖$this->data的逻辑改为生成新数据,然后返回新的ArrayCollection实例。这样原实例始终保留初始数据,每次链式调用都是基于前一个实例的数据创建新实例。
  2. 调整get()方法逻辑:去掉array_pop()(会修改数组),直接读取对应键的值,保证数据的不可变性。

这样无论调用多少次,每次都是从原实例的初始数据出发,经过链式调用生成新的中间实例,最终都能得到正确结果。

内容的提问来源于stack exchange,提问作者hyperplural

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 02:46:07