453

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);
Dharman
  • 30,962
  • 25
  • 85
  • 135
maček
  • 76,434
  • 37
  • 167
  • 198
  • 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`. – oriadam Apr 02 '18 at 13:45

12 Answers12

526

With array_intersect_key and array_flip:

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

array(1) {
  ["foo"]=>
  int(1)
}
NDM
  • 6,731
  • 3
  • 39
  • 52
Vincent Savard
  • 34,979
  • 10
  • 68
  • 73
  • 1
    @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. – Matthew Nov 23 '10 at 20:03
  • 2
    Why use `array_flip`? Simply define the `$allowed` with keys: `allowed = array ( 'foo' => 1, 'bar' => 1 );` – Yuval A. Jun 04 '17 at 12:55
  • @YuvalA. If the array is larger, then array_flip gets necessary – Gabriel Beauchemin-Dauphinais Dec 20 '22 at 17:50
469

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']
Richard Turner
  • 12,506
  • 6
  • 36
  • 37
  • 32
    Damn, as the [author](https://github.com/php/php-src/commit/a88c7be88b57162eb0a89ff1b7c2ef60ac1de935) of that feature I should have looked for this question ;-) – Ja͢ck Jun 08 '15 at 08:09
  • 1
    PHP 7.4+ `$filtered = array_filter( $my_array, fn ($key) => in_array($key, $allowed), ARRAY_FILTER_USE_KEY );` – jartaud Feb 27 '21 at 02:38
  • 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 Jan 16 '22 at 05:45
  • 1
    @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. – Richard Turner Jan 17 '22 at 09:25
  • There are no redeeming features to your snippets and I would not use any of them in my own projects. VincentSavard's flip&intersect_key technique is more performant, more concise, more elegant, more readable, and suitably utilizes a fully native functional approach. I am not attacking you, I am comparing the posts. – mickmackusa Jan 17 '22 at 09:43
10

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.

COil
  • 7,201
  • 2
  • 50
  • 98
  • 1
    I wouldn't exactly call this "more flexible"; it feels a lot less straightforward than the accepted solution, too. – maček Jan 26 '13 at 21:33
  • I agree. It would be more flexible is the condition was a more complex one. – COil Jan 31 '13 at 11:25
  • 1
    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. – user23127 Jun 09 '14 at 17:38
  • 2
    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). – nfplee Sep 07 '14 at 13:05
  • 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. – mickmackusa Jan 18 '22 at 21:33
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.

Alastair
  • 6,837
  • 4
  • 35
  • 29
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
)
maček
  • 76,434
  • 37
  • 167
  • 198
Nicolas Zimmer
  • 424
  • 4
  • 10
  • 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. – maček Mar 02 '14 at 19:51
  • I've added an adaptation of your answer that might help other people – maček Mar 02 '14 at 20:05
  • 1
    You are certainly right, maček, that is a more versatile approach for users who are comfortable with regex. Thanks. – Nicolas Zimmer Mar 03 '14 at 08:02
  • This is the correct answer to a different question. [Remove all elements from array that do not start with a certain string](https://stackoverflow.com/q/4979238/2943403) Your answer ignores the requirements in the asked question. – mickmackusa Jan 17 '22 at 10:19
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).

flu
  • 14,307
  • 8
  • 74
  • 71
  • 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()`](https://www.php.net/manual/en/function.each.php) has been deprecated since PHP7.2 and completely removed since PHP8. – mickmackusa Jan 17 '22 at 10:11
  • 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 – flu Jan 20 '22 at 16:29
  • Please also keep in mind, that this question was called "How to use array_filter() to filter array keys?" (see: https://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. – flu Jan 20 '22 at 16:50
  • 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. – mickmackusa Jan 20 '22 at 21:42
  • As a researcher I wouldn't have changed the title (and with that, possibly the subject of most of the answers) of a seven year old question. From a researchers point of view it would be very interesting to see, if there was a way "to use array_filter() to filter array keys?" seven years ago, when PHP 5.5 was installed on more than half of all systems. and there was no `ARRAY_FILTER_USE_KEY` flag around. There are many questions like this and in a way they pose a challenge: Is this even possible? As no answer actually used `array_filter` at the time, I'd say my answer still has value, yes. – flu Jan 21 '22 at 16:12
  • 1
    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. – emilushi Jun 29 '22 at 18:00
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;
    }
}
Gras Double
  • 15,901
  • 8
  • 56
  • 54
  • 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. – mickmackusa Jan 17 '22 at 10:16
  • 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. – Gras Double Jan 23 '22 at 17:29
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.

  • Why wasn't Alastair's snippet included in the benchmarks? – mickmackusa Jan 17 '22 at 10:25
  • 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. – CrAzY_pRoGrAmMeR Jan 18 '22 at 20:03
  • 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. – mickmackusa Jan 18 '22 at 21:38
3

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 ) 
prince jose
  • 193
  • 1
  • 7
  • This late answer has completely ignored the requirements of the question asked. This is, at best, thr correct answer to a different question. – mickmackusa Jan 17 '22 at 10:23
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

Athari
  • 33,702
  • 16
  • 105
  • 146
  • `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 Jan 17 '22 at 10:28
  • @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. – Athari Jan 18 '22 at 13:40
  • 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). – Athari Jan 18 '22 at 13:40
1

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)
gagarine
  • 4,190
  • 2
  • 30
  • 39
  • This answer merely restates what is already stated [here](https://stackoverflow.com/a/4260168/2943403) 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 Sep 15 '22 at 22:41
  • @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 Sep 16 '22 at 13:31
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
sepiariver
  • 511
  • 3
  • 8
  • 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. – mickmackusa Jan 17 '22 at 10:36
  • 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. – sepiariver Jan 18 '22 at 15:45