Problem
I need to write a function that searches the two-tier board according to certain parameters, which works, but I question whether there is a simpler, lighter way to perform this task.
<?php
function findValue(array $array, array $parameters, $multipleResoult = false){
$result = array();//used when $multipleResoult == true
$suspicious = false;
foreach($array as $childArray){
foreach($parameters as $k => $p){
if(array_key_exists($k,$childArray)){
if($childArray[$k] == $p){
$suspicious = $childArray;
} else {
$suspicious = false;
continue 2;
}
} else {
$suspicious = false;
continue 2;
}
}
if(is_array($suspicious)){
$result[] = $suspicious;
if($multipleResoult == true){
$suspicious = false;
} else {
break;
}
}
}
return $result;
}
$arr = Array
(
0 => Array
(
"id" => 1,
"id_shop" => 1,
"id_lang" => 1,
"id_product" => 1,
"id_field" => 3,
"field_value" => "zxczxc"
),
// find if single and multiple
1 => Array
(
"id" => 2,
"id_shop" => 1,
"id_lang" => 2,
"id_product" => 1,
"id_field" => 3,
"field_value" => "sdfsdfsdf"
),
2 => Array
(
"id" => 3,
"id_shop" => 1,
"id_lang" => 2,
"id_product" => 2,
"id_field" => 3,
"field_value" => "sdfsdfsdf"
),
// find if multiple
3 => Array
(
"id" => 3,
"id_shop" => 1,
"id_lang" => 2,
"id_product" => 1,
"id_field" => 3,
"field_value" => "sdfsdfsdf"
)
);
echo '-----------------SINGLE----------------------';echo "n";echo '<pre>';
print_r(findValue($arr, array(
'id_shop' => 1,
'id_field' => 3,
'id_lang'=> 2,
'id_product' => 1)
));
echo '-----------------MULTIPLE----------------------';echo "n";echo '<pre>';
print_r(findValue($arr, array(
'id_shop' => 1,
'id_field' => 3,
'id_lang'=> 2,
'id_product' => 1)
,true));
?>
Solution
Firstly, this could use a better description (in a comment) explaining what it does. I had to figure it out from the code. I think the description should probably be something like
// Searches $array for associative arrays which are supersets of $parameters.
// If $multipleResults it will return all matches in an array;
// otherwise just the first, in an array.
Secondly, names.
findValue
isn’t very descriptive. How aboutfindSupersets
?$array
would traditionally be called$haystack
in PHP, since you’re searching through it for matches to a predicate.$multipleResoult
is a misspelling. Correcting the spelling and adjective-noun agreement we get$multipleResults
, which is ok, although I personally would prefer to communicate an action:$returnAll
.$suspicious
has the wrong connotations for this native English speaker. If something is suspicious, it means that I think there’s something wrong with it. My preferred variable name for something which may or may not pass an acceptance test is$candidate
.
Thirdly, since PHP has a dynamic type system I think it would be more idiomatic to return just the element if !$multipleResults
, rather than wrapping it in an array.
Now, you specifically ask whether there’s a simpler way to do it. I think that using booleans it can be simplified quite a bit by inverting the assumption. Rather than prove it acceptable, prove it not acceptable. Taking into account various of my suggestions above and refactoring I get:
foreach($haystack as $childArray){
$isCandidate = true;
foreach($parameters as $k => $p){
if(!array_key_exists($k,$childArray) || $childArray[$k] != $p){
$isCandidate = false;
break;
}
}
if($isCandidate){
if(!$multipleResults){
return $childArray;
}
$result[] = $childArray;
}
}
return $result;
It’s a couple of years now since I used PHP, and I’m not sure offhand whether there are built-in functions to test whether $childArray
contains $parameters
as a subset, or to filter an array by a predicate. It may be that the whole function can be reduced to two calls to built-ins. If not, you could consider whether you want to factor it out as
function findMatch(array $haystack, $predicate, $returnAll = false) {
$results = []
foreach ($haystack as $element) {
if ($predicate($element)) {
if ($returnAll) return $element;
$results[] = $element;
}
}
return $results;
}
function isSuperset(array $candidate, array $subset) {
foreach ($subset as $k => $v) {
if (!isset($candidate[$k]) || $candidate[$k] != $v) return false;
}
return true;
}
linking the two with a lambda.
You don’t need to call a second foreach loop and temporarily store partial matches (suspicious/candidates) as you go.
Use array_intersect_assoc()
and sizeof()
(or count()
) to instantly identify qualifying subarrays.
Code: (Demo)
function findValue(array $array, array $terms, $get_all=false){
$term_count=sizeof($terms); // cache the element count of $terms
$results=[]; // establish empty array for print_r if no matches are found.
foreach($array as $subarray){
if(sizeof(array_intersect_assoc($subarray,$terms))==$term_count){ // qualifying subarray
if(!$get_all){
return $subarray; // end loop and return the single subarray
}else{
$results[]=$subarray;
}
}
}
return $results;
}