ArrayIterator / ArrayObject
概述
ArrayIterator 和 ArrayObject 是 PHP SPL 中将数组包装为对象的两个核心类。它们都实现了 Traversable 接口,可以在 foreach 中遍历,同时提供了丰富的数组操作方法。两者的区别在于:ArrayIterator 在创建时复制数组,而 ArrayObject 默认存储引用。
核心区别
ArrayIterator 用于迭代操作(实现了完整的 Iterator 接口),ArrayObject 用于将数组视为对象操作(实现了 Countable、ArrayAccess 等接口)。
基础概念
ArrayIterator
ArrayIterator 实现了 Iterator、ArrayAccess、SeekableIterator、Countable 等多个接口,是功能最全面的数组包装器。
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"; // 31ArrayIterator 的迭代控制
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"; // 6ArrayObject 基本用法
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 — 仍然复制最佳实践
- 优先使用原生数组:大多数场景下原生数组性能更好。
- ArrayIterator 用于需要迭代器接口时:当函数参数类型为
Iterator时使用。 - ArrayObject 用于对象化访问:需要
$obj->property风格时使用。 - 谨慎使用引用标志:引用行为容易导致意外的副作用。
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_LIST | 1 | 属性访问只对声明属性有效 |
ARRAY_AS_PROPS | 2 | 数组元素可以通过属性访问 |
行为差异
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 的选择
| 考量因素 | ArrayIterator | ArrayObject |
|---|---|---|
| 对象语义 | 迭代器 | 数据容器 |
| 可序列化 | 否 | 是 |
| 属性访问 | 不支持 | 支持(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]