466

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);
1
  • Not a solution but another approach that might be useful is to $b = ['foo' => $a['foo'], 'bar' => $a['bar']] This will result in $b['bar'] be null. Commented Apr 2, 2018 at 13:45

12 Answers 12

539
Answer recommended by PHP Collective

With array_intersect_key and array_flip:

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

array(1) {
  ["foo"]=>
  int(1)
}
Sign up to request clarification or add additional context in comments.

3 Comments

@GWW, Generally, I've found that these types of array functions are faster than the equivalent foreach loop (and sometimes considerably), but the only way to know for sure is to time them both on the same data.
Why use array_flip? Simply define the $allowed with keys: allowed = array ( 'foo' => 1, 'bar' => 1 );
@YuvalA. If the array is larger, then array_flip gets necessary
485

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) {
        // N.b. in_array() is notorious for being slow 
        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,
    fn ($val, $key) => isset($allowed[$key]) && (
        $allowed[$key] === true || $allowed[$key] === $val
    ),
    ARRAY_FILTER_USE_BOTH
); // ['foo' => 1, 'bar' => 'baz']

4 Comments

Damn, as the author of that feature I should have looked for this question ;-)
PHP 7.4+ $filtered = array_filter( $my_array, fn ($key) => in_array($key, $allowed), ARRAY_FILTER_USE_KEY );
Any answer that leverages iterated calls of in_array() will not be more efficient than the more elegant call of array_intersect_key(). Yes, the lookup array will need to be flipped once, but because PHP is very fast about making key lookups (such as isset()), I expect in_array() to be left in the dust in the majority of test cases. More simply, isset() has been proven time and time again to greatly outperform in_array() in benchmarks. The only danger to be aware of is when the flipping technique mutates the value -- such as when you flip a float value into a key, it becomes an int.
@mickmackusa You’re likely to need to have a large array for the difference to be significant to the running of your application. Usually readability trumps performance micro-optimisations. Certainly something to be conscious of though.
9

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.

5 Comments

I wouldn't exactly call this "more flexible"; it feels a lot less straightforward than the accepted solution, too.
I agree. It would be more flexible is the condition was a more complex one.
Just passing by, for other users: This solution does not deal with the case that the $my_array has duplicate values or values that are not integers or strings. So I would not use this solution.
I agree this is more flexible as it allows you to change the filter logic. For example I used an array of disallowed keys and simply returned !in_array($key, $disallowed).
It is dangerous to call array_flip($my_array). If there are duplicate values in the array, the size of the array will be reduced because arrays cannot have duplicate keys in the same level. This approach should not be used -- it is unstable/unreliable.
4

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.

3 Comments

You should check if key $key exists in $array before doing unset.
@JarekJakubowski you do not need to check if an array key exists when using unset(). No warnings are issued if the key doesn't exist.
I haven't benchmarked the viable solutions on this page, but this may be a contender for most performant.
4

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
)

4 Comments

thanks for your answer. I would submit to you that using stristr within the "work" of the function is making some assumptions for the end user. Perhaps it would be better to allow the user to pass in a regular expression; this would give them more flexibility over certain things like anchors, word boundaries, and case sensitivity, etc.
I've added an adaptation of your answer that might help other people
You are certainly right, maček, that is a more versatile approach for users who are comfortable with regex. Thanks.
This is the correct answer to a different question. Remove all elements from array that do not start with a certain string Your answer ignores the requirements in the asked question.
4

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;
    }
}

2 Comments

I fail to see any new value in this answer. It all seems over-engineered, convoluted, and/or redundant because earlier answers already offered direct approaches to solve the question asked. Please explain why your answer should stay on the page, if you can.
When I posted this answer, PHP 5.6 had been released just 1 year ago, so it was far from being available on all hosts, hence the usefulness of userland implementations. Then, my answer isn't about providing a ready-to-pick (without really knowing what you do) solution. It's about thinking, step by step, how to solve the problem the best way. I'm expecting that once the reader has studied the answer, he should have understood the various approaches, and be able to determine how to solve the problem in his use case.
4

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 ) 

1 Comment

This late answer has completely ignored the requirements of the question asked. This is, at best, thr correct answer to a different question.
4

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).

6 Comments

