4

I have this PHP function that works well at searching a multidimensional associative array using a key value pair. I would now like to extend it to search for an array where the key value pair has an SQL-like construct similar to this: name = '%john%'.

function search($array, $key, $value)
{
    $results = array();
    like_search_r($array, $key, $value, $results);
    return $results[0];
}

function like_search_r($array, $key, $value, &$results)
{
    if (!is_array($array)) {
        return;
    }

    if (isset($array[$key]) && $array[$key] == $value) {
        $results[] = $array;
    }

    foreach ($array as $subarray) {
        like_search_r($subarray, $key, $value, $results);
    }
}

I found a good example here filter values from an array similar to SQL LIKE '%search%' using PHP but it only searches a one-dimensional array. The key in this example is preg_grep but I have not figured how to use it in a multidimensional associative array. Any help is appreciated.

EDITED: I would love to be able to pass an array of key values pairs to do one sql like filter. The original requirements remain the same. Filter must support something similar to sql '%like%', no input is required, return the root array when a combination of matches are met. If a key/value pair does not match ignore and move on to the next key/value pair. My input array looks like this:

     array('FIRST_NAME'=>'ma','MIDDLE_NAME'=>'bill',
     'LAST_NAME'=>'jo','ALIASES'=>'phil',
     'DOB'=>'2017-07-05','COUNTRY_OF_BIRTH'=>'Jamaica',
     'Countries1'=>array(array('COUNTRY_CODE'=>'JM'),array('COUNTRY_CODE'=>'AL')),
     'Countries2'=>array(array('COUNTRY_CODE'=>'JM'),array('COUNTRY_CODE'=>'AL')));

A sample array to be filtered can be found here: https://www.tehplayground.com/dIMKbb6Tcw5YU38R

jessiPP
  • 436
  • 1
  • 6
  • 21

1 Answers1

3

I think the way to go is with preg_match():

function match($search, $subject)
{
    $search = str_replace('/', '\\/', $search);

    return preg_match("/$search/i", (string)$subject);
}

function like_search_r($array, $key, $value, array &$results = [])
{
    if (!is_array($array)) {
        return;
    }

    $key   = (string)$key;
    $value = (string)$value;

    foreach ($array as $arrayKey => $arrayValue) {
        if (match($key, $arrayKey) && match($value, $arrayValue)) {
            // add array if we have a match
            $results[] = $array;
        }

        if (is_array($arrayValue)) {
            // only do recursion on arrays
            like_search_r($arrayValue, $key, $value, $results);
        }
    }
}

$array1 = [
    'foo'    => 'bar',
    'subarr' => [
        'test'                 => 'val',
        'dangerous/characters' => 1,
    ],
];

$results1 = [];
like_search_r($array1, 'fo', 'bar', $results1);
print_r($results1);

/*
Array
(
    [0] => Array
        (
            [foo] => bar
            [subarr] => Array
                (
                    [test] => val
                    [dangerous/characters] => 1
                )

        )

)
*/

$results2 = [];
like_search_r($array1, 'est', 'val', $results2);
print_r($results2);

/*
Array
(
    [0] => Array
        (
            [test] => val
            [dangerous/characters] => 1
        )

)
*/

$results3 = [];
like_search_r($array1, 's/c', 1, $results3);
print_r($results3);

/*
Array
(
    [0] => Array
        (
            [test] => val
            [dangerous/characters] => 1
        )

)
*/

Adjusted after your comment:

function match($search, $subject) { /* no change */ }

function like_search_r($array, $key, $value, array &$results = [], $level = 0)
{
    if (!is_array($array)) {
        return false;
    }

    $key   = (string)$key;
    $value = (string)$value;

    $found = false;

    foreach ($array as $arrayKey => $arrayValue) {
        if (match($key, $arrayKey) && match($value, $arrayValue)) {
            return true;
        }

        if (is_array($arrayValue)) {
            // only do recursion on arrays
            // results are only added on top level
            if (like_search_r($arrayValue, $key, $value, $results, $level+1)) {
                if ($level == 1) {
                    $results[] = $array;
                }
                $found = true;
            }
        }
    }

    return $found;
}

$array2   = [['id' => 0, 'values' => ['name' => 'bill']], ['id' => 1, 'values' => ['name' => 'john']]];
$results4 = [];
like_search_r($array2, 'name', 'john', $results4);
print_r($results4);

/*
Array
(
    [0] => Array
        (
            [id] => 1
            [values] => Array
                (
                    [name] => john
                )

        )

)
*/
colburton
  • 4,685
  • 2
  • 26
  • 39
  • It does work but the only issue is that it is case sensitive. So so if the value stored is "Mark" and I search for "ma" I do not get a result. If I, however, search for "Ma" I do get a result. Also, the third test did not work for me but it does not present an important use case for me. Make it case insensitive and I will give you the 50 points. – jessiPP Jul 08 '17 at 14:36
  • I've changed it, to be case-insensitive. It's just an `i` after the match-pattern. – colburton Jul 08 '17 at 14:37
  • I tried to award you but it says I have to wait 13 hours. – jessiPP Jul 08 '17 at 15:59
  • Is there a way to have it return the array from the top node array instead of the sub array? for example array( [0]=>[[id]=>0,values['name']=>'bill'], [1]=>[[id]=>1,values['name'=>'john']]); If search for name = john, I would love to get not just the values array but from the root node [1]. Is this possible? – jessiPP Jul 08 '17 at 17:00
  • If it could return from the root node as stated above it would be perfect for my use case. – jessiPP Jul 08 '17 at 17:12
  • Ok. I've used your provided test array. Hope I got your use case right. – colburton Jul 08 '17 at 17:42
  • How difficult would it be to pass an array of name-value pairs and do a filter based on a combination of those values? – jessiPP Jul 09 '17 at 08:01
  • Hi colburton. I know I assigned the points already but if it is still possible I would appreciate it if you could take a look. I updated the question. – jessiPP Jul 10 '17 at 05:53
  • Sorry I don't understand the task. An array can't have the same key twice like `'COUNTRY_CODE'=>'JM','COUNTRY_CODE'=>'AL'`. Also what `$key` and `$value` would you like to look for? – colburton Jul 10 '17 at 17:00
  • My bad. I fixed the array. Please take a look again. In essence, you can filter the country by multiple country codes which are in the form of an array. – jessiPP Jul 10 '17 at 18:36
  • The array you are seeing is all the possible inputs. An example of the array to be filtered can be found here: https://www.tehplayground.com/dIMKbb6Tcw5YU38R. Once one or more key/value pair matched is found, return the array from the root node, not the sub array. I hope it is clear now. – jessiPP Jul 10 '17 at 19:00
  • I take it that the updated request is a difficult one to do? – jessiPP Jul 11 '17 at 23:53
  • I'm still confused why you can't simply do `foreach ($inputArray ...) like_search_r(...)` – colburton Jul 12 '17 at 07:41