Skip to content

ArrayIterator / ArrayObject

概述

ArrayIteratorArrayObject 是 PHP SPL 中将数组包装为对象的两个核心类。它们都实现了 Traversable 接口,可以在 foreach 中遍历,同时提供了丰富的数组操作方法。两者的区别在于:ArrayIterator 在创建时复制数组,而 ArrayObject 默认存储引用。

核心区别

ArrayIterator 用于迭代操作(实现了完整的 Iterator 接口),ArrayObject 用于将数组视为对象操作(实现了 CountableArrayAccess 等接口)。

基础概念

ArrayIterator

ArrayIterator 实现了 IteratorArrayAccessSeekableIteratorCountable 等多个接口,是功能最全面的数组包装器。

ArrayObject

ArrayObject 将数组包装为对象,支持属性访问和数组访问两种方式。

STD_PROP_LIST 标志

ArrayObject::STD_PROP_LIST 标志控制属性访问行为:设置此标志后,对象的属性和数组元素独立存储。

语法与代码

ArrayIterator 基本用法

php
<?php
declare(strict_types=1);

$data = ['name' => 'Alice', 'age' => 30, 'city' => 'Beijing'];
$iterator = new \ArrayIterator($data);

foreach ($iterator as $key => $value) {
    echo "{$key}: {$value}\n";
}
// name: Alice
// age: 30
// city: Beijing

// ArrayAccess 支持
echo $iterator['name'] . "\n"; // Alice

// 修改元素
$iterator['age'] = 31;
echo $iterator['age'] . "\n"; // 31

ArrayIterator 的迭代控制

php
<?php
declare(strict_types=1);

$iterator = new \ArrayIterator([10, 20, 30, 40, 50]);

// seek() — 跳到指定位置
$iterator->seek(2);
echo $iterator->current() . "\n"; // 30

// ksort() — 按键排序
$iterator->ksort();
// asort() — 按值排序
$iterator->asort();
// natsort() — 自然排序
$iterator->natsort();

// append() — 追加元素
$iterator->append(60);

// count()
echo "元素数量: " . $iterator->count() . "\n"; // 6

ArrayObject 基本用法

php
<?php
declare(strict_types=1);

$arrayObj = new \ArrayObject(['a' => 1, 'b' => 2, 'c' => 3]);

// 数组风格访问
echo $arrayObj['a'] . "\n"; // 1

// 对象风格访问
// echo $arrayObj->a . "\n"; // 不设置 STD_PROP_LIST 时,两种方式共享

// 遍历
foreach ($arrayObj as $key => $value) {
    echo "{$key}: {$value}\n";
}

// 获取底层迭代器
$iterator = $arrayObj->getIterator();

ArrayObject STD_PROP_LIST

php
<?php
declare(strict_types=1);

// 不使用 STD_PROP_LIST(默认)
$obj1 = new \ArrayObject(['key' => 'value']);
echo $obj1['key'] . "\n";  // value — 数组访问
echo $obj1->key . "\n";    // value — 属性访问(与数组共享)

// 使用 STD_PROP_LIST
$obj2 = new \ArrayObject(['key' => 'array_value'], \ArrayObject::STD_PROP_LIST);
echo $obj2['key'] . "\n";  // array_value — 数组访问
// echo $obj2->key . "\n"; // 未定义属性

ArrayObject 与 ArrayAccess

php
<?php
declare(strict_types=1);

$config = new \ArrayObject([
    'host' => 'localhost',
    'port' => 3306,
]);

// isset/empty 检查
echo isset($config['host']) ? 'yes' : 'no'; // yes
echo empty($config['port']) ? 'empty' : 'not empty'; // not empty

// unset
unset($config['port']);

// 追加
$config[] = 'new value';

// 转为数组
$array = $config->getArrayCopy();

原生数组 vs ArrayIterator 对比

php
<?php
declare(strict_types=1);

// 原生数组
$arr = [1, 2, 3];
$arr[] = 4;
sort($arr);
echo count($arr); // 4

// ArrayIterator
$iter = new \ArrayIterator([1, 2, 3]);
$iter->append(4);
$iter->asort();
echo $iter->count(); // 4

