PHP Conference Japan 2024

ArrayAccess 接口

(PHP 5, PHP 7, PHP 8)

简介

提供将对象作为数组访问的接口。

接口概要

interface ArrayAccess {
/* 方法 */
public offsetExists(mixed $offset): bool
public offsetGet(mixed $offset): mixed
public offsetSet(mixed $offset, mixed $value): void
public offsetUnset(mixed $offset): void
}

示例

示例 #1 基本用法

<?php
class Obj implements ArrayAccess {
public
$container = [
"one" => 1,
"two" => 2,
"three" => 3,
];

public function
offsetSet($offset, $value): void {
if (
is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}

public function
offsetExists($offset): bool {
return isset(
$this->container[$offset]);
}

public function
offsetUnset($offset): void {
unset(
$this->container[$offset]);
}

public function
offsetGet($offset): mixed {
return isset(
$this->container[$offset]) ? $this->container[$offset] : null;
}
}

$obj = new Obj;

var_dump(isset($obj["two"]));
var_dump($obj["two"]);
unset(
$obj["two"]);
var_dump(isset($obj["two"]));
$obj["two"] = "A value";
var_dump($obj["two"]);
$obj[] = 'Append 1';
$obj[] = 'Append 2';
$obj[] = 'Append 3';
print_r($obj);
?>

以上示例将输出类似以下内容

bool(true)
int(2)
bool(false)
string(7) "A value"
obj Object
(
    [container:obj:private] => Array
        (
            [one] => 1
            [three] => 3
            [two] => A value
            [0] => Append 1
            [1] => Append 2
            [2] => Append 3
        )

)

目录

添加注释

用户贡献的注释 12 条注释

Per
13 年前
它今天咬了我一口,所以把它放在这里希望它能帮助其他人
如果对实现 ArrayAccess 的类的对象调用 array_key_exists(),则不会调用 ArrayAccess::offsetExists()。
Yousef Ismaeil Cliprz
10 年前
<?php

/**
* 数组和对象访问
* 是的,您可以同时像数组一样访问类,也像对象一样访问
*
* @author Yousef Ismaeil <[email protected]>
*/

class ArrayAndObjectAccess implements ArrayAccess {

/**
* 数据
*
* @var array
* @access private
*/
private $data = [];

/**
* 通过键获取数据
*
* @param string 要检索的数据键
* @access public
*/
public function &__get ($key) {
return
$this->data[$key];
}

/**
* 为指定数据分配一个值
*
* @param string 要为其分配值的键
* @param mixed 要设置的值
* @access public
*/
public function __set($key,$value) {
$this->data[$key] = $value;
}

/**
* 是否通过键存在数据
*
* @param string 要检查的数据键
* @access public
* @return boolean
* @abstracting ArrayAccess
*/
public function __isset ($key) {
return isset(
$this->data[$key]);
}

/**
* 通过键取消设置数据
*
* @param string 要取消设置的键
* @access public
*/
public function __unset($key) {
unset(
$this->data[$key]);
}

/**
* 为指定的偏移量分配一个值
*
* @param string 要为其分配值的偏移量
* @param mixed 要设置的值
* @access public
* @abstracting ArrayAccess
*/
public function offsetSet($offset,$value) {
if (
is_null($offset)) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
}
}

/**
* 是否存在偏移量
*
* @param string 要检查的偏移量
* @access public
* @return boolean
* @abstracting ArrayAccess
*/
public function offsetExists($offset) {
return isset(
$this->data[$offset]);
}

/**
* 取消设置偏移量
*
* @param string 要取消设置的偏移量
* @access public
* @abstracting ArrayAccess
*/
public function offsetUnset($offset) {
if (
$this->offsetExists($offset)) {
unset(
$this->data[$offset]);
}
}

/**
* 返回指定偏移量处的值
*
* @param string 要检索的偏移量
* @access public
* @return mixed
* @abstracting ArrayAccess
*/
public function offsetGet($offset) {
return
$this->offsetExists($offset) ? $this->data[$offset] : null;
}

}

?>

用法

<?php
$foo
= new ArrayAndObjectAccess();
// 将数据设置为数组和对象
$foo->fname = 'Yousef';
$foo->lname = 'Ismaeil';
// 作为对象调用
echo 'fname 作为对象 '.$foo->fname."\n";
// 作为数组调用
echo 'lname 作为数组 '.$foo['lname']."\n";
// 作为数组重置
$foo['fname'] = 'Cliprz';
echo
$foo['fname']."\n";

/** 输出
fname 作为对象 Yousef
lname 作为数组 Ismaeil
Cliprz
*/

?>
Taliesin Nuin 公开于 taliesinnuin 点 net
5 年前
您可能想知道实现 ArrayAccess 接口是否使类可迭代。毕竟,它是一个“数组”。答案是否定的,它不是。此外,如果您同时添加两者并希望它成为关联数组,则会有一些细微的陷阱。下面是一个同时具有 ArrayAccess 和 Iterator 接口的类。以及 Countable,以使其完整。

