Backend DevelopmentIteratorsWeb Development

PHP Iterators Part 3: SPL Filter Iterator, Append Iterator, and Multiple Iterator

We will talking in this part of the series (PHP Iterators) about there other important iterators which are Filter Iterator, Append Iterator, and Multiple Iterator.

 

 

Series Topics:

 

Filter Iterator

This iterator acts like the php function array_filter() which filters array values according to custom callback, but this iterator acts as Object Oriented Manner.

The filter iterator is an abstract iterator that filters out unwanted values. To use it you must extend it to implement custom iterator filters.

The most important method in this iterator is FilterIterator::accept() and it must be implemented in the subclasses.

 
abstract FilterIterator extends IteratorIterator implements OuterIterator {
/* Some iterator methods */
public abstract bool accept ( void )
public __construct ( Iterator $iterator )
public mixed current ( void )
public Iterator getInnerIterator ( void )

 

Let’s see an example in action, suppose given the below array:

<?php

$products = array(
    array(
        'name' => 'Pants',
        'available' => 1,
        'sold' => 1,
    ),
    array(
        'name' => 'Dresses',
        'available' => 1,
        'sold' => 0,
    ),
    array(
        'name' => 'Jackets',
        'available' => 1,
        'sold' => 1,
    ),
    array(
        'name' => 'Shoes',
        'available' => 1,
        'sold' => 0,
    ),
);

And we want to filter the the array items and get only the sold items, may be someone can do something like this:

<?php

$filtered = array_filter($products, function($val) {
    return $val['sold']==1?true:false;
});

Now using the Filter Iterator we can do this:

class SellableItems extends FilterIterator
{
    public function accept()
    {
        $current = $this->current();

        if ($current['sold'] == 1) {
            return true;
        }

        return false;
    }
}

$sellable = new SellableItems(new ArrayIterator($products));

foreach($sellable as $key => $value)
{
   echo $value['name']."<br/>";
}

// output
Pants
Jackets

As shown in the code above we extend the FilterIterator class and implemented accept() method. This method must return a boolean value. If this value is false then the item is removed from the array otherwise it’s remain.

 

The FilterIterator constructor accept an iterator object not array so we injected the ArrayIterator of $products like this:

$sellable = new SellableItems($products);   // Invalid

$sellable = new SellableItems(new ArrayIterator($products));   // Valid

 

Let’s see another example:

<?php
class UserFilter extends FilterIterator
{
    private $userFilter;
   
    public function __construct(Iterator $iterator , $filter )
    {
        parent::__construct($iterator);
        $this->userFilter = $filter;
    }
   
    public function accept()
    {
        $user = $this->getInnerIterator()->current();
        if( strcasecmp($user['name'],$this->userFilter) == 0) {
            return false;
        }       
        return true;
    }
}

$array = array(
   array('name' => 'Jonathan','id' => '5'),
   array('name' => 'Abdul' ,'id' => '22')
);


$object = new ArrayObject($array);

// Note it is case insensitive check in our example due the usage of strcasecmp function
$iterator = new UserFilter($object->getIterator(),'abdul');

foreach ($iterator as $result) {
    echo $result['name'];
}

/* Outputs Jonathan */

In the example here we filtered the array based on a given value then we used the ArrayObject to retrieve the inner iterator using Arraybject::getIterator().

 

Append Iterator

The append iterator like the array_push() in arrays but for iterators used to iterate over several iterators one after the other so it looks like a long queue or string.

 
AppendIterator extends IteratorIterator implements OuterIterator {
    /* Some iterator methods */
    public __construct ( void )
    public void append ( Iterator $iterator )
    public mixed current ( void )
    public ArrayIterator getArrayIterator ( void )
    public Iterator getInnerIterator ( void )
}

The append iterator incorporates a method AppendIterator::append() which takes an iterator object as an argument and pushes it into the iterator queue most like a multi dimensional array.

 

Let’s look at this example:

$multiple = new AppendIterator();

$iterator1 = new ArrayIterator(range('A', 'C'));
$iterator2 = new ArrayIterator(range('D', 'F'));

$multiple->append($iterator1);
$multiple->append($iterator2);


foreach ($multiple as $key => $value) {
    $iteratorIndex = $multiple->getIteratorIndex();

    echo "(Iterator Index: $iteratorIndex) [$key] => $value <br/>";
}


// output
(Iterator Index: 0)[0] => A
(Iterator Index: 0)[1] => B
(Iterator Index: 0)[2] => C
(Iterator Index: 1)[0] => D
(Iterator Index: 1)[1] => E
(Iterator Index: 1)[2] => F

As shown here you need to use append() for each iterator you want to append. Additionally it’s possible to obtain the inner iterator that is currently iterated over with getInnerIterator() similar to other standard iterators. Also it’s possible to get all iterators via getArrayIterator()

 

Another example

    
    $array1 = new ArrayIterator( array('php', 'perl', 'python', 'c') );
    $array2 = new ArrayIterator( array('singleton', 'factory', 'strategy') );
    $array3 = new ArrayIterator( array('yellow', 'blue', 'red', 'white') );
    
    // Create iterator and add all arrays to it
    $iterator = new AppendIterator();
    $iterator->append($array1);
    $iterator->append($array2);
    $iterator->append($array3);
    
    foreach($iterator as $key => $item) {
        echo $key . ' = '.$item . PHP_EOL;
    }

// output
0 = php
1 = perl
2 = python
3 = c
0 = singleton
1 = factory
2 = strategy
0 = yellow
1 = blue
2 = red
3 = white

Note here how the key is reset with each iterator start as if it be a separate loop.

 

Multiple Iterator

The multiple iterator is more like the append iterator except that instead of iterating over the iterators sequentially, the multiple iterator iterates over them in parallel. This behavior is useful when you need to traverse a group of collections in parallel order.

 MultipleIterator implements Iterator {
    /* Some constants */
    const integer MIT_NEED_ANY = 0 ;
    const integer MIT_NEED_ALL = 1 ;
    const integer MIT_KEYS_NUMERIC = 0 ;
    const integer MIT_KEYS_ASSOC = 2 ;
    
    /* Some methods */
    public __construct ([ int $flags = MultipleIterator::MIT_NEED_ALL|MultipleIterator::MIT_KEYS_NUMERIC ] )
    public void attachIterator ( Iterator $iterator [, string $infos ] )
    public bool containsIterator ( Iterator $iterator )
    public int countIterators ( void )
.....

iterators the multiple iterator are attached instead of appended:

$multiple = new MultipleIterator();
$multiple->attachIterator($iterator1);
$multiple->attachIterator($iterator2);

 

As it iterates over multiple iterators at once, things aren’t that straight forward. For example, the index or key of an element: Which one is it? By default, MultipleIterator will return an Array for both the current keys and values. As PHP does only allows an integer value or string as a “key”, the following example spits a warning:

Warning: Illegal type returned from MultipleIterator::key() 

foreach ($multiple as $key => $value) {
    echo " [$key] => $value <br/>";
}

Because by default MultipleIterator returns an invalid key (array), the warning appears and it is converted to 0. To obtain keys as well, the key() method needs to be called:

foreach ($multiple as $value) {
    $key = $multiple->key();
    printf(" * [%s] => %s\n", implode(', ', $key), implode(', ', $value));
}


// output
* [0, 0] => A, D
* [1, 1] => B, E
* [2, 2] => C, F

As you see above both $key and $value are an Array using implode here visualized what happens behind the scene.

 

The multiple iterator has two key constants:

  • MultipleIterator::MIT_KEYS_NUMERIC is the default one, it will return a numeric index (position), as usual 0-based -OR-
  • MultipleIterator::MIT_KEYS_ASSOC which will take the key value from the subiterator.

 

To make use of MultipleIterator::MIT_KEYS_ASSOC, the iterators needs to be attached with an integer or string value for the info parameter of attachIterator() (the second parameter). If set to MIT_KEYS_ASSOC that associated information will be taken. Example:

$multiple = new MultipleIterator(MultipleIterator::MIT_KEYS_ASSOC);
$multiple->attachIterator($iterator1, 'foo');
$multiple->attachIterator($iterator2, 'bar');
 
foreach ($multiple as $value) {
    $key = $multiple->key();
    printf(" * key(%s)  =>  value(%s)\n", formatIterator($key), formatIterator($value));
}

function formatArray(array $array) {
    $glue = ', ';
    foreach($array as $key => &$element) {
        $element = sprintf('[%s] => %s', $key, $element);
    }
    unset($element);
    return implode($glue, $array);
}

As for the first iterator 'foo' and for the second 'bar' is used for the info parameter and the assoc-flag is set, the output now contains those as keys:

* key([foo] => 0, [bar] => 0)  =>  value([foo] => A, [bar] => D)
* key([foo] => 1, [bar] => 1)  =>  value([foo] => B, [bar] => E)
* key([foo] => 2, [bar] => 2)  =>  value([foo] => C, [bar] => F)

 

If you don’t set the info parameter for one or more of the attached iterators and you make use of MIT_KEYS_ASSOC you’ll get an exception as the data is missing (NULL):

Fatal error: Uncaught exception ‘InvalidArgumentException’ with message ‘Sub-Iterator is associated with NULL’

Next to this key related behaviour, a question is what happens if one of the iterators is larger than the other? like A, B instead of A, B, C for the first iterator? By default the MultipleIterator will only return if all iterators are valid. The following output shows this, the last [2] => F element of the second iterator is just gone:

* key([0] => 0, [1] => 0)  =>  value([0] => A, [1] => D)
* key([0] => 1, [1] => 1)  =>  value([0] => B, [1] => E)

 

This behaviour can be changed with another pair of flags, one of the two MultipleIterator::MIT_NEED... ones:

  • MultipleIterator::MIT_NEED_ALL is the default one, it will invalidate the iteration if not all iterators are valid any longer -OR-
  • MultipleIterator::MIT_NEED_ANY will as long as at least one iterator is (still) valid.
$iterator1 = new ArrayIterator(range('A', 'B'));
$iterator2 = new ArrayIterator(range('D', 'F'));
 
$multiple = new MultipleIterator(MultipleIterator::MIT_NEED_ANY);
$multiple->attachIterator($iterator1);
$multiple->attachIterator($iterator2);
 
foreach ($multiple as $value) {
    $key = $multiple->key();
    printf(" * key(%s)  =>  value(%s)\n", formatArray($key), formatArray($value));
}

output

* key([0] => 0, [1] => 0)  =>  value([0] => A, [1] => D)
* key([0] => 1, [1] => 1)  =>  value([0] => B, [1] => E)
* key([0] => , [1] => 2)  =>  value([0] => , [1] => F)

The “missing” values in the output are actually NULL. As array keys can not be NULL, it’s safe to check with those if a sub-iterator did provide a value or not.

There is another pair of flags, that one controls what will be taken as keys.

All flags are listed in the PHP Manual as well. They can be changed while iterating with the setFlags() and getFlags() returns the current flags.

 

<?php

$it1 = new ArrayIterator(array(1,2,3));
$it2 = new ArrayIterator(array(4,5,6));

$multipleIterator = new MultipleIterator(MultipleIterator::MIT_NEED_ALL|MultipleIterator::MIT_KEYS_ASSOC);

$multipleIterator->attachIterator($it1, 1);
$multipleIterator->attachIterator($it2, 'second');

foreach ($multipleIterator as $key => $value) {
    echo "Key\n"; var_dump($key);
    echo "Value\n"; var_dump($value);
    echo "---next---\n";
}
?>

Result with PHP 5.5.0 and up:

Key
array(2) {
  [1]=>
  int(0)
  ["second"]=>
  int(0)
}
Value
array(2) {
  [1]=>
  int(1)
  ["second"]=>
  int(4)
}
---next---
Key
array(2) {
  [1]=>
  int(1)
  ["second"]=>
  int(1)
}
Value
array(2) {
  [1]=>
  int(2)
  ["second"]=>
  int(5)
}
---next---
Key
array(2) {
  [1]=>
  int(2)
  ["second"]=>
  int(2)
}
Value
array(2) {
  [1]=>
  int(3)
  ["second"]=>
  int(6)
}
---next---

 

 

Conclusion

In this tutorial of this series (PHP Iterators) we talked about three other SPL Iterators, Starting from the filter iterator to filter collections and the append and multiple iterators to iterator over several iterators at once.

4 2 votes
Article Rating

What's your reaction?

Excited
0
Happy
2
Not Sure
0
Confused
1

You may also like

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments