Reputation: 61
I am just learning about OOP in PHP from a book and the section on Iterators and Iteration has me stumped.
For what I understand, I think in order to loop through an object's attributes, you need to implement the built-in class Iterator. Then implement the IteratorAggregate interface and create a getIterator method within. However, I am currently confused on the role each of these elements play and in what format they need to be written. In other words, I am just looking for a simply (plain-English) explanation of these concepts and a simple example. Any help would be greatly appreciated!!!!!
And thank you for your time and help in advance!!!!
Upvotes: 0
Views: 331
Reputation: 86506
The thing you want to loop over must implement Traversable
. However, you can't implement Traversable
directly; you have to implement one of its subtypes. How you'll do things depends on how the object will be used.
If you wanted, you could just implement Iterator
. That works well enough if your type is intended to be a forward-only list of stuff already. If you decide to build an iterator, you'll have 5 methods to implement:
current
, which retrieves the value at the current location;key
, which retrieves the current location's key;next
, which advances to the next location;rewind
, which resets the current location to the first; andvalid
, which returns false
if iteration has fallen off the end of the list of stuff being iterated over.PHP calls those methods over the course of iteration. Specifically, let's say you have code like this:
foreach ($it as $key => $value) {
doStuffWith($key, $value);
}
This is rather equivalent to the following:
for ($it->rewind(); $it->valid(); $it->next()) {
$value = $it->current();
$key = $it->key(); // only if the foreach has a `$key =>`
doStuffWith($key, $value);
}
Basically you just need to build a type that implements Iterator
and responds properly to those methods being invoked in roughly that order. Ideally it should also be possible for something to happen in between...but that's usually not an issue unless you're passing references around.
If you don't need custom iteration, you could instead just implement IteratorAggregate
, and return an existing iterator type if it's able to do what you need. (For example, if you want to allow looping over an internal array, there's already an ArrayIterator
made for the job. No need to roll your own.) For IteratorAggregate
, you only have to implement getIterator
, which returns something that's traversable. This is a better solution if you have something that can already be traversed by one of the built-in SPL iterators, or can easily be reduced to an array or something.
That same loop above, if called on an IteratorAggregate, would equate to something like
foreach ($it->getIterator() as $key => $value) {
doStuffWith($key, $value);
}
getIterator()
has to return either an implementation of Iterator
or some primitive traversable thingie, like an array.
As for Java-style iterators, you might build an Iterator subtype that can remember its place and loop over your collection, and then implement IteratorAggregate
on your collection to return an instance of the iterator type.
Upvotes: 2
Reputation: 12806
For basic functionality you only really need to implement Iterator
and add the relevant functionality for the rewind
, valid
, key
, current
, and next
methods. See below for an example:
/**
* Awesome
*
* Do awesome stuff
*/
final class Awesome implements Iterator
{
/**
* An array of data
*
* @access private
* @var array $_data
*/
private $_data;
/**
* Store the initial data
*
* @access public
* @param array $data
*/
public function __construct(array $data = array())
{
$this->_data = $data;
}
/**
* Rewind the iterator
*
* @access public
*/
public function rewind()
{
reset($this->_data);
}
/**
* Validate the existence of the next element
*
* @access public
* @return boolean
*/
public function valid()
{
return isset($this->_data[$this->key()]);
}
/**
* Return the current key
*
* @access public
* @return integer
*/
public function key()
{
return key($this->_data);
}
/**
* Return the current value
*
* @access public
* @return mixed
*/
public function current()
{
return current($this->_data);
}
/**
* Increment the iteration index
*
* @access public
*/
public function next()
{
next($this->_data);
}
}
// Instantiate a new Awesome object
$awesome = new Awesome(array('Michael', ' ', 'Rushton', ' ', 'is', ' ', 'awesome', '!'));
// Iterate over the awesome object and output a universal truth
foreach ($awesome as $almost_awesome)
{
echo $almost_awesome;
}
If you wish to instead iterate over the object's properties simply change the __construct
to:
/**
* Construct the object
*
* @access public
*/
public function __construct()
{
// Get the properties
$object_vars = get_object_vars($this);
// Unset the data reference
unset($object_vars['_data']);
// Set the data
$this->_data = $object_vars;
}
Upvotes: 0