Voting

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

<< Back to user notes page

To Top