PHP Conference Japan 2024

ArrayAccess 介面

(PHP 5, PHP 7, PHP 8)

簡介

提供以陣列方式存取物件的介面。

介面概要

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

範例

範例 #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

/**
* ArrayAndObjectAccess
* Yes you can access class as array and the same time as object
*
* @author Yousef Ismaeil <cliprz@gmail.com>
*/

class ArrayAndObjectAccess implements ArrayAccess {

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

/**
* Get a data by key
*
* @param string The key data to retrieve
* @access public
*/
public function &__get ($key) {
return
$this->data[$key];
}

/**
* Assigns a value to the specified data
*
* @param string The data key to assign the value to
* @param mixed The value to set
* @access public
*/
public function __set($key,$value) {
$this->data[$key] = $value;
}

/**
* Whether or not an data exists by key
*
* @param string An data key to check for
* @access public
* @return boolean
* @abstracting ArrayAccess
*/
public function __isset ($key) {
return isset(
$this->data[$key]);
}

/**
* Unsets an data by key
*
* @param string The key to unset
* @access public
*/
public function __unset($key) {
unset(
$this->data[$key]);
}

/**
* Assigns a value to the specified offset
*
* @param string The offset to assign the value to
* @param mixed The value to set
* @access public
* @abstracting ArrayAccess
*/
public function offsetSet($offset,$value) {
if (
is_null($offset)) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
}
}

/**
* Whether or not an offset exists
*
* @param string An offset to check for
* @access public
* @return boolean
* @abstracting ArrayAccess
*/
public function offsetExists($offset) {
return isset(
$this->data[$offset]);
}

/**
* Unsets an offset
*
* @param string The offset to unset
* @access public
* @abstracting ArrayAccess
*/
public function offsetUnset($offset) {
if (
$this->offsetExists($offset)) {
unset(
$this->data[$offset]);
}
}

/**
* Returns the value at specified offset
*
* @param string The offset to retrieve
* @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 as object '.$foo->fname."\n";
// 以陣列方式呼叫
echo 'lname as array '.$foo['lname']."\n";
// 以陣列方式重新設定
$foo['fname'] = 'Cliprz';
echo
$foo['fname']."\n";

/** 輸出
fname as object Yousef
lname as array Ismaeil
Cliprz
*/

?>
Taliesin Nuin public at taliesinnuin dot net
5 年前
您可能想知道實作 ArrayAccess 介面是否會讓類別變成可迭代的。畢竟,它是一個「陣列」。答案是否定的,它不會。此外,如果您同時新增兩者並希望它是一個關聯陣列,則有一些微妙的陷阱。以下是一個同時具有 ArrayAccess 和 Iterator 介面,以及 Countable 介面(為了完整性)的類別。

<?php
//This uses return types which are only valid in PHP 7. They can be removed if you are forced to use an older version of PHP.
//N.b. The offsetSet method contains a function that is only valid from PHP 7.3 onwards.

class HandyClass implements ArrayAccess, Iterator, Countable {

private
$container = array(); //An Array of your actual values.
private $keys = array(); //We use a separate array of keys rather than $this->position directly so that we can
private $position; //have an associative array.

public function __construct() {
$position = 0;

$this->container = array( //Arbitrary array for demo. You probably want to set this to empty in practice or
"a" => 1, //get it from somewhere else, e.g. passing it into the constructor.
"b" => 2,
"c" => 3,
);
$this->keys = array_keys($this->container);
}

public function
count() : int { //This is necessary for the Countable interface. It could as easily return
return count($this->keys); //count($this->container). The number of elements will be the same.
}

public function
rewind() { //Necessary for the Iterator interface. $this->position shows where we are in our list of
$this->position = 0; //keys. Remember we want everything done via $this->keys to handle associative arrays.
}

public function
current() { //Necessary for the Iterator interface.
return $this->container[$this->keys[$this->position]];
}

public function
key() { //Necessary for the Iterator interface.
return $this->keys[$this->position];
}

public function
next() { //Necessary for the Iterator interface.
++$this->position;
}

public function
valid() { //Necessary for the Iterator interface.
return isset($this->keys[$this->position]);
}

public function
offsetSet($offset, $value) { //Necessary for the ArrayAccess interface.
if(is_null($offset)) {
$this->container[] = $value;
$this->keys[] = array_key_last($this->container); //THIS IS ONLY VALID FROM php 7.3 ONWARDS. See note below for alternative.
} 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); //This line re-indexes the array of container keys because if someone
} //deletes the first element, the rewind to position 0 when iterating would
//cause no element to be found.
public function offsetGet($offset) {
return isset(
$this->container[$offset]) ? $this->container[$offset] : null;
}
}
?>

範例用法

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

echo(
"使用內建測試元素進行 Foreach 迴圈:\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(
"更改元素後的 Foreach 迴圈:\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 {
私有
$container = array();

公開函數
__construct() {
$this->container = array(
"one" => 1,
"two" => 2,
"three" => 3,
);
}

公開函數
offsetSet($offset, $value) {
echo
"offsetSet 方法觸發";

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

公開函數
offsetExists($offset) {
echo
"offsetExists 方法觸發";

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

公開函數
offsetUnset($offset) {
echo
"offsetUnset 方法觸發";

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

公開函數
offsetGet($offset) {
echo
"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 完全模仿一般陣列指派的 functionality。

<?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
class obj implements 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
class MyArrayAccessObject implements 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` 陣列的方法(例如 `offsetSet()`、`offsetUnset()` 等)的結尾呼叫 `$this->supportReset()`。

這樣,您就可以正常使用 `reset()` 函式。

<?php
$firstArrayItem
= reset($myArrayAccessObject);
?>
max at flashdroid dot com
14 年前
在 PHP 5.3.0 中,實作 ArrayAccess 的物件可以透過引用傳回物件。

您可以像這樣實作您的 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