Filter array by its keys using an array of allowed keys

PhpArraysKeyFilteringWhitelist

Php Problem Overview


The callback function in array_filter() only passes in the array's values, not the keys.

If I have:

$my_array = array("foo" => 1, "hello" => "world");

$allowed = array("foo", "bar");

What's the best way to delete all keys in $my_array that are not in the $allowed array?

Desired output:

$my_array = array("foo" => 1);

Php Solutions


Solution 1 - Php

With array_intersect_key and array_flip:

var_dump(array_intersect_key($my_array, array_flip($allowed)));

array(1) {
  ["foo"]=>
  int(1)
}

Solution 2 - Php

PHP 5.6 introduced a third parameter to array_filter(), flag, that you can set to ARRAY_FILTER_USE_KEY to filter by key instead of value:

$my_array = ['foo' => 1, 'hello' => 'world'];
$allowed  = ['foo', 'bar'];
$filtered = array_filter(
    $my_array,
    function ($key) use ($allowed) {
        return in_array($key, $allowed);
    },
    ARRAY_FILTER_USE_KEY
);

Since PHP 7.4 introduced arrow functions we can make this more succinct:

$my_array = ['foo' => 1, 'hello' => 'world'];
$allowed  = ['foo', 'bar'];
$filtered = array_filter(
    $my_array,
    fn ($key) => in_array($key, $allowed),
    ARRAY_FILTER_USE_KEY
);

Clearly this isn't as elegant as array_intersect_key($my_array, array_flip($allowed)), but it does offer the additional flexibility of performing an arbitrary test against the key, e.g. $allowed could contain regex patterns instead of plain strings.

You can also use ARRAY_FILTER_USE_BOTH to have both the value and the key passed to your filter function. Here's a contrived example based upon the first, but note that I'd not recommend encoding filtering rules using $allowed this way:

$my_array = ['foo' => 1, 'bar' => 'baz', 'hello' => 'wld'];
$allowed  = ['foo' => true, 'bar' => true, 'hello' => 'world'];
$filtered = array_filter(
    $my_array,
    // N.b. it's ($val, $key) not ($key, $val):
    fn ($val, $key) => isset($allowed[$key]) && (
        $allowed[$key] === true || $allowed[$key] === $val
    ),
    ARRAY_FILTER_USE_BOTH
); // ['foo' => 1, 'bar' => 'baz']

Solution 3 - Php

Here is a more flexible solution using a closure:

$my_array = array("foo" => 1, "hello" => "world");
$allowed = array("foo", "bar");
$result = array_flip(array_filter(array_flip($my_array), function ($key) use ($allowed)
{
	return in_array($key, $allowed);
}));
var_dump($result);

Outputs:

array(1) {
  'foo' =>
  int(1)
}

So in the function, you can do other specific tests.

Solution 4 - Php

Here's a less flexible alternative using unset():

$array = array(
    1 => 'one',
    2 => 'two',
    3 => 'three'
);
$disallowed = array(1,3);
foreach($disallowed as $key){
    unset($array[$key]);
}

The result of print_r($array) being:

Array
(
    [2] => two
)

This is not applicable if you want to keep the filtered values for later use but tidier, if you're certain that you don't.

Solution 5 - Php

If you are looking for a method to filter an array by a string occurring in keys, you can use:

$mArray=array('foo'=>'bar','foo2'=>'bar2','fooToo'=>'bar3','baz'=>'nope');
$mSearch='foo';
$allowed=array_filter(
    array_keys($mArray),
    function($key) use ($mSearch){
        return stristr($key,$mSearch);
    });
$mResult=array_intersect_key($mArray,array_flip($allowed));

The result of print_r($mResult) is

Array ( [foo] => bar [foo2] => bar2 [fooToo] => bar3 )

An adaption of this answer that supports regular expressions

function array_preg_filter_keys($arr, $regexp) {
  $keys = array_keys($arr);
  $match = array_filter($keys, function($k) use($regexp) {
    return preg_match($regexp, $k) === 1;
  });
  return array_intersect_key($arr, array_flip($match));
}

