Applying multiple php FilterIterator instances in a cascading fashion I wrote an implementation of php FilterIterator objects that allows me to apply them in a "cascade" way, so I can finally obtain an array of objects which is the result of all those filters.

It works fine, but - being new to SPL objects - I'm wondering if there is a better way to achieve the same results with FilterIterators et similia.

My implementation

I have a Factory class holding an array of objects in a static property:

class Factory {

    /**
     * @var Reservation[]
     */
    public static $reservations;

    public function __construct(){
        self::$reservations = $this-&gt;get_all();
    }

    private function get_all(){
        // Queries the database...
        return $results; // array of Reservation objects
    }

    // other unrelated things...
}


As I need the ability to filter such array by some criteria on-demand, I defined a bunch of FilterIterator sub-classes. The following is byOrder, others (byService, byProvider etc.) are defined in a similar way:

class byOrder extends FilterIterator {

    protected $order;

    public function __construct(Iterator $iterator, $order){
        parent::__construct($iterator);
        $this-&gt;order = $order;
    }

    public function accept(){

        /** @var $current Reservation */
        $current = $this-&gt;current();

        return $current-&gt;order() === $this-&gt;order;
    }
}


In order to apply just one filter to the array (I need it to stay as array) wherever in my application I can do:

// somewhere else...
$factory = new Factory();
// later on...
$reservations = new ArrayObject(Factory::$reservations);
$iterator = $reservations-&gt;getIterator();
$filteredReservation = iterator_to_array(new byOrder($iterator, 'ORDER_ID'));


To be able to apply multiple filters in a "cascade" way, I created the following helper class:

class Select {

    /**
     * @var ArrayIterator
     */
    private $reservations;

    public function __construct(array $reservations)
    {
        $obj = new ArrayObject($reservations);
        $this-&gt;reservations = $obj-&gt;getIterator();
    }

    public function get(){
        return iterator_to_array($this-&gt;reservations);
    }

    public function byOrder($order){
        $this-&gt;reservations = new byOrder($this-&gt;reservations, $order);

        return $this;
    }

    public function byService($service){
        $this-&gt;reservations = new byService($this-&gt;reservations, $service);

        return $this;
    }

    // ... and so on
}


It is instantiated on-demand through a method inside the Factory class:

class Factory {

    // All the previously defined content, plus:
    public function select() {
        return new Select(self::$reservations);
    }
}


So I'm able to apply multiple cascading filters like this:

// somewhere else...
$factory = new Factory();
// later on...
$filtered = $factory-&gt;select()
    -&gt;byOrder('ORDER_ID')
    -&gt;byService('SERVICE_NAME')
    -&gt;get();
Edit

One suggestion is to make your helper class more dynamic. As it currently stands it is heavily tied to each filter class so you could not reuse it in another project. For example, imagine a new project that deals with selling cars. Your Select helper class only knows about orders, services, providers. You'd have to rewrite it to know about cars, prices, colours etc. 

To get around this, you can define a filter property which holds a list of filters. Then, when calling get(), loop over the filters and apply each one. The benefit here is that the helper class is now
unaware of any filter implementations and can be used in any project. 

&lt;?php

/*************************************************/
/******************** FILTERS ********************/
/*************************************************/
class Order extends FilterIterator
{
    private $_value = null;

    public function __construct(Iterator $iterator, $value)
    {
        $this-&gt;_value = $value;
        parent::__construct($iterator);
    }

    public function accept()
    {
        return $this-&gt;current()-&gt;order === $this-&gt;_value;
    }
}

class Future extends FilterIterator
{
    public function __construct(Iterator $iterator)
    {
        parent::__construct($iterator);
    }

    public function accept()
    {
        return $this-&gt;current()-&gt;date &gt; new DateTime();
    }
}

class Date extends FilterIterator
{
    private $_value = null;

    public function __construct(Iterator $iterator, $value)
    {
        $this-&gt;_value = $value;
        parent::__construct($iterator);
    }

    public function accept()
    {
        return $this-&gt;current()-&gt;date === $this-&gt;_value;
    }
}

/*************************************************/
/*************** COLLECTION HELPER ***************/
/*************************************************/
class Helper
{
    private $_items = [];
    private $_filters = [];

