Filter By Date Of Birth Easily with a FilterIterator

After a previous post on FilterIterators, a great discussion ensued on the point of FilterIterators. The last comment from Elvis really got me thinking. He said:

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 printing people born in the 70s, in the 80s and 90s by constructing the same iterator with different parameters (could be the timestamp range)

On reading that, I thought that it was an excellent idea for writing a more instructive and real-world case example of using FilterIterators. So, for your reading pleasure, here it is.

The Setup

We'll start with the data source array from the previous post.

$dataList = array(
    array(
        'name' => 'John Citizen',
        'email' => 'johnc@citizen.org',
        'dob' => '1960-01-21',
        'location' => 'Brisbane, Qld, Australia',
        'active' => true
    ),
    array(
        'name' => 'Jane Citizen',
        'email' => 'janec@citizen.org',
        'dob' => '1970-10-21',
        'location' => 'Townsville, Qld, Australia',
        'active' => false
    ),
    array(
        'name' => 'Peter Walker',
        'email' => 'peterw@citizen.org',
        'dob' => '1975-04-21',
        'location' => 'Sydney, Nsw, Australia',
        'active' => false
    ),
    array(
        'name' => 'Wendy Hardworker',
        'email' => 'wendyh@citizen.org',
        'dob' => '1990-11-21',
        'location' => 'Melbourne, Vic, Australia',
        'active' => true
    ),
);

What I've done here is change the dob element in each array from a UNIX timestamp to a standard year string. However, this example should work equally as well with a timestamp. I've kept the rest of the data array the same otherwise.

$dataIterator = new MaltBlue_Iterator_Filter_DateOfBirth(
    new ArrayIterator($dataList)
);
$dataIterator->setYearOfBirth("1976");

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

I've then changed the iterator used to MaltBlue_Iterator_Filter_DateOfBirth which I'll cover shortly. I then call a function on the iterator: setYearOfBirth. This function sets the year that we will be searching from. It internally creates a date from the first of January of that year.

It's rather simple and does no checking that it's a valid year. If you're interested, you could update the code to do this. [hint]. Then the rest of the code is as before, iterating over the results available.

The Date Of Birth Filter Iterator

<?php

class MaltBlue_Iterator_Filter_DateOfBirth extends FilterIterator
{
    protected $_yearOfBirth;
    protected $_comparisonDate;

    public function setYearOfBirth($yearOfBirth = null)
    {
        if (!empty($yearOfBirth)) {
            $this->_yearOfBirth = $yearOfBirth;
            try {
                $this->_comparisonDate = new DateTime($yearOfBirth . "-01-01");
            } catch(Exception $e) {
                // log error
            }
        }
    }

    public function accept()
    {
        $value = $this->current();
        if (array_key_exists('dob', $value) && !empty($value['dob'])) {
            try {
                $dob = new DateTime($value['dob']);
                if ($this->_comparisonDate
                    && $this->_comparisonDate instanceof DateTime)
                {
                    $dateInterval = $this->_comparisonDate->diff($dob);
                    return ($dateInterval->format('%R%a') >= 0) ? true : false;
                }
            } catch (Exception $e) {
                // log error
            }
        }
        return false;
    }
}

Now here's where it's really different. Firstly, the revised class has two protected variables: $yearOfBirth and $comparisonDate. These store the year that the record has to be older than and a DateTime object with which to compare the user against.

We then have a setYearOfBirth function, which allows us to specify the year of birth. If it's a valid value, then we create a DateTime object, which is the first of January of that year. I've wrapped it in a try/catch block for a bit of error checking.

Then we have the accept function, required in all implementations of the FilterIterator. Similar to before, if the dob field exists in the current record, then we attempt to create another DateTime object from the value of the field.

We then use the diff method of the DateTime class to return a DateInterval object. We can then call the format method on that object. The reason that this is done and in this way, is to see if the date of birth of the user, is equal with or greater than the date of the $_comparisonDate object, by at least one day.

If so, then the person has a valid date of birth and we return true so they will be shown.

Fork the Code on Github

If you want to play with the code, then fork or clone a copy of it - it's available on our github account now.

Parting Thoughts

Now, is this the most efficient code? I don't know to be honest as I've not done any performance tests on it. So, don't assume that this will be the best performing code.

However, as it's not aimed at that, I'm not too concerned. This goes also for the tests on the year value entered by the user and the date of birth value stored in the user record. The aim here is to provide a more real world use of the FilterIterator, which I believe this post has provided.

So, do you see that the FilterIterator really helps make code more flexible and elegant, by allowing us to apply custom logic over the existing lexicographical constructs of PHP? What possibilities do you see for using it in your code? Let me know in the comments.



About Matthew

Matthew Setter Matthew Setter is a PHP & Zend Framework specialist. If you're in need of a custom software application, need to migrate an existing legacy application, or want to know your current application's GPA - get in touch.