ArrayAccess(数组式访问)接口

(No version information available, might only be in Git)

简介

提供像访问数组一样访问对象的能力的接口。

接口摘要

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

Example #1 Basic usage

<?php
class obj implements arrayaccess {
    private 
$container = array();
    public function 
__construct() {
        
$this->container = array(
            
"one"   => 1,
            
"two"   => 2,
            
"three" => 3,
        );
    }
    public function 
offsetSet($offset$value) {
        if (
is_null($offset)) {
            
$this->container[] = $value;
        } else {
            
$this->container[$offset] = $value;
        }
    }
    public function 
offsetExists($offset) {
        return isset(
$this->container[$offset]);
    }
    public function 
offsetUnset($offset) {
        unset(
$this->container[$offset]);
    }
    public function 
offsetGet($offset) {
        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
        )

)

Table of Contents

User Contributed Notes

msherazjaved at gmail dot com 01-Jul-2019 04:53
An experimental check addition to the findings of Per (on offsetExists method) added here (8 years ago).

<?php
class obj implements ArrayAccess {
    private
$container = array();

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

    public function
offsetSet($offset, $value) {
        print
"offsetSet method Triggered";
       
        if (
is_null($offset)) {
           
$this->container[] = $value;
        } else {
           
$this->container[$offset] = $value;
        }
    }