$mArray = array('foo'=>'yes', 'foo2'=>'yes', 'FooToo'=>'yes', 'baz'=>'nope');

print_r(array_preg_filter_keys($mArray, "/^foo/i"));

Output

Array
(
    [foo] => yes
    [foo2] => yes
    [FooToo] => yes
)

Solution 6 - Php

How to get the current key of an array when using array_filter

Regardless of how I like Vincent's solution for Maček's problem, it doesn't actually use array_filter. If you came here from a search engine and where looking for a way to access the current iteration's key within array_filter's callback, you maybe where looking for something like this (PHP >= 5.3):

$my_array = ["foo" => 1, "hello" => "world"];

$allowed = ["foo", "bar"];

reset($my_array ); // Unnecessary in this case, as we just defined the array, but
                   // make sure your array is reset (see below for further explanation).

$my_array = array_filter($my_array, function($value) use (&$my_array, $allowed) {
  $key = key($my_array); // request key of current internal array pointer
  next($my_array); // advance internal array pointer

  return isset($allowed[$key]);
});

// $my_array now equals ['foo' => 1]

It passes the array you're filtering as a reference to the callback. As array_filter doesn't conventionally iterate over the array by increasing it's public internal pointer you have to advance it by yourself.

What's important here is that you need to make sure your array is reset, otherwise you might start right in the middle of it (because the internal array pointer was left there by some code of your's that was executed before).

Solution 7 - Php

Starting from PHP 5.6, you can use the ARRAY_FILTER_USE_KEY flag in array_filter:

$result = array_filter($my_array, function ($k) use ($allowed) {
    return in_array($k, $allowed);
}, ARRAY_FILTER_USE_KEY);


Otherwise, you can use this function (from TestDummy):

function filter_array_keys(array $array, $callback)
{
    $matchedKeys = array_filter(array_keys($array), $callback);

    return array_intersect_key($array, array_flip($matchedKeys));
}

$result = filter_array_keys($my_array, function ($k) use ($allowed) {
    return in_array($k, $allowed);
});


And here is an augmented version of mine, which accepts a callback or directly the keys:

function filter_array_keys(array $array, $keys)
{
    if (is_callable($keys)) {
        $keys = array_filter(array_keys($array), $keys);
    }

    return array_intersect_key($array, array_flip($keys));
}

// using a callback, like array_filter:
$result = filter_array_keys($my_array, function ($k) use ($allowed) {
    return in_array($k, $allowed);
});

// or, if you already have the keys:
$result = filter_array_keys($my_array, $allowed));


Last but not least, you may also use a simple foreach:

$result = [];
foreach ($my_array as $key => $value) {
    if (in_array($key, $allowed)) {
        $result[$key] = $value;
    }
}

Solution 8 - Php

Based on @sepiariver I did some similar testing on PHP 8.0.3:

$arr = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8];
$filter = ['a', 'e', 'h'];


$filtered = [];
$time = microtime(true);
$i = 1000000;
while($i) {
  $filtered = array_intersect_key($arr, array_flip($filter));
  $i--;
}
print_r($filtered);
echo microtime(true) - $time . " using array_intersect_key\n\n";


$filtered = [];
$time = microtime(true);
$i = 1000000;
while($i) {
  $filtered = array_filter(
    $arr,
    function ($key) use ($filter){return in_array($key, $filter);},
    ARRAY_FILTER_USE_KEY
  );
  $i--;
}
print_r($filtered);
echo microtime(true) - $time . " using array_filter\n\n";

$filtered = [];
$time = microtime(true);
$i = 1000000;
while($i) {
  foreach ($filter as $key)
    if(array_key_exists($key, $arr))
      $filtered[$key] = $arr[$key];
  $i--;
}
print_r($filtered);
echo microtime(true) - $time . " using foreach + array_key_exists\n\n";
  • 0.28603601455688 using array_intersect_key
  • 1.3096671104431 using array_filter
  • 0.19402384757996 using foreach + array_key_exists

The 'problem' of array_filter is that it will loop over all elements of $arr, whilst array_intersect_key and foreach only loop over $filter. The latter is more efficient, assuming $filter is smaller than $arr.