<?php
//此代码使用了 PHP 7 中才有的返回值类型。如果您必须使用旧版本的 PHP,可以将其删除。
//注意:offsetSet 方法包含一个仅在 PHP 7.3 及更高版本中才有效的函数。

class HandyClass implements ArrayAccess, Iterator, Countable {

private
$container = array(); //实际值的数组。
private $keys = array(); //我们使用一个单独的键数组而不是 $this->position,以便于
private $position; //处理关联数组。

public function __construct() {
$position = 0;

$this->container = array( //演示用的任意数组。在实际应用中,您可能希望将其设置为为空,或
"a" => 1, //从其他地方获取,例如将其传递给构造函数。
"b" => 2,
"c" => 3,
);
$this->keys = array_keys($this->container);
}

public function
count() : int { //Countable 接口必需。它也可以返回
return count($this->keys); //count($this->container)。元素数量相同。
}

public function
rewind() { //Iterator 接口必需。$this->position 显示我们在键列表中的位置
$this->position = 0; //记住,我们希望所有操作都通过 $this->keys 进行,以处理关联数组。
}

public function
current() { //Iterator 接口必需。
return $this->container[$this->keys[$this->position]];
}

public function
key() { //Iterator 接口必需。
return $this->keys[$this->position];
}

public function
next() { //Iterator 接口必需。
++$this->position;
}

public function
valid() { //Iterator 接口必需。
return isset($this->keys[$this->position]);
}

public function
offsetSet($offset, $value) { //ArrayAccess 接口必需。
if(is_null($offset)) {
$this->container[] = $value;
$this->keys[] = array_key_last($this->container); //此函数仅在 PHP 7.3 及更高版本中有效。请参见下面的注释以了解替代方法。
} else {
$this->container[$offset] = $value;
if(!
in_array($offset, $this->keys)) $this->keys[] = $offset;
}
}

public function
offsetExists($offset) {
return isset(
$this->container[$offset]);
}

public function
offsetUnset($offset) {
unset(
$this->container[$offset]);
unset(
$this->keys[array_search($offset,$this->keys)]);
$this->keys = array_values($this->keys); //此行重新索引容器键数组,因为如果有人
} //删除第一个元素,则在迭代时回退到位置 0 将找不到任何元素。
public function offsetGet($offset) {
return isset(
$this->container[$offset]) ? $this->container[$offset] : null;
}
}
?>

使用示例

<?php
$myClass
= new HandyClass();
echo(
'元素数量:' . count($myClass) . "\n\n");

echo(
"遍历内置测试元素:\n");
foreach(
$myClass as $key => $value) {
echo(
"$value\n");
}
echo(
"\n");

$myClass['d'] = 4;
$myClass['e'] = 5;
echo(
'添加两个元素后的数量:' . count($myClass) . "\n\n");

unset(
$myClass['a']);
echo(
'移除一个元素后的数量:' . count($myClass) . "\n\n");

echo(
"直接访问元素:\n");
echo(
$myClass['b'] . "\n\n");

$myClass['b'] = 5;
echo(
"修改元素后的遍历:\n");
foreach(
$myClass as $key => $value) {
echo(
"$value\n");
}
echo(
"\n");
?>
Hayley Watson
11 年前
ArrayAccess 对象中使用的索引不限于字符串和整数(数组的索引限制),只要您编写实现来处理它们,就可以使用任何类型的索引。SplObjectStorage 类利用了这一特性。
Aussie Bags
7 年前
尽管 $offset 可以是任何类型,但类似于整数的字符串在调用任何方法之前会被转换为整数。

$x[1] 的偏移量为整数 1
$x['1'] 的偏移量为整数 1
$x['1.'] 的偏移量为字符串 '1.'
msherazjaved at gmail dot com
5 年前
对 Per(关于 offsetExists 方法的发现)的实验性检查补充添加于此(8 年前)。



