Painless Data Traversal with PHP FilterIterators

Managing Sessions in ZF2

There’s load of ways to traverse data, especially in PHP where there are a variety of loops available; including while, do while, for and foreach. These are fine for normal structures, such as scalar and associative arrays. But what if you want to get a bit more fancy?

Say you started out with a structure similar to the following:

<?php
$dataList = array(
    array(
        'name' => 'John Citizen',
        'email' => 'johnc@citizen.org',
        'dob' => 1350910989,
        'location' => 'Brisbane, Qld, Australia',
        'active' => true
    ),
    array(
        'name' => 'Jane Citizen',
        'email' => 'janec@citizen.org',
        'dob' => 1350910989,
        'location' => 'Townsville, Qld, Australia',
        'active' => false
    ),
    array(
        'name' => 'Peter Walker',
        'email' => 'peterw@citizen.org',
        'dob' => 1350910989,
        'location' => 'Sydney, Nsw, Australia',
        'active' => false
    ),
    array(
        'name' => 'Wendy Hardworker',
        'email' => 'wendyh@citizen.org',
        'dob' => 1350910989,
        'location' => 'Melbourne, Vic, Australia',
        'active' => true
    ),
    array(
        // ...
    ),
);

You can see that you could quickly and simple filter the information using any of the previously mentioned loops. The following is a rather trivial example of how you could do just this.

<?php
foreach ($dataList as $userDetail) {
    printf(
        "Name: %s | Email: %s | Date Of Birth: %s | Location: %s",
        $userDetail["name"], 
        $userDetail["email"], 
        $userDetail["dob"], 
        $userDetail["location"]
    );
}

This would display output similar to the following:

Name: John Citizen | Email: johnc@citizen.org | DOB: 1350910989 | Location: Brisbane, Qld, Australia
Name: Jane Citizen | Email: janec@citizen.org | DOB: 1350910989 | Location: Townsville, Qld, Australia
Name: Peter Walker | Email: peterw@citizen.org | DOB: 1350910989 | Location: Sydney, Nsw, Australia
Name: Wendy Hardworker | Email: wendyh@citizen.org | DOB: 1350910989 | Location: Melbourne, Vic, Australia

But say, over time, the the data structure grew in complexity and you wanted to filter out some records, but not others. I’m sure that one of your first reactions would, naturally, be to change the SQL query – assuming that we’re talking to a datasource that is an RDBMS. But:

  • What if it wasn’t?
  • What if your only access was an API?
  • What if you were not allowed to change the underlying code?
  • What if you didn’t know all the ramifications of what a change may be?

It’s not quite so easy is it? Let’s say that one or more of these constraints is in effect. Is there an answer? Yes, there most certainly is – so long as you’re using PHP >= 5.1.0 that is.

Enter the FilterIterator

That’s right, in PHP 5.1.0 as part of the SPL (Standard PHP Library), Filter Iterators were introduced. If you’ve not heard of them or used them, the PHP manual describes them as:

This abstract iterator filters out unwanted values. This class should be extended to implement custom iterator filters. The FilterIterator::accept() must be implemented in the subclass.

Iterators and the FilterIterator are a wonderful addition to the PHP language as they allow us, as developers, to apply the existing looping constructs to our custom data structures.

Recently in a Malt Blue project, we were tasked with building a project that provides data, that is largely similar in nature, which needed to be viewed across a range of different parts of the application. Given that the data was so similar in nature, it made no sense to write a series of different, utility, functions for it – which would then need to be maintained over time.

So we chose to instead implement a series of FilterIterators so that we could make one data request call and display the same information in a multitude of, slightly, different ways.

For the purposes of this example, let’s assume that the data structure is similar to the above. Have a look at the code below and we’ll work through it, showing how to implement a FilterIterator:

<?php
 
class MyIterator_Filter_InActive extends FilterIterator
{
    public function accept()
    {
        $value = $this->current();
        if (array_key_exists('active', $value) && 
            !empty($value['active'])) {
            return $value['active'];    
        }
        return false;       
    }   
}

In the code above, we define the class MyIterator_Filter_InActive which extends FilterIterator. The FilterIterator interface requires us to only implement one method: accept. If accept returns true, then the current record is displayed. If it returns false, then the record is skipped.

