Search code examples
phparraysstringsubstringfiltering

Filter array to keep elements where the key exists in the value string


I need to filter my flat, associative array of string-type values by checking if its key is found (case-sensitive) in its value as a substring. In other words, if an element's key is in the element's value, I want to retain the element in the output array.

I can craft a classic foreach() loop with calls of strpos(), but this feels very pedestrian. Is there a more modern/elegant way do this?

My code:

$array = [
    'needle' => 'needle in haystack text',
    'Need' => 'another needle',
    'fine' => 'refined',
    'found' => 'Foundation',
    '' => 'non-empty haystack',
    'missing' => 'not here',
    'This is 0 in a haystack!',
];

$result = [];
foreach ($array as $needle => $haystack) {
    if (strpos($haystack, $needle) !== false) {
        $result[$needle] = $haystack;
    }
}
var_export($result);

Output:

array (
  'needle' => 'needle in haystack text',
  'fine' => 'refined',
  '' => 'non-empty haystack',
  0 => 'This is 0 in a haystack!',
)

Solution

  • From PHP5.6, array_filter() gained the ARRAY_FILTER_USE_BOTH flag. This makes array_filter() an equivalent functional-style technique.

    Code: (Demo)

    var_export(
        array_filter(
            $array,
            function($haystack, $needle) {
                return strpos($haystack, $needle) !== false;
            },
            ARRAY_FILTER_USE_BOTH
        )
    );
    

    One advantage of this is that $result doesn't need to be declared. On the other hand, despite the fact that it can technically be written as a one-liner, it would make a very long line of code -- something that puts the code near or over the soft character limit per line according to PSR-12 coding standards. So there is very little to compel you to change your code so far.

    The good news is that PHP8 introduced str_contains() specifically to offer a clean, native, case-sensitive function to replace strpos() and its obligatory strict boolean false check.

    Code: (Demo)

    var_export(
        array_filter($array, 'str_contains', ARRAY_FILTER_USE_BOTH)
    );
    

    This returns the same desired output, is a functional-style, one-liner, and concisely spans just 59 characters. I would recommend this approach.

    To understand what is happening, the verbose syntax with array_filter() where str_contains() is not called by its string name looks like this:

    var_export(
        array_filter(
            $array,
            fn($haystack, $needle) => str_contains($haystack, $needle),
            ARRAY_FILTER_USE_BOTH
        )
    );