Can someone explain this strange behaviour I am witnessing in PHP 8.1.6?
I guess the behaviour is according to standards, I just don't understand it. The function call has a parameter by value, yet the array is changed after the call, all because of the reference to the first element.
$arr = [3, 4];
$ref = &$arr[0] ; // this statement causes the weird behaviour. Without it all is ok
print_r($arr); // as expected
print_r(doSomethingTo($arr)); // as expected
print_r($arr); // WHAT JUST HAPPENED?
function doSomethingTo($arr) {
// $arr BY VALUE
foreach($arr as $k => $v)
$arr[$k]=$v+1 ;
return $arr ;
}
result:
Array
(
[0] => 3
[1] => 4
)
Array
(
[0] => 4
[1] => 5
)
Array
(
[0] => 4
[1] => 4
)
Why? Why does the reference to the first array element change the behaviour of the by value parameter passing? The first element was now passed by reference to the function, and the second by value! ???
Thank you for an explanation!
A little complex formulated answer I found at https://www.php.net/manual/en/language.references.whatdo.php
I will translate the answer to this particular situation.
The thing many people may not realise, is, is that $a =& $b does not mean that $a references now to $b, but $a and $b BOTH become references to the same value that was originally contained in $b.
So when evaluating $ref = &$arr[0] ; the $arr[0] element and the $ref become both references to the 3 value.
Now when an array is passed as a parameter by value, the array is always duplicated. Same happens here.
Which means the $arr[0] reference is duplicated too, i.e. in the duplicate array another reference is created to the same 3 value. So we have $ref, the original $arr[0] and the $arr[0] within the function all being references to the 3 value.
When either of these three references gets an assignment, of course the value changes.
Thank you #Sammitch for setting me on the right track!!!
Very simplified example:
function returnArray() {
return array('First','Second','Third');
}
$arr = array('hello','world');
$arr[] = returnArray();
print_r($arr);
I was (wrongly) expecting to see $arr containing 5 elements, but it actually contains this (and I understand it makes sense):
Array
(
[0] => hello
[1] => world
[2] => Array
(
[0] => First
[1] => Second
[2] => Third
)
)
Easily enough, I "fixed" it using a temporary array, scanning through its elements, and adding the elements one by one to the $arr array.
But I guess/hope that there must be a way to tell PHP to add the elements automatically one by one, instead of creating a "child" array within the first one, right? Thanks!
You can use array_merge,
$arr = array_merge($arr, returnArray());
will result in
Array
(
[0] => hello
[1] => world
[2] => First
[3] => Second
[4] => Third
)
This will work smoothly here, since your arrays both have numeric keys. If the keys were strings, you’d had to be more careful (resp. decide if that would be the result you want, or not), because
If the input arrays have the same string keys, then the later value for that key will overwrite the previous one. If, however, the arrays contain numeric keys, the later value will not overwrite the original value, but will be appended.
You are appending the resulting array to previously created array. Instead of the use array merge.
function returnArray() {
return array('First','Second','Third');
}
$arr = array('hello','world');
$arr = array_merge($arr, returnArray());
print_r($arr);
I'm facing some strange behavior with the array_shift function in PHP:
function shift($arr)
{
array_shift($arr);
}
$a = [1, 2, 3];
shift($a);
print_r($a);
Output:
Array ( [0] => 1 [1] => 2 [2] => 3 )
My Expected Output:
Array ( [0] => 2 [1] => 3 )
Explanation:
I believe that $a and $arr, despite being different references, point to the same array object. I expect array_shift to look where $arr is pointing and modify (shorten) that array. Then that change will be visible when looking up the array via $a.
However, when I test my theory, no change is visible. The array is just as long as before array_shift was called. What gives?
The $arr inside your function is not the same array as $a. A local copy is created unless you pass it by reference, e.g.
function shift(&$arr) ...
Check the PHP documentation on variable scope:
However, within user-defined functions a local function scope is introduced. Any variable used inside a function is by default limited to the local function scope.
array_shift is working the way you expect it to, though. You can add a print_r($arr); inside your function after you do the array_shift to see the shortened version of the array within the function scope. It just won't affect the original array unless you pass it by reference.
This question already has answers here:
Difference between array_map, array_walk and array_filter
(5 answers)
Closed 2 years ago.
I looked into the similar topics in web as well stack overflow, but could get this one into my head clearly. Difference between array_map, array_walk and array_filter
<?php
error_reporting(-1);
$arr = array(2.4, 2.6, 3.5);
print_r(array_map(function($a) {
$a > 2.5;
},$arr));
print_r(array_filter($arr, function($a){
return $a > 2.5;
}));
?>
The above code returns me a filtered array whose value is > 2.5. Can i achieve what an array_filter does with an array_map?.
All three, array_filter, array_map, and array_walk, use a callback function to loop through an array much in the same way foreach loops loop through an $array using $key => $value pairs.
For the duration of this post, I will be referring to the original array, passed to the above mentioned functions, as $array, the index, of the current item in the loop, as $key, and the value, of the current item in the loop, as $value.
array_filter is likened to MySQL's SELECT query which SELECTs records but doesn't modify them.
array_filter's callback is passed the $value of the current loop item and whatever the callback returns is treated as a boolean.
If true, the item is included in the results.
If false, the item is excluded from the results.
Thus you might do:
<pre><?php
$users=array('user1'=>array('logged_in'=>'Y'),'user2'=>array('logged_in'=>'N'),'user3'=>array('logged_in'=>'Y'),'user4'=>array('logged_in'=>'Y'),'user5'=>array('logged_in'=>'N'));
function signedIn($value)
{
if($value['logged_in']=='Y')return true;
return false;
}
$signedInUsers=array_filter($users,'signedIn');
print_r($signedInUsers);//Array ( [user1] => Array ( [logged_in] => Y ) [user3] => Array ( [logged_in] => Y ) [user4] => Array ( [logged_in] => Y ) )
?></pre>
array_map on the other hand accepts multiple arrays as arguments.
If one array is specified, the $value of the current item in the loop is sent to the callback.
If two or more arrays are used, all the arrays need to first be passed through array_values as mentioned in the documentation:
If the array argument contains string keys then the returned array
will contain string keys if and only if exactly one array is passed.
If more than one argument is passed then the returned array always has
integer keys
The first array is looped through and its value is passed to the callback as its first parameter, and if a second array is specified it will also be looped through and its value will be sent as the 2nd parameter to the callback and so-on and so-forth for each additional parameter.
If the length of the arrays don't match, the largest array is used, as mentioned in the documentation:
Usually when using two or more arrays, they should be of equal length
because the callback function is applied in parallel to the
corresponding elements. If the arrays are of unequal length, shorter
ones will be extended with empty elements to match the length of the
longest.
Each time the callback is called, the return value is collected.
The keys are preserved only when working with one array, and array_map returns the resulting array.
If working with two or more arrays, the keys are lost and instead a new array populated with the callback results is returned.
array_map only sends the callback the $value of the current item not its $key.
If you need the key as well, you can pass array_keys($array) as an additional argument then the callback will receive both the $key and $value.
However, when using multiple arrays, the original keys will be lost in much the same manner as array_values discards the keys.
If you need the keys to be preserved, you can use array_keys to grab the keys from the original array and array_values to grab the values from the result of array_map, or just use the result of array_map directly since it is already returning the values, then combine the two using array_combine.
Thus you might do:
<pre><?php
$array=array('apple'=>'a','orange'=>'o');
function fn($key,$value)
{
return $value.' is for '.$key;
}
$result=array_map('fn',array_keys($array),$array);
print_r($result);//Array ( [0] => a is for apple [1] => o is for orange )
print_r(array_combine(array_keys($array),$result));//Array ( [apple] => a is for apple [orange] => o is for orange )
?></pre>
array_walk is very similar to foreach($array as $key=>$value) in that the callback is sent both a key and a value. It also accepts an optional argument if you want to pass in a 3rd argument directly to the callback.
array_walk returns a boolean value indicating whether the loop completed successfully.
(I have yet to find a practical use for it)
Note that array_walk doesn't make use of the callback's return.
Since array_walk returns a boolean value, in order for array_walk to affect something,
you'll need to reference &$value so you have that which to modify or use a global array.
Alternatively, if you don't want to pollute the global scope, array_walk's optional 3rd argument can be used to pass in a reference to a variable with which to write to.
Thus you might do:
<pre><?php
$readArray=array(1=>'January',2=>'February',3=>'March',4=>'April',5=>'May',6=>'June',7=>'July',8=>'August',9=>'September',10=>'October',11=>'November',12=>'December');
$writeArray=array();
function fn($value,$key,&$writeArray)
{
$writeArray[$key]=substr($value,0,3);
}
array_walk($readArray,'fn',&$writeArray);
print_r($writeArray);//Array ( [1] => Jan [2] => Feb [3] => Mar [4] => Apr [5] => May [6] => Jun [7] => Jul [8] => Aug [9] => Sep [10] => Oct [11] => Nov [12] => Dec )
?></pre>
array_filter returns the elements of the original array for which the function returns true.
array_map returns an array of the results of calling the function on all the elements of the original array.
I can't think of a situation where you could use one instead of the other.
array_map Returns an array containing all the elements of array after applying the callback function to each one.
for example:
$a=array("a","bb","ccd","fdjkfgf");
$b = array_map("strlen",$a);
print_r($b);
//output
Array
(
[0] => 1 //like strlen(a)
[1] => 2 //like strlen(bb)
[2] => 3 //like strlen(ccd)
[3] => 7 //like strlen(fdjkfgf)
)
whereas array_filter return only those elements of array for which the function is true
example: remove "bb" value from array
function test_filter($b)
{
if($b=="bb")
{
return false;
}
else
{
return true;
}
}
$a=array("a","bb","ccd","fdjkfgf");
$b = array_filter($a,"test_filter");
print_r($b);
//output
Array
(
[0] => a //test_filter() return true
[2] => ccd //test_filter() return true
[3] => fdjkfgf //test_filter() return true
)
array_filter works without a callable (function) being passed, whereas for array_map it is mandatory.
e.g.
$v = [true, false, true, true, false];
$x = array_filter($v);
var_dump($x);
array(3) { [0]=> bool(true) [2]=> bool(true) [3]=> bool(true) }
array_walk changes the actual array passed in, whereas array_filter and array_map return new arrays, this is because the array is passed by reference.
array_map has no collateral effects while array_map never changes its arguments.
The resulting array of array_map/array_walk has the same number of
elements as the argument(s); array_filter picks only a subset of the
elements of the array according to a filtering function. It does
preserve the keys.
Example:
<pre>
<?php
$origarray1 = array(2.4, 2.6, 3.5);
$origarray2 = array(2.4, 2.6, 3.5);
print_r(array_map('floor', $origarray1)); // $origarray1 stays the same
// changes $origarray2
array_walk($origarray2, function (&$v, $k) { $v = floor($v); });
print_r($origarray2);
// this is a more proper use of array_walk
array_walk($origarray1, function ($v, $k) { echo "$k => $v", "\n"; });
// array_map accepts several arrays
print_r(
array_map(function ($a, $b) { return $a * $b; }, $origarray1, $origarray2)
);
// select only elements that are > 2.5
print_r(
array_filter($origarray1, function ($a) { return $a > 2.5; })
);
?>
</pre>
Result:
Array
(
[0] => 2
[1] => 2
[2] => 3
)
Array
(
[0] => 2
[1] => 2
[2] => 3
)
0 => 2.4
1 => 2.6
2 => 3.5
Array
(
[0] => 4.8
[1] => 5.2
[2] => 10.5
)
Array
(
[1] => 2.6
[2] => 3.5
)
Althoug this works well enough, I am curious if anyone knows of a prettier way of doing this as this situation seems to come up quite often.
<?php
//Initialy, data is nested up in $some_array[0] ...
$some_array = array(array('somevar' => "someValue", "someOtherVar" => "someOtherValue"));
print_r($some_array);
Array ( [0] => Array ( [somevar] => someValue [someOtherVar] => someOtherValue ) )
// Could the following line be achieved a more elegant fashion?
$some_array = $some_array[0];
print_r($some_array);
// Prints the intended result:
Array ( [somevar] => someValue [someOtherVar] => someOtherValue )
Does anyone know of a way to achieve this with a native function or in a more elegant fashion?
Thanks!
The native function you're looking for is called reset (Demo):
$some_array = reset($some_array);
For explicit clarification: current is not necessary.
You could use current (explained here), it basically points to the first element in the array and returns it.
To be absolutely sure you get the first element, you should reset your array, like so:
reset($arr)
$firstElement = current($arr)