// 关键区别:ArrayIterator 可以在 foreach 中修改
$iter = new \ArrayIterator(['a', 'b', 'c']);
foreach ($iter as $key => &$value) {
    $value = strtoupper($value); // 注意:需要引用
}
unset($value);
print_r($iter->getArrayCopy());
// Array ( [0] => A [1] => B [2] => C )

详细说明

ArrayIterator 接口实现

ArrayIterator 实现了以下接口:

  • Iterator — 遍历支持
  • ArrayAccess — 数组风格访问
  • SeekableIterator — seek() 支持
  • Countable — count() 支持
  • Serializable — 序列化支持

ArrayObject 接口实现

ArrayObject 实现了以下接口:

  • IteratorAggregate — 通过 getIterator() 获取迭代器
  • ArrayAccess — 数组风格访问
  • Countable — count() 支持
  • Serializable — 序列化支持

ArrayObject 的 ARRAY_AS_PROPS 标志

php
<?php
declare(strict_types=1);

// ARRAY_AS_PROPS — 数组元素作为对象属性访问
$obj = new \ArrayObject(
    ['name' => 'Alice', 'age' => 30],
    \ArrayObject::ARRAY_AS_PROPS
);

echo $obj->name . "\n"; // Alice — 通过属性访问数组元素

实战示例

实战:配置管理器

php
<?php
declare(strict_types=1);

class ConfigManager extends \ArrayObject
{
    public function __construct(array $config = [])
    {
        parent::__construct($config, \ArrayObject::ARRAY_AS_PROPS);
    }

    public function get(string $key, mixed $default = null): mixed
    {
        return $this->offsetExists($key) ? $this->offsetGet($key) : $default;
    }

    public function set(string $key, mixed $value): void
    {
        $this->offsetSet($key, $value);
    }

    public function merge(array $config): void
    {
        foreach ($config as $key => $value) {
            $this->offsetSet($key, $value);
        }
    }
}

$config = new ConfigManager(['debug' => false, 'timeout' => 30]);
$config->set('debug', true);
$config->merge(['cache' => true, 'prefix' => 'app_']);

echo $config->debug; // true
echo $config->get('timeout'); // 30
echo $config->get('missing', 'default'); // default

注意事项

ArrayIterator 复制 vs 引用

php
<?php
declare(strict_types=1);

$original = ['a' => 1];
$iterator = new \ArrayIterator($original);

// 修改迭代器不影响原数组
$iterator['a'] = 2;
echo $original['a']; // 1 — 原数组不变

// 修改原数组不影响迭代器
$original['a'] = 100;
echo $iterator['a']; // 2 — 迭代器不变

ArrayObject 的引用行为

php
<?php
declare(strict_types=1);

// ArrayObject 的 setFlags 可以改变引用行为
$arr = ['x' => 1];
$obj = new \ArrayObject($arr);
$obj['x'] = 2;
echo $arr['x']; // 1 — 默认复制

// 使用引用标志
$arr2 = ['x' => 1];
$obj2 = new \ArrayObject($arr2);
$obj2->setFlags(\ArrayObject::STD_PROP_LIST);
$obj2['x'] = 2;
echo $arr2['x']; // 1 — 仍然复制

最佳实践

  1. 优先使用原生数组:大多数场景下原生数组性能更好。
  2. ArrayIterator 用于需要迭代器接口时:当函数参数类型为 Iterator 时使用。
  3. ArrayObject 用于对象化访问:需要 $obj->property 风格时使用。
  4. 谨慎使用引用标志:引用行为容易导致意外的副作用。
php
<?php
declare(strict_types=1);

// 推荐:使用原生数组
$data = ['key' => 'value'];

// 需要迭代器时:包装为 ArrayIterator
function processIterator(\Iterator $iterator): void
{
    foreach ($iterator as $item) {
        // 处理...
    }
}
processIterator(new \ArrayIterator($data));

ArrayObject 深度解析

ArrayObject 的标志位

ArrayObject 的行为受构造函数标志影响:

php
<?php
declare(strict_types=1);

