Reputation: 2282
Today I've learned that array_filter
preserves references in arrays:
$str = 'original';
$refArray = [&$str];
$filtered = array_filter($refArray, fn () => true);
$filtered[0] = 'MODIFIED';
echo $str; // prints MODIFIED
(3v4l)
I'm looking for a clean, concise, idiomatic way to filter an array producing a result that is safe for modification, ie. echo $str
would print original
.
Looping over the array seems to work: $value
assignment drops the reference. It's not concise though (6 lines vs. 1) and a comment is necessary to explain why this approach is used:
$filtered = [];
foreach ($refArray as $key => $value) {
if (true /*your filter here*/) {
$filtered[$key] = $value;
}
}
$filtered[0] = 'MODIFIED';
echo $str; // prints original
I've tried to copy the input array with array_combine
, but apparently it preserves references too, along with array_values
:
$copy = array_combine(array_keys($refArray), array_values($refArray));
$filtered = array_filter($copy, fn () => true);
$filtered[0] = 'MODIFIED';
echo $str; // prints MODIFIED
Upvotes: 0
Views: 99
Reputation: 163362
If you have an array of references, as a work around you might use array_map (which will return the same amount of items)
Reading array_filter:
Iterates over each value in the array passing them to the callback function. If the callback function returns true, the current value from array is returned into the result array.
Reading array_map
returns an array containing the results of applying the callback to the corresponding index of array
When testing this (on PHP 7.4.3), it seems that when using array_filter and the callback returns true, the current value (the reference) is returned. For array_map, the reference is used in then callback and returns the value that it references.
For example
$str = 'original';
$refArray = [&$str];
$refArray[0] = 'MODIFIED';
echo $str; // prints MODIFIED
$str2 = 'original2';
$refArray2 = [&$str2];
$refArray2 = array_filter($refArray2, fn() => true);
$refArray2[0] = 'MODIFIED';
echo $str2; // prints MODIFIED
$str3 = 'original3';
$refArray3 = [&$str3];
$refArray3 = array_map(fn($x) => $x, $refArray3);
$refArray3[0] = 'MODIFIED';
echo $str3; // prints original3
$str4 = ['original4'];
$refArray4 = [&$str4];
$refArray4 = array_map(fn($x) => $x, $refArray4);
$refArray4[0][0] = 'MODIFIED';
var_dump($str4);
// gives
//array(1) {
// [0]=>
// string(9) "original4"
//}
Upvotes: 2
Reputation: 2282
lstrojny/functional-php
's select()
or its alias filter()
should do the job because it's using a loop internally.
It's a drop-in replacement for array_filter
:
use function Functional\filter;
$filtered = filter($refArray, fn () => true);
Serialization also removes the reference, although it will be more resource-heavy and can have additional side effects if magic methods are implemented for array values:
$copy = unserialize(serialize($refArray));
$filtered = array_filter($copy, fn () => true);
Upvotes: 0
Reputation: 21
$refArray
is array of references, not values thus array_filter
returns same elements you passed in - references.
IMO this is wrong assumption from the very beggining, but don't know your context.
The only solution you have is to create new array with values based on array of refs.
You can achieve this with looping as you did or just flipping array twice, because array key cannot be reference. But
example:
$str = 'original';
$refArray = [&$str];
$filtered = array_filter(array_flip(array_flip($refArray)), fn () => true);
$filtered[0] = 'MODIFIED';
echo $str; // prints original
I wouldn't call it solution but rather dirty workaround.
Upvotes: 1