The Note You're Voting On
Taliesin Nuin public at taliesinnuin dot net ¶ 6 years ago
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");
?>