I decided to rewrite my answer to accommodate both filtering and sorting. I took a heavily object oriented approach to solving this problem, which I will detail below.
You can see all of this code in action at this ideone.com live demonstration.
The first thing I did was define two interfaces.
interface Filter {
public function filter($item);
}
interface Comparator {
public function compare($a, $b);
}
As their names suggest, Filter
is used for filtering, and Comparator
is used for comparing.
Next, I defined three concrete classes that implements these interfaces, and accomplish what I wanted.
First is KeyComparator
. This class simply compares the key of one element to the key of another element.
class KeyComparator implements Comparator {
protected $direction;
protected $transform;
protected $key;
public function __construct($key, $direction = SortDirection::Ascending, $transform = null) {
$this->key = $key;
$this->direction = $direction;
$this->transform = $transform;
}
public function compare($a, $b) {
$a = $a[$this->key];
$b = $b[$this->key];
if ($this->transform) {
$a = $this->transform($a);
$b = $this->transform($b);
}
return $a === $b ? 0 : (($a > $b ? 1 : -1) * $this->direction);
}
}
You can specify a sort direction, as well as a transformation to be done to each element before they are compared. I defined a helped class to encapsulate my SortDirection
values.
class SortDirection {
const Ascending = 1;
const Descending = -1;
}
Next, I defined MultipleKeyComparator
which takes multiple KeyComparator
instances, and uses them to compare two arrays against each other. The order in which they are added to the MultipleKeyComparator
is the order of precedence.
class MultipleKeyComparator implements Comparator {
protected $keys;
public function __construct($keys) {
$this->keys = $keys;
}
public function compare($a, $b) {
$result = 0;
foreach ($this->keys as $comparator) {
if ($comparator instanceof KeyComparator) {
$result = $comparator->compare($a, $b);
if ($result !== 0) return $result;
}
}
return $result;
}
}
Finally, I created MultipleKeyValueFilter
which is meant to filter an array based on an array of key/value pairs:
class MultipleKeyValueFilter implements Filter {
protected $kvPairs;
public function __construct($kvPairs) {
$this->kvPairs = $kvPairs;
}
public function filter($item) {
$result = true;
foreach ($this->kvPairs as $key => $value) {
if ($item[$key] !== $value)
$result &= false;
}
return $result;
}
}
Now, given the input array (Notice I rearranged them a bit to make the sorting obvious):
$array = array (
'1' => array ('type' => 'blah2', 'category' => 'cat2', 'exp_range' => 'this_week' ),
'2' => array ('type' => 'blah1', 'category' => 'cat1', 'exp_range' => 'this_week' ),
'3' => array ('type' => 'blah1', 'category' => 'cat2', 'exp_range' => 'next_week' ),
'4' => array ('type' => 'blah1', 'category' => 'cat1', 'exp_range' => 'next_week' )
);
Sorting can be achieved by doing the following:
$comparator = new MultipleKeyComparator(array(
new KeyComparator('type'),
new KeyComparator('exp_range')
));
usort($array, array($comparator, 'compare'));
echo "Sorted by multiple fields
";
print_r($array);
Filtering can be achieved by doing the following:
$filter = new MultipleKeyValueFilter(array(
'type' => 'blah1'
));
echo "Filtered by multiple fields
";
print_r(array_filter($array, array($filter, 'filter')));
At this point I've given you a great deal of code. I'd suggest that your next step is to combine these two pieces into a single class. This single class would then apply both filtering and sorting together.