// STD_PROP_LIST:只能通过属性访问声明的属性
$obj1 = new \ArrayObject([], \ArrayObject::STD_PROP_LIST);
$obj1->custom = 'value';   // 设置为动态属性
$obj1['key'] = 'array_val';
echo $obj1->custom;  // 'value' (通过属性访问)
echo $obj1['custom']; // 'value'
echo $obj1->key;      // 未定义属性(空或错误)

// ARRAY_AS_PROPS:数组元素可以通过属性访问
$obj2 = new \ArrayObject([], \ArrayObject::ARRAY_AS_PROPS);
$obj2['key'] = 'array_val';
echo $obj2->key;      // 'array_val' (通过属性访问)
标志说明
STD_PROP_LIST1属性访问只对声明属性有效
ARRAY_AS_PROPS2数组元素可以通过属性访问

行为差异

ARRAY_AS_PROPS 模式下,数组键名如果与类属性名冲突,行为取决于属性是否声明。建议使用一种访问方式,不要混用。

ArrayObject 的序列化

php
<?php
declare(strict_types=1);

$data = new \ArrayObject([
    'name' => 'Alice',
    'age' => 30,
    'hobbies' => ['reading', 'coding'],
]);

// 序列化
$serialized = serialize($data);
echo "序列化长度: " . strlen($serialized) . " bytes\n";

// 反序列化
$restored = unserialize($serialized);
echo $restored['name'] . "\n"; // Alice
print_r($restored->getArrayCopy());

ArrayObject 实现的接口

ArrayObject 实现了以下接口:

  • IteratorAggregate — 可被 foreach 遍历
  • ArrayAccess — 支持 [] 操作符
  • Countable — 支持 count()
  • Serializable — 支持序列化
  • JsonSerializable — 支持 json_encode()

实战:配置管理器

php
<?php
declare(strict_types=1);

class ConfigManager extends \ArrayObject
{
    public function __construct(array $config = [])
    {
        parent::__construct($config, \ArrayObject::ARRAY_AS_PROPS);
    }

    public function get(string $key, mixed $default = null): mixed
    {
        return $this->offsetExists($key) ? $this[$key] : $default;
    }

    public function set(string $key, mixed $value): void
    {
        $this[$key] = $value;
    }

    public function has(string $key): bool
    {
        return $this->offsetExists($key);
    }

    public function merge(array $config): self
    {
        foreach ($config as $key => $value) {
            $this[$key] = $value;
        }
        return $this;
    }

    public function section(string $key): self
    {
        $section = $this->offsetExists($key) && is_array($this[$key])
            ? $this[$key]
            : [];
        return new self($section);
    }
}

$config = new ConfigManager([
    'app' => [
        'name' => 'MyApp',
        'debug' => true,
    ],
    'database' => [
        'host' => 'localhost',
        'port' => 3306,
    ],
]);

// 通过属性访问
echo $config->app['name'] . "\n";
echo $config->database['host'] . "\n";

// 通过方法访问
echo $config->get('app.name') . "\n";

// 获取子配置
$dbConfig = $config->section('database');
echo $dbConfig['host'] . "\n";

ArrayIterator 与 ArrayObject 的选择

考量因素ArrayIteratorArrayObject
对象语义迭代器数据容器
可序列化
属性访问不支持支持(ARRAY_AS_PROPS)
用途遍历和操作数组存储和传递数组数据
排序方法有(asort/ksort/uasort/uksort/natcasesort)无(需要 getArrayCopy 再排序)
修改原始数组通过引用时是

常见误区与 FAQ

ArrayObject 和原生数组的性能对比?

对于简单操作,原生数组性能更好。ArrayObject 有额外的对象开销。但在需要对象语义(序列化、接口实现、属性访问)的场景中,ArrayObject 更合适。

ArrayIterator 可以修改原始数组吗?

可以。ArrayIterator 直接引用原始数组,修改迭代器的值会反映在原始数组中。

php
<?php
declare(strict_types=1);

$data = ['a' => 1, 'b' => 2, 'c' => 3];
$iterator = new \ArrayIterator($data);

foreach ($iterator as $key => &$value) {
    $value *= 10;
}

print_r($iterator->getArrayCopy());
// ['a' => 10, 'b' => 20, 'c' => 30]

参考链接