Solution 9 - Php

array filter function from php:

array_filter ( $array, $callback_function, $flag )

$array - It is the input array

$callback_function - The callback function to use, If the callback function returns true, the current value from array is returned into the result array.

$flag - It is optional parameter, it will determine what arguments are sent to callback function. If this parameter empty then callback function will take array values as argument. If you want to send array key as argument then use $flag as ARRAY_FILTER_USE_KEY. If you want to send both keys and values you should use $flag as ARRAY_FILTER_USE_BOTH .

For Example : Consider simple array

$array = array("a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5);

If you want to filter array based on the array key, We need to use ARRAY_FILTER_USE_KEY as third parameter of array function array_filter.

$get_key_res = array_filter($array,"get_key",ARRAY_FILTER_USE_KEY );

If you want to filter array based on the array key and array value, We need to use ARRAY_FILTER_USE_BOTH as third parameter of array function array_filter.

$get_both = array_filter($array,"get_both",ARRAY_FILTER_USE_BOTH );

Sample Callback functions:

 function get_key($key)
 {
	if($key == 'a')
	{
		return true;
	} else {
		return false;
	}
}
function get_both($val,$key)
{
	if($key == 'a' && $val == 1)
	{
		return true;
	}	else {
		return false;
	}
}

It will output

Output of $get_key is :Array ( [a] => 1 ) 
Output of $get_both is :Array ( [a] => 1 ) 

Solution 10 - Php

Perhaps an overkill if you need it just once, but you can use YaLinqo library* to filter collections (and perform any other transformations). This library allows peforming SQL-like queries on objects with fluent syntax. Its where function accepts a calback with two arguments: a value and a key. For example:

$filtered = from($array)
    ->where(function ($v, $k) use ($allowed) {
        return in_array($k, $allowed);
    })
    ->toArray();

(The where function returns an iterator, so if you only need to iterate with foreach over the resulting sequence once, ->toArray() can be removed.)

* developed by me

Solution 11 - Php

Naive and ugly (but seems to be faster) solution?

Only tried this in php 7.3.11 but an ugly loop seems to execute in about a third of the time. Similar results on an array with a few hundred keys. Micro-optimization, probably not useful in RW, but found it surprising and interesting:

$time = microtime(true);
$i = 100000;
while($i) {
    $my_array = ['foo' => 1, 'hello' => 'world'];
    $allowed  = ['foo', 'bar'];
    $filtered = array_filter(
        $my_array,
        function ($key) use ($allowed) {
            return in_array($key, $allowed);
        },
        ARRAY_FILTER_USE_KEY
    );
    $i--;
}
print_r($filtered);
echo microtime(true) - $time . ' on array_filter';

// 0.40600109100342 on array_filter
$time2 = microtime(true);
$i2 = 100000;
while($i2) {
    $my_array2 = ['foo' => 1, 'hello' => 'world'];
    $allowed2  = ['foo', 'bar'];
    $filtered2 = [];
    foreach ($my_array2 as $k => $v) {
        if (in_array($k, $allowed2)) $filtered2[$k] = $v;
    }
    $i2--;
}
print_r($filtered2);
echo microtime(true) - $time2 . ' on ugly loop';
// 0.15677785873413 on ugly loop

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmačekView Question on Stackoverflow
Solution 1 - PhpVincent SavardView Answer on Stackoverflow
Solution 2 - PhpRichard TurnerView Answer on Stackoverflow
Solution 3 - PhpCOilView Answer on Stackoverflow
Solution 4 - PhpAlastairView Answer on Stackoverflow
Solution 5 - PhpNicolas ZimmerView Answer on Stackoverflow
Solution 6 - PhpfluView Answer on Stackoverflow
Solution 7 - PhpGras DoubleView Answer on Stackoverflow
Solution 8 - PhpCrAzY_pRoGrAmMeRView Answer on Stackoverflow
Solution 9 - Phpprince joseView Answer on Stackoverflow
Solution 10 - PhpAthariView Answer on Stackoverflow
Solution 11 - PhpsepiariverView Answer on Stackoverflow