    public function __construct(array $items)
    {
        $this-&gt;_items = (new ArrayObject($items))-&gt;getIterator();
    }

    public function filter($filter, $value = null)
    {
        $this-&gt;_filters[] = [$filter =&gt; $value];

        return $this;
    }

    public function get()
    {
        foreach ($this-&gt;_filters as $filter) {
            foreach ($filter as $name =&gt; $value) {

                if ($value) {
                    $this-&gt;_items = new $name($this-&gt;_items, $value);
                } else {
                    $this-&gt;_items = new $name($this-&gt;_items);
                }
            }
        }

        return iterator_to_array($this-&gt;_items);
    }
}

/*************************************************/
/****************** TEST CASES *******************/
/*************************************************/
class Reservation
{
    public $order = null;
    public $date = null;
    public $status = null;

    public function __construct($order, $date, $status)
    {
        $this-&gt;order = $order;
        $this-&gt;date = $date;
        $this-&gt;status = $status;
    }
}

$reservations = new Helper([
    new Reservation(123, new DateTime('+1Day'), 'Active'),
    new Reservation(512, new DateTime('+2Month'), 'Active'),
    new Reservation(456, new DateTime('-6Hour'), 'Pending'),
    new Reservation(789, new DateTime('-17Day'), 'Cancelled'),
    new Reservation(264, new DateTime('+1Hour'), 'Pending'),
    new Reservation(151, new DateTime('+1Year'), 'Active'),
]);

$filtered = $reservations
    -&gt;filter('Future')
    -&gt;filter('Order', 151)
    -&gt;get();

var_dump($filtered);


Final note - I played around with the implementation for a while and found FilterIterators to be rather cumbersome because I had to write a single filter class for each permutation of a property. For example, in a reservation system you'd need the following filters: ByOrder, ByOrderNotEqualTo, ByDate, ByFuture, ByPast, By6MonthsFromNow, ByStatus, ByStatusNotEqualTo, ByCustomerId, etc.

One solution is to add a where method to your helper class which would accept a comparison operator. Again, I point you to Laravel's implementation and a document highlighting its usage. Food for thought. 

EndEdit

If you're looking for a simple method, take a look at Laravel's implementation of their Collection::filter which calls Arr::filter. It provides a clean, chainable way to pass a callback to array_filter. 

Below, is a basic demonstration on how to use it. I've implemented it using ArrayObject but you can use any iterable class. The filter calls will be slightly longer to type out than your implementation since you must define a callback function each time, but it makes the source much cleaner since you don't have to define a separate filter class for every property. 

http://sandbox.onlinephpfunctions.com/code/cea564648d52c702e112d9775d1afe76f41fc810

&lt;?php

class Collection extends ArrayObject
{
    public function filter(callable $callback = null)
    {
        if ($callback) {
            // This will return a new Collection object with the filtered results.
            // Late static binding -  You **could use either `static`
            // or `self` here since both will resolve to Collection.
            // Going with `static` in case you want to extend this class.
            // See - https://stackoverflow.com/q/5197300/296555
            return new static (array_filter($this-&gt;getArrayCopy(), $callback, ARRAY_FILTER_USE_BOTH));
        }

        return new static (array_filter($this-&gt;getArrayCopy()));
    }
}

class Reservation
{
    public $order = null;
    public $date = null;
    public $status = null;

    public function __construct($order, $date, $status)
    {
        $this-&gt;order = $order;
        $this-&gt;date = $date;
        $this-&gt;status = $status;
    }
}

$reservations = new Collection([
    new Reservation(123, new DateTime('+1Day'), 'Active'),
    new Reservation(512, new DateTime('+2Month'), 'Active'),
    new Reservation(456, new DateTime('-6Hour'), 'Pending'),
    new Reservation(789, new DateTime('-17Day'), 'Cancelled'),
    new Reservation(264, new DateTime('+1Hour'), 'Pending'),
    new Reservation(151, new DateTime('+1Year'), 'Active'),
]);

$filtered = $reservations 
    // Active reservations
    -&gt;filter(function ($value, $key) {
        return $value-&gt;status === 'Active';
    })
    // In the future
    -&gt;filter(function ($value, $key) {
        return $value-&gt;date &gt; new DateTime();
    });

var_dump($filtered);


Finally, here's a decent package that took this idea a step further and implements most array functions so you can chain them.