    public function
offsetExists($offset) {
        print
"offsetExists method Triggered";

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

    public function
offsetUnset($offset) {
        print
"offsetUnset method Triggered";

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

    public function
offsetGet($offset) {
        print
"offsetGet method Triggered";

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

$obj = new obj;

## Assigning a Value
$obj['two'] = '2';    // Output: offsetSet method Triggered

## Checking if array offset already set
isset($obj['two']);   // Output : offsetExists method Triggered

## Unsetting array value on the offset 'two'
unset($obj['two']);   // Output : offsetUnset method Triggered

## Accessing array value at offset 'two'
return $obj['two'];   // Output : offsetGet method Triggered

?>
Taliesin Nuin public at taliesinnuin dot net 04-Mar-2019 09:41
You might be wondering whether implementing an ArrayAccess interface makes the class iterable. It is, after all, an "array". The answer is no, it doesn't. Additionally there are a couple of subtle gotchas if you add both and want it to be an associate array. The below is a class that has both ArrayAccess AND Iterator interfaces. And Countable as well just to be complete.

<?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;
  }
}
?>

Example usages:

<?php
$myClass
= new HandyClass();
echo(
'Number of elements: ' . count($myClass) . "\n\n");

echo(
"Foreach through the built in test elements:\n");
foreach(
$myClass as $key => $value) {
  echo(
"$value\n");
}
echo(
"\n");

$myClass['d'] = 4;
$myClass['e'] = 5;
echo(
'Number of elements after adding two: ' . count($myClass) . "\n\n");

unset(
$myClass['a']);
echo(
'Number of elements after removing one: ' . count($myClass) . "\n\n");

echo(
"Accessing an element directly:\n");
echo(
$myClass['b'] . "\n\n");

$myClass['b'] = 5;
echo(
"Foreach after changing an element:\n");
foreach(
$myClass as $key => $value) {
  echo(
"$value\n");
}
echo(
"\n");
?>
ProgMiner 10-Jan-2018 08:07
Maybe it help anyone, if you do
<?php
$arrayAccessObject
[] = 'foo';
?>
PHP give you an empty string as $offset to offsetSet($offset, $value) method.
headplan at gmail dot com 14-Nov-2017 09:06
整理前面各位大仙的解决方法.
<?php

namespace InterfaceTest;

use
ArrayAccess;

class
TestArrayAccess implements ArrayAccess
{
   
# 存储数据
   
private $data = [];

   
/**
     * 以对象的方式访问数组中的数据
     *
     * @param $key
     * @return mixed
     */
   
public function __get($key)
    {
        return
$this->data[$key];
    }

   
/**
     * 以对象方式添加一个数组元素
     *
     * @param $key
     * @param $val
     */
   
public function __set($key, $val)
    {
       
$this->data[$key] = $val;
    }

   
/**
     * 以对象方式判断数组元素是否设置
     *
     * @param $key
     * @return bool
     */
   
public function __isset($key)
    {
        return isset(
$this->data[$key]);
    }

   
/**
     * 以对象方式删除一个数组元素
     *
     * @param $key
     */
   
public function __unset($key)
    {
        unset(
$this->data[$key]);
    }

   
/**
     * @param mixed $offset
     * @return mixed|null
     */
   
public function offsetGet($offset)
    {
        return
$this->offsetExists($offset) ? $this->data[$offset] : null;
    }

   
/**
     * @param mixed $offset
     * @param mixed $value
     */
   
public function offsetSet($offset, $value)
    {
        if (
is_null($offset)) {
           
$this->data[] = $value;
        } else {
           
$this->data[$offset] = $value;
        }
    }

   
/**
     * @param mixed $offset
     * @return bool
     */
   
public function offsetExists($offset)
    {
        return isset(
$this->data[$offset]);
    }

   
/**
     * @param mixed $offset
     */
   
public function offsetUnset($offset)
    {
        if (
$this->offsetExists($offset)) {
            unset(
$this->data[$offset]);
        }
    }

   
/**
     * @return array
     */
   
public function &__invoke()
    {
        return
$this->data;
    }
}

$testArrayAccess = new \InterfaceTest\TestArrayAccess();

$testArrayAccess['one'] = '小明';
$testArrayAccess->two = '小红';
$testArrayAccess['three'] = '邪皇';
$testArrayAccess->four = '魔伶'; # 调用ArrayAndObjectAccess::__set
$testArrayAccess['five'] = '天尊'; # 调用ArrayAndObjectAccess::offsetSet

var_dump(isset($testArrayAccess->one)); # 调用ArrayAndObjectAccess::__isset
unset($testArrayAccess->two); # 调用ArrayAndObjectAccess::__unset

var_dump($testArrayAccess->one); # 调用ArrayAndObjectAccess::__get
var_dump($testArrayAccess->three);
var_dump($testArrayAccess->four);
var_dump($testArrayAccess->five);

var_dump(isset($testArrayAccess['one'])); # 调用ArrayAndObjectAccess::offsetExists
unset($testArrayAccess['two']); # 调用ArrayAndObjectAccess::offsetUnset

var_dump($testArrayAccess['one']); # 调用ArrayAndObjectAccess::offsetGet
var_dump($testArrayAccess['three']);
var_dump($testArrayAccess['four']);
var_dump($testArrayAccess['five']);

var_dump($testArrayAccess);
var_dump($testArrayAccess());
var_dump(array_values($testArrayAccess()));
$var = 'hi';
$testArrayAccess['a'] = $var;
$testArrayAccess()['b'] = &$var;
$var = 'baby';
array_push($testArrayAccess(),'test'); # int array_push ( array &$array , mixed $value1 [, mixed $... ] )
var_dump($testArrayAccess);
var_dump(reset($testArrayAccess())); # mixed reset ( array &$array )
var_dump(array_key_exists('five',$testArrayAccess()));
Soaku 01-Sep-2017 04:01
When working in namespaces ALWAYS remember to prefix the ArrayAccess name with \. It will think you mean an undefined interface.

This is a bit weird, because this interface *could* be global - You cannot define a class or interface with this name, even in a namespace.

Hope it helps someone.
igorsantos07 at gibberish dot gmail dot com 24-Jun-2017 07:48
Building on comments about incompatibility between plain arrays and ArrayAccess objects, many (most?) of the array_* methods won't work with ArrayAccess objects.
Simple calls such as sizeof() work, but array_values() will throw an error, for instance.

Don't expect much magic when implementing this.
Aussie Bags 12-May-2017 10:57
Although $offset can be anything, a string that looks like an integer is cast to integer before the call to any of the methods.

$x[1]  offset is integer 1
$x['1'] offset is integer 1
$x['1.'] offset is string '1.'
jordistc at gmail dot com 31-Jul-2016 11:07
You can use the array functions on a object of a class that implements ArrayAccess using the __invoke magic method in this way:

<?php
class ArrayVar implements ArrayAccess
{
    private
$data = [];

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

Now you can use it in this way:
<?php
    $arrayar
= new ArrayVar();
   
$arrayar['one'] = 'primer';
   
$arrayar['two'] = 'segon';
   
$arrayar['three'] = 'tercer';

   
$keys = array_keys($arrayar());
   
var_dump($keys);
   
// array (size=3)
    //    0 => string 'one'
    //    1 => string 'two'
    //    2 => string 'three'

   
$diff = array_diff($arrayar(), [ 'two' => 'segon']);
   
var_dump($diff);
   
// array (size=2)
    //    'one' => string 'primer'
    //    'three' => string 'tercer'
?>
luc at s dot illi dot be 28-May-2016 09:51
Note, that the ArrayAccess class is not limited to scalar keys:

<?php
class A implements ArrayAccess
{
    function
offsetSet($a, $b)
    {
       
var_dump(func_get_args());
    }
   
    function
offsetGet($a)
    {
       
var_dump(func_get_args());
    }
   
    function
offsetExists($a)
    {
       
var_dump(func_get_args());
    }
   
    function
offsetUnset($a)
    {
       
var_dump(func_get_args());
    }
}

class
B{}
class
C{}

$A = new A;
$A[[1,2,3]] = [4,5,6];
$A[new B] = new C;

/*
array(2) {
  [0]=>
  array(3) {
    [0]=>
    int(1)
    [1]=>
    int(2)
    [2]=>
    int(3)
  }
  [1]=>
  array(3) {
    [0]=>
    int(4)
    [1]=>
    int(5)
    [2]=>
    int(6)
  }
}
array(2) {
  [0]=>
  object(web\B)#3 (0) {
  }
  [1]=>
  object(web\C)#4 (0) {
  }
}
*/
?>
nick at little-apps dot com 20-Apr-2016 10:22
A class that implements ArrayAccess will not work with array_push

For example:

<?php
class TestArrayAccess implements ArrayAccess {
    private
$container = array();

   
// ArrayAccess methods
}

$obj = new TestArrayAccess();

array_push($obj, 'Hello World!'); // Nothing will be added
?> 

One way of being able to use array_push would be by adding a toArray() method (note the return value is a reference).

<?php
class TestArrayAccess implements ArrayAccess {
    private
$container = array();
   
   
// ArrayAccess methods
   
   
public function &toArray() {
        return
$this->container;
     }
}

$obj = new TestArrayAccess();

array_push($obj->toArray(), 'Hello World!'); // Will now be added to array
?>
kaRemovTihsjouni at gmAndTihsaildot com 30-Jun-2015 03:03
reset() method may not work as you expect with ArrayAccess objects.

Using reset($myArrayAccessObject) returns the first property from $myArrayAccessObject, not the first item in the items array.

If you want to use the reset() method to return the first array item, then you can use the following simple workaround:

<?php
class MyArrayAccessObject implements Iterator, ArrayAccess, Countable {
    protected
$first = null; //WARNING! Keep this always first.
   
protected $items = null;
    private function
supportReset() {
     
$this->first = reset($this->items); //Support reset().
   
}
   
// ...
   
public function offsetSet($offset, $value) {
        if (
$offset === null) {
           
$this->items[] = $value;
        }
        else {
           
$this->items[$offset] = $value;
        }
       
$this->supportReset();
    }
}
?>

Finally, call $this->supportReset() in the end of all methods that change the internal $items array, such as in offsetSet(), offsetUnset() etc.

This way, you can use the reset() method as normally:

<?php
$firstArrayItem
= reset($myArrayAccessObject);
?>
php at lanar dot com dot au 27-Jan-2014 01:35
Objects implementing ArrayAccess do not support the increment/decrement operators ++ and --, unlike array() and ArrayObject()

<?php

class MyArray implements ArrayAccess
{
   
// offsetSet, offsetGet etc implemented
}

$x = new MyArray() ;
$x[0] = 0 ;
$x[0]++ ; //error 'Indirect modification of overloaded element has no effect'
$x[0] += 1 ; // this works OK.

?>
Yousef Ismaeil Cliprz 11-Dec-2013 07:38
<?php

/**
 * ArrayAndObjectAccess
 * Yes you can access class as array and the same time as object
 *
 * @author Yousef Ismaeil <[email protected]>
 */

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;
    }

}

?>

Usage

<?php
$foo
= new ArrayAndObjectAccess();
// Set data as array and object
$foo->fname = 'Yousef';
$foo->lname = 'Ismaeil';
// Call as object
echo 'fname as object '.$foo->fname."\n";
// Call as array
echo 'lname as array '.$foo['lname']."\n";
// Reset as array
$foo['fname'] = 'Cliprz';
echo
$foo['fname']."\n";

/** Outputs
fname as object Yousef
lname as array Ismaeil
Cliprz
*/

?>
ivan dot dossev at gmail dot com 20-Apr-2013 07:09
Sadly you cannot assign by reference with the ArrayAccess (at least in PHP 5.3.23)
It's too bad there is no syntax for optionally passing variables by reference to functions (a feature in retro PHP).
That option would have let ArrayAccess fully mimic the functionality of normal array assignments:

<?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"
?>

Declaring "function offsetSet($offset, &$value)" will cause a fatal error.
So to assign by ref you can use an ugly function call, for example:

<?php
class obj implements ArrayAccess {

   
// ... ArrayAccess example code ...
   
   
public function &offsetSetRef($offset, &$value) {
        if (
is_null($offset)) {
           
$this->container[] = &$value;
        } else {
           
$this->container[$offset] = &$value;
        }
        return
$value; // should return in case called within an assignment chain
   
}
}

$var = 'hello';
$obj = new obj();
$obj[0] = $var;
//$obj[1] = &$var; // Fatal error: Cannot assign by reference to overloaded object
$obj->offsetSetRef(1, $var); // the work around
$var = 'world';
var_dump($obj[0], $obj[1]);

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

?>
Hayley Watson 31-Mar-2013 12:25
The indexes used in an ArrayAccess object are not limited to strings and integers as they are for arrays: you can use any type for the index as long as you write your implementation to handle them. This fact is exploited by the SplObjectStorage class.
jojor at gmx dot net 12-Dec-2012 01:08
Conclusion: Type hints \ArrayAccess and array are not compatible.

<?php

    
class MyArrayAccess implements \ArrayAccess
    
{
         public function
offsetExists($offset)
         {

         }

         public function
offsetSet($offset, $value)
         {

         }

         public function
offsetGet($offset)
         {

         }

         public function
offsetUnset($offset)
         {

         }
     }


     function
test(array $arr)
     {
     }

     function
test2(\ArrayAccess $arr)
     {

     }


    
$arrObj = new MyArrayAccess();
    
test([]); //result: works!
    
test($arrObj); //result: does NOT work
    
test2([]); //result: does NOT work
    
test2($arrObj); // result: works!
?>
Per 20-May-2011 02:16
It bit me today, so putting it here in the hope it will help others:
If you call array_key_exists() on an object of a class that implements ArrayAccess, ArrayAccess::offsetExists() wil NOT be called.
max at flashdroid dot com 05-Apr-2010 06:49
Objects implementing ArrayAccess may return objects by references in PHP 5.3.0.

You can implement your ArrayAccess object like this:

    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);
        }

        ...

    }

This base class allows you to get / set your object properties using the [] operator just like in Javascript:

    class Boo extends Reflectable {
        public $name;
    }

    $obj = new Boo();
    $obj['name'] = "boo";
    echo $obj['name']; // prints boo