So, we retrieve the current record, with $value = $this->current();. We then check whether it has an active value and if that value is set. If it is, then we return it. If one of these conditions is not met, then we automatically return false.

So let’s instantiate the FilterIterator with our existing dataset and see what the result is.

<?php
$dataIterator = new MyIterator_Filter_InActive(
    new ArrayIterator($dataList)
);

As our data is already in an array, we start by using it to instantiate an ArrayIterator, which makes it painless to iterate over the whole dataset – not what we want. This instantiated ArrayIterator is then passed to our custom FilterIterator.

With this instantiated FilterIterator, we can then loop over the data as before, but now only with records that have active set as true – as the below code does:

<?php
foreach ($dataIterator as $userDetail) {
    printf(
        "Name: %s | Email: %s | Date Of Birth: %s | Location: %s",
        $userDetail["name"], 
        $userDetail["email"], 
        $userDetail["dob"], 
        $userDetail["location"]
    );
}

The updated output will be as below:

Name: John Citizen | Email: johnc@citizen.org | DOB: 1350910989 | Location: Brisbane, Qld, Australia
Name: Wendy Hardworker | Email: wendyh@citizen.org | DOB: 1350910989 | Location: Melbourne, Vic, Australia

Pretty simple, hey? Now yes, this was a simple example. But you can see how, through creating a custom FilterIterator, you can quickly and simply use the existing PHP looping constructs to iterate through your custom data sets returning only the information that you specifically need.

Questions

Do you use FilterIterators already? What’s your experience with them? Do they help you build more maintainable code? Share your experience in the comments. If you liked this post, then check out the follow up to it: filter by date of birth easily with a filteriterator.

Intermediate PHP
  • elvispm83

    I normally use array_filter + closure to apply filters with one line of code when the logic is simple. Never had the need to have a complex filter so far, but I’ll consider it in the future.
     
    p.s: why array_key_exists(‘active’, $value) && !empty($value['active'])  ? isn’t the logic the same with the second condition only ? empty does not trigger any error for a missing key

    • jakefolio

      @elvispm83 The key difference is the FilterIterator just filters output for the iteration, but doesn’t actually remove unwanted elements. In PHP 5.4, you can use the CallbackFilterIterator, so you could use an anonymous function as the accept method.

      • http://www.matthewsetter.com/ maltblue

        @jakefolio  @elvispm83 thanks for the feedback @jakefolio  and thanks for mentioning the CallbackFilterIterator. I must give that a look and get some experience with it.

      • elvispm83

        @jakefolio I knew the difference, but for simple cases, simplicity often wins$activeUsers = array_filter($dataList, function($e) {   return isset($e['active']) && $e['active'];});foreach ($activeUsers as as $userDetail) {… }- Pro: simple- Cons: not reuse the logic, faster (one iteration only, but it needs measurament)
        An alternative is using collections rather then arrays (see doctrine2), in order to implement custom methods on the collection, my favourite option for readability and concern separationforeach ($dataList->getActive() as $user) {}

    • http://www.matthewsetter.com/ maltblue

      @elvispm83 Hey Elvis, admittedly the example that I’ve used here is likely a bit too simplistic. I should have put in something a bit more complex and worthy. *note to self*. My intent with the array_key_exists and !empty was first to check it was there and then if it had something. Likely too trivial also. What’s your suggestion for a more worthy example for a FilterIterator?

      • elvispm83

        @maltblue without changing the data in the example, what about a FilterIterator that accepts a parameter (like the DateOfBirth interval) ? In that case you can show code reusage by priting people born in the 70s, in the 80s and 90s by constructing the same iterator with different parameters (could be the timestamp range)

        • http://www.matthewsetter.com/ maltblue

          @elvispm83 Hmmm, that seems like a good idea. I think I’ll do a follow up post taking your suggestion in to account. Thanks for the inspiration.

  • Pingback: Using accept() in Filter Iterator in PHP to get values from an arrayCopyQuery CopyQuery | Question & Answer Tool for your Technical Queries,CopyQuery, ejjuit, query, copyquery, copyquery.com, android doubt, ios question, sql query, sqlite query, nodej