<?php
obj 实现 ArrayAccess {
private
$container = array();

public function
__construct() {
$this->container = array(
"one" => 1,
"two" => 2,
"three" => 3,
);
}

public function
offsetSet($offset, $value) {
print
"offsetSet 方法触发";

if (
is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}

public function
offsetExists($offset) {
print
"offsetExists 方法触发";

return isset(
$this->container[$offset]);
}

public function
offsetUnset($offset) {
print
"offsetUnset 方法触发";

unset(
$this->container[$offset]);
}

public function
offsetGet($offset) {
print
"offsetGet 方法触发";

return isset(
$this->container[$offset]) ? $this->container[$offset] : null;
}
}

$obj = new obj;

## 赋值
$obj['two'] = '2'; // 输出:offsetSet 方法触发

## 检查数组偏移量是否已设置
isset($obj['two']); // 输出:offsetExists 方法触发

## 取消设置偏移量 'two' 处的数组值
unset($obj['two']); // 输出:offsetUnset 方法触发

## 访问偏移量 'two' 处的数组值
return $obj['two']; // 输出:offsetGet 方法触发

?>
ivan dot dossev at gmail dot com
11 年前
遗憾的是,你无法使用 ArrayAccess 进行引用赋值(至少在 PHP 5.3.23 中是这样)。
很可惜,没有语法可以可选地将变量按引用传递给函数(复古 PHP 中的一个特性)。
这个选项可以让 ArrayAccess 完全模仿普通数组赋值的功能。

<?php
$var
= 'hello';
$arr = array();
$arr[0] = $var;
$arr[1] = &$var;
$var = 'world';
var_dump($arr[0], $arr[1]);

// string(5) "hello"
// string(5) "world"
?>

声明 "function offsetSet($offset, &$value)" 将导致致命错误。
因此,要进行引用赋值,可以使用一个丑陋的函数调用,例如

<?php
obj 实现 ArrayAccess {

// ... ArrayAccess 示例代码 ...

public function &offsetSetRef($offset, &$value) {
if (
is_null($offset)) {
$this->container[] = &$value;
} else {
$this->container[$offset] = &$value;
}
return
$value; // 在赋值链中调用时应返回
}
}

$var = 'hello';
$obj = new obj();
$obj[0] = $var;
//$obj[1] = &$var; // 致命错误:无法对重载对象进行引用赋值
$obj->offsetSetRef(1, $var); // 解决方法
$var = 'world';
var_dump($obj[0], $obj[1]);

// string(5) "hello"
// string(5) "world"

?>
kaRemovTihsjouni at gmAndTihsaildot com
9 年前
reset() 方法在 ArrayAccess 对象上可能无法按预期工作。

使用 reset($myArrayAccessObject) 返回 $myArrayAccessObject 的第一个属性,而不是项目数组中的第一个项目。

如果要使用 reset() 方法返回第一个数组项目,则可以使用以下简单解决方法

<?php
MyArrayAccessObject 实现 Iterator, ArrayAccess, Countable {
protected
$first = null; //警告!始终保持此为第一个。
protected $items = null;
private function
supportReset() {
$this->first = reset($this->items); //支持 reset()。
}
// ...
public function offsetSet($offset, $value) {
if (
$offset === null) {
$this->items[] = $value;
}
else {
$this->items[$offset] = $value;
}
$this->supportReset();
}
}
?>

最后,在所有更改内部 $items 数组的方法的末尾调用 $this->supportReset(),例如在 offsetSet()、offsetUnset() 等中。

这样,就可以像往常一样使用 reset() 方法

<?php
$firstArrayItem
= reset($myArrayAccessObject);
?>
max at flashdroid dot com
14 年前
实现 ArrayAccess 的对象可以在 PHP 5.3.0 中按引用返回对象。

您可以像这样实现 ArrayAccess 对象

class Reflectable implements ArrayAccess {

public function set($name, $value) {
$this->{$name} = $value;
}

public function &get($name) {
return $this->{$name};
}

public function offsetGet($offset) {
return $this->get($offset);
}

public function offsetSet($offset, $value) {
$this->set($offset, $value);
}

...

}

此基类允许您像在 Javascript 中一样使用 [] 运算符获取/设置对象属性

class Boo extends Reflectable {
public $name;
}

$obj = new Boo();
$obj['name'] = "boo";
echo $obj['name']; // 输出 boo
ProgMiner
6 年前
也许对某些人有帮助,如果你这样做
<?php
$arrayAccessObject
[] = 'foo';
?>
PHP 会将空字符串作为 $offset 传递给 offsetSet($offset, $value) 方法。
php at lanar dot com dot au
10 年前
实现 ArrayAccess 的对象不支持增量/减量运算符 ++ 和 --,这与 array() 和 ArrayObject() 不同。

<?php

MyArray 实现 ArrayAccess
{
// offsetSet、offsetGet 等已实现
}

$x = new MyArray() ;
$x[0] = 0 ;
$x[0]++ ; //错误“重载元素的间接修改无效”
$x[0] += 1 ; // 这可以正常工作。

?>
jordistc at gmail dot com
8 年前
您可以通过这种方式使用 __invoke 魔术方法对实现 ArrayAccess 的类的对象使用数组函数

<?php
ArrayVar 实现 ArrayAccess
{
private
$data = [];

public function
__invoke()
{
return
$this->data;
}
}
?>

现在您可以这样使用它
<?php
$arrayar
= new ArrayVar();
$arrayar['one'] = 'primer';
$arrayar['two'] = 'segon';
$arrayar['three'] = 'tercer';

$keys = array_keys($arrayar());
var_dump($keys);
// 数组 (大小: 3)
// 0 => 字符串 'one'
// 1 => 字符串 'two'
// 2 => 字符串 'three'

$diff = array_diff($arrayar(), [ 'two' => 'segon']);
var_dump($diff);
// 数组 (大小: 2)
// 'one' => 字符串 'primer'
// 'three' => 字符串 'tercer'
?>
To Top