An implementation of a search function that uses a callback, to allow searching for objects of arbitrary complexity:
<?php
function array_usearch(array $array, Closure $test) {
$found = false;
$iterator = new ArrayIterator($array);
while ($found === false && $iterator->valid()) {
if ($test($iterator->current())) {
$found = $iterator->key();
}
$iterator->next();
}
return $found;
}
?>
The above takes a closure, rather than a callable, but that could easily be altered. Like array_search, this will return false if no match is found. I made the closure the last parameter in case php one day implements trailing-closure syntax.
For instance, if you have an array of objects with an id property, you could search for the object with a specific id like this:
<?php
class Thing {
public $id;
public $name;
public function __construct($id, $name, $category) {
$this->id = $id;
$this->name = $name;
}
}
$listOfThings = [
new Thing(1, 'one'),
new Thing(2, 'two'),
new Thing(3, 'three'),
new Thing(4, 'four'),
];
$id4Index = array_usearch($listOfThings, function($thing) {
return $thing->id === 4;
});
?>
Since the Closure can capture information, it becomes simple to write a function that takes search criteria as parameters, creates a closure with those criteria, and calls the above. A simple example that does the same search as above would be
<?php
$idClosure = function($id) {
return function($item) use ($id) {
return $item->id = $id;
}
}
$id4Index = array_usearch($idClosure(4));
?>
It's more complex to set up, but now any search for an id can be written in a single line, making things clean and concise.
For a more complex example, this function takes an array of key/value pairs and returns the key for the first item in the array that has all those properties with the same values.
<?php
function firstIndexMatching(array $array, array $criteria, bool $useStrict = true) {
if (count($criteria) < 1) {
return false;
}
// create a closure that has captured the search criteria
$testWithCriteria = function($criteria, $useStrict) {
return function($item) use ($criteria, $useStrict) {
foreach($criteria as $key => $value) {
if (!isset($item->$key)) {
return false;
} else if ($useStrict && $item->$key !== $value) {
return false;
} else if (!$useStrict && $item->$key != $value) {
return false;
}
}
return true;
};
};
return array_usearch($array, $testWithCriteria($criteria, $useStrict));
}
?>
If you have a list of people, for instance, you can now simply find the person record with a specific first and last name:
<?php
$joeSchlabotnikIndex = firstIndexMatching($people, [
'firstName' => 'Joe',
'lastName' => 'Schlabotnik'
]);
?>
Obviously firstIndexMatching could be modified to use case-insensitive comparisons or anything else that fits your use-case. They idea is capturing the input data.
The final step is a function that returns the item, rather than its key, or null if no match found:
<?php
function firstItemMatching(array $array, array $criteria, bool $useStrict = true) {
// fun fact: $array[false] is equivalent to $array[0]
$index = firstIndexMatching($array, $criteria, $useStrict);
return $index !== false ? $array[$index] ?? null : null;
}
?>