今天我被這個問題困擾了,所以把它放在這裡,希望能幫助其他人
如果您在實作 ArrayAccess 的類別的物件上呼叫 array_key_exists(),則不會呼叫 ArrayAccess::offsetExists()。
(PHP 5, PHP 7, PHP 8)
提供以陣列方式存取物件的介面。
範例 #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 ) )
今天我被這個問題困擾了,所以把它放在這裡,希望能幫助其他人
如果您在實作 ArrayAccess 的類別的物件上呼叫 array_key_exists(),則不會呼叫 ArrayAccess::offsetExists()。
<?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
*/
?>
您可能想知道實作 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");
?>
ArrayAccess 物件中使用的索引不限於字串和整數,就像陣列一樣:您可以使用任何類型作為索引,只要您編寫的實作可以處理它們即可。SplObjectStorage 類別利用了這個特性。
雖然 $offset 可以是任何東西,但在呼叫任何方法之前,看起來像整數的字串會被強制轉換為整數。
$x[1] 位移是整數 1
$x['1'] 位移是整數 1
$x['1.'] 位移是字串 '1.'
在此新增了對 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 方法觸發
?>
很遺憾,你無法使用 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"
?>
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);
?>
在 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
如果您這樣做,或許對某些人有幫助
<?php
$arrayAccessObject[] = 'foo';
?>
PHP 會將一個空字串作為 `$offset` 傳遞給 `offsetSet($offset, $value)` 方法。
實作 ArrayAccess 的物件不支援遞增/遞減運算子 ++ 和 --,不像 `array()` 和 `ArrayObject()`。
<?php
類別 MyArray 實作 ArrayAccess
{
// 已實作 offsetSet, offsetGet 等方法
}
$x = new MyArray() ;
$x[0] = 0 ;
$x[0]++ ; //錯誤 '間接修改重載元素無效'
$x[0] += 1 ; // 這個可以正常運作。
?>
您可以透過 __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'
?>