This answer completely ignores the asker's requirements and sample data. This answer is, at best, the correct answer to a different question ...except it's not. $&array is not valid PHP and each() has been deprecated since PHP7.2 and completely removed since PHP8.
Hi @mickmackusa and thank you for your kind and constructive words. Seven years ago, when I wrote this answer, PHP 8 wasn't even at the horizon and each() wasn't deprecated at all. Imho, the gist of my answer could be easily transferred to the asker's question but I updated it accordingly, so that now, it can be copied and pasted without the need to give it much thought. I also fixed the small typo with the references ($& => &$). Feel free to edit my answer if there's still something in it, you don't like. Cheers
Please also keep in mind, that this question was called "How to use array_filter() to filter array keys?" (see: stackoverflow.com/posts/4260086/revisions) and was asked, when PHP 5.6 was not very widespread, so the new ARRAY_FILTER_USE_KEY flag wasn't commonly available. All answers on SO are children of their time and may not be valid, accurate or helpful more than half a decade later. I actually don't know if now-deprecated answers should be removed or kept for historical reasons. Someone might still be forced to support a project that uses a long-outdated version of PHP.
Ask yourself, if you were a researcher who was looking for the "best" approach to implement in their application, would you consider this answer to be "worth reading"? Sometimes their is "academic value" in a posted answer despite it not being optimal. If you think your post will be helpful to future researchers, keep it here. If you think it adds unnecessary bloat to a page with 11 different answers, then spare researchers' time by trashing the post. Even decade-old pages need curation on SO, this is why I monitor new and old pages. I care more than the average user about our content.
I think that deprecated answers should still be available. - for history and as well for people that may be still using OLD php (whatever language) - People that have used php 5.2 for example will know how to adapt the answer to a new version of php.
|
4

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.

3 Comments

Why wasn't Alastair's snippet included in the benchmarks?
Since, as he acknowledges himself, the functionality of his code is different than that of the three I've tested. In his case, $array ($arr in my code) is modified (unset); in my case $arr keeps its original state. Since functionality differ it's not fair to compare.
Well, if the original array needs to be preserved, then just save a copy before looping. Add that cost to the benchmark. Then the results will be the same.
4

I use a small "Utils" class where I add two filter static function to filter array using a denylist or a allowlist.

<?php

class Utils {
 
  /**
   * Filter an array based on a allowlist of keys
   *
   * @param array $array
   * @param array $allowlist
   *
   * @return array
   */
  public static function array_keys_allowlist( array $array, array $allowlist ): array {
    return array_intersect_key( $array, array_flip( $allowlist ) );
  }


  /**
   * Filter an array based on a denylist of keys
   *
   * @param array $array
   * @param array $denylist
   *
   * @return array
   */
  public static function array_keys_denylist( array $array, array $denylist ): array {
    return array_diff_key($array,array_flip($denylist));
  }

}

You can then use it like this

<?php

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

$my_array = Utils::array_keys_allowlist($my_array,  $allowed)

3 Comments

This answer merely restates what is already stated here and then answers a different question about how to do the inverse operation. Is it beneficial to write helper methods for what PHP already does well natively?
@mickmackusa the title of this question is broader than a deny list. It is beneficial to have such code centralized. You can then easily see performance impact because you have a function call and you can measure the resource usage of this function and do optimization in one place if needed.
@gagarine This is what I'd recommend (despite my own answer adding ancillary information about ARRAY_FILTER_USE_KEY): it has the benefit of speed and elegance and, more importantly, the function names document the effects. Over the years, there have been many comments on the array_intersect_key() answer, questioning the need for array_flip: that demonstrates how it's not necessarily obvious what that snippet does. Wrapping it in a function and giving it a descriptive name is a good way to resolve that.
1

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

3 Comments

in_array() is one of PHP's worst performing array searching functions. Adding the overhead of a library will only further slow down performance. Since two native functions or looped unset calls will concisely solve this problem, I would never entertain using a library's methods.
@mickmackusa While you're technically correct, it's a pure microptimization in most cases. If data with 10-100 items came from a database or a web service, you're making 0.5% of overall work, say, 5x faster, which achieves nothing. Of course, if we're talking about filtering 100,000 items coming right from RAM, then the overhead is considerable.
Anyway, this is more of an example of the library having a straightforward conscise solution compared to clunky features introduced in PHP after the library was released (array_filter with ARRAY_FILTER_USE_KEY), using an unusual function no other language I know has (array_flip) or having deep knowledge of PHP architecture (knowing that unset has the speed of hash-table access and that in_array scales lineraly).
0

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

2 Comments

Neither of these benchmarked filtering techniques should be used. There are better approaches that do not need in_array(). As the size of the whitelist array increases, the performance of in_array() will get ever worse.
These were not posted as solutions to be used IRL, but just as illustrations that all else equal, sometimes a loop performs better than a native function. Replace in_array with whatever you like in both of those “benchmarks” and the results (relative performance not absolute) would be the same or similar.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.