browsing an array by reference is changing it (no actions done) - php

edit:do not read the related topic, the answer below is clear and gives the solution, while the other topic just states the issue.
I have something weird here
My code looks like this:
var_dump($resultFlatTree);
foreach($resultFlatTree as &$element)
{
/*if(isset($element["action"]) && $element["action"] == "new")
{
//let's save the original ID so we can find the children
$originalID = $element["id"];
//now we get the object
$newObject = $setUpForDimension->createAnObject($dimension,$element,$customer);
$element['id'] = $newObject->getId();
echo "new";
//and let's not forget to change the parent_id of its children
$arrayFunctions->arrayChangingValues($resultFlatTree,"parent_id",$element['id'],$originalID);
$em->persist($newObject);
} */
}
$em->flush();
var_dump($resultFlatTree);
the code inside the foreach is commented to be sure that it's not what I'm doing that's changing the array.
here the array before the foreach:
array(3) {
[0]=>
array(10) {
["id"]=>
int(2)
["name"]=>
string(7) "Revenue"
["code"]=>
string(6) "700000"
["sense"]=>
string(2) "CR"
["lft"]=>
int(1)
["lvl"]=>
int(2)
["rgt"]=>
int(1)
["root"]=>
int(1)
["$$hashKey"]=>
string(3) "00D"
["parent_id"]=>
int(1)
}
[1]=>
array(10) {
["id"]=>
int(3)
["name"]=>
string(7) "Charges"
["code"]=>
string(6) "600000"
["sense"]=>
string(2) "DR"
["lft"]=>
int(3)
["lvl"]=>
int(2)
["rgt"]=>
int(4)
["root"]=>
int(1)
["$$hashKey"]=>
string(3) "00P"
["parent_id"]=>
int(4)
}
[2]=>
array(10) {
["id"]=>
int(4)
["name"]=>
string(6) "Energy"
["code"]=>
string(6) "606000"
["sense"]=>
string(2) "DR"
["lft"]=>
int(2)
["lvl"]=>
int(1)
["rgt"]=>
int(5)
["root"]=>
int(1)
["$$hashKey"]=>
string(3) "00E"
["parent_id"]=>
int(1)
}
}
and then after:
array(3) {
[0]=>
array(10) {
["id"]=>
int(2)
["name"]=>
string(7) "Revenue"
["code"]=>
string(6) "700000"
["sense"]=>
string(2) "CR"
["lft"]=>
int(1)
["lvl"]=>
int(2)
["rgt"]=>
int(1)
["root"]=>
int(1)
["$$hashKey"]=>
string(3) "00D"
["parent_id"]=>
int(1)
}
[1]=>
array(10) {
["id"]=>
int(3)
["name"]=>
string(7) "Charges"
["code"]=>
string(6) "600000"
["sense"]=>
string(2) "DR"
["lft"]=>
int(3)
["lvl"]=>
int(2)
["rgt"]=>
int(4)
["root"]=>
int(1)
["$$hashKey"]=>
string(3) "00P"
["parent_id"]=>
int(4)
}
[2]=>
&array(10) {
["id"]=>
int(4)
["name"]=>
string(6) "Energy"
["code"]=>
string(6) "606000"
["sense"]=>
string(2) "DR"
["lft"]=>
int(2)
["lvl"]=>
int(1)
["rgt"]=>
int(5)
["root"]=>
int(1)
["$$hashKey"]=>
string(3) "00E"
["parent_id"]=>
int(1)
}
}
As you can see, the last element is now changed and is by reference.
This completely messes up the processes I do with the array afterward.
Is that normal behavior ?
How can I avoid it ?

When you pass by reference to a foreach statement, you really should read the docs :)
http://php.net/manual/en/control-structures.foreach.php
In order to be able to directly modify array elements within the loop precede $value with &. In that case the value will be assigned by reference.
<?php
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
$value = $value * 2;
}
// $arr is now array(2, 4, 6, 8)
unset($value); // break the reference with the last element
?>
Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().
Basically, it's saying that when you pass by ref, it will remain locked on the last item due to an internal pointer.
The second user-comment at 40 points:
"Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset()."
I cannot stress this point of the documentation enough! Here is a simple example of exactly why this must be done:
<?php
$arr1 = array("a" => 1, "b" => 2, "c" => 3);
$arr2 = array("x" => 4, "y" => 5, "z" => 6);
foreach ($arr1 as $key => &$val) {}
foreach ($arr2 as $key => $val) {}
var_dump($arr1);
var_dump($arr2);
?>
The output is:
array(3) { ["a"]=> int(1) ["b"]=> int(2) ["c"]=> &int(6) }
array(3) { ["x"]=> int(4) ["y"]=> int(5) ["z"]=> int(6) }
Notice how the last index in $arr1 is now the value from the last index in $arr2!
There are more comments which you will find interesting if you look for "reference" in that link.
tl;dr:
It's a bit funny/buggy/weird/un-patched.
Understand what the implications are as you write your code and make space for them.

Related

How to Generate an Array of Random Number Sets in PHP?

I'm trying to figure out the best way to create an array of sets of randomly selected numbers from a range. So, for instance I have a range of numbers from which to select: 1-100. I want to generate X number of sets of 5 of those numbers WITHOUT DUPLICATES. So, I want to generate something like:
[3, 24, 32, 49, 68]
[2, 18, 43, 76, 98]
[10, 12, 23, 45, 67]
[5, 56, 64, 72, 90]
...
I know how to generate random numbers from a range once, I just am stuck on doing it X number of times without the possibility of duplicate sets.
One way to solve this problem is simply to generate the desired range $arr = range(1, 100), then shuffle it to randomize shuffle($arr) then chunk it to get sets of 5 $sets = array_chunk($arr, 5).
So the final answer:
$arr = range(1, 100); // gives us an array of 100 integers 1-100
shuffle($arr); // randomly orders those integers
$sets = array_chunk($arr, 5); // creats an array of arrays of sets of 5 integers
foreach ($sets as $set) {
echo implode(',', $set), "\n";
}
Gives you something like...
14,24,60,95,86
47,54,10,77,3
11,22,88,80,39
72,46,81,78,59
63,98,52,82,8
79,34,43,13,41
67,33,75,1,2
4,57,84,73,17
32,55,35,18,70
64,85,100,93,71
25,19,26,20,76
99,89,7,87,91
37,97,68,27,5
74,48,65,61,58
45,31,9,30,21
16,56,50,96,90
92,40,6,44,23
28,94,38,83,29
36,62,51,66,15
12,69,49,42,53
Alternative implementation
Another way to solve this problem, that doesn't require writing the entire array into memory at once and then resorting it randomly, would be to use a hashset that tracks all randomly generated integers to prevent duplicates. This gives us the same exact result except that we do both the random number generation and sorting in a single step instead of two. You can also even do the chunking in the same step this way.
Keep in mind, however, that this solution is technically slower since it's in unbounded time (we have to keep guessing if the number already exists).
I'm going to use a generator to implement this solution since it's easier to write than an Iterator pattern.
function randomizeIntegerXRangeChunked($start, $end, $chunks = 5): Generator
{
$inSet = []; // track the integers already in the set
for ($i = $start, $c = 0; $i <= $end; $i++) {
/* solution is in unbounded time */
while (isset($inSet[($n = mt_rand($start, $end))]));
$inSet[$n] = true;
yield $c => $n;
if (!($i % $chunks)) {
$c++;
}
}
}
$arr = [];
foreach (randomizeIntegerXRangeChunked(1, 100, 5) as $chunk => $int) {
$arr[$chunk][] = $int;
}
var_dump($arr);
The output will be similar to...
array(20) {
[0]=>
array(5) {
[0]=>
int(43)
[1]=>
int(52)
[2]=>
int(38)
[3]=>
int(73)
[4]=>
int(55)
}
[1]=>
array(5) {
[0]=>
int(59)
[1]=>
int(3)
[2]=>
int(71)
[3]=>
int(47)
[4]=>
int(50)
}
[2]=>
array(5) {
[0]=>
int(54)
[1]=>
int(19)
[2]=>
int(60)
[3]=>
int(40)
[4]=>
int(5)
}
[3]=>
array(5) {
[0]=>
int(26)
[1]=>
int(46)
[2]=>
int(93)
[3]=>
int(80)
[4]=>
int(63)
}
[4]=>
array(5) {
[0]=>
int(18)
[1]=>
int(45)
[2]=>
int(98)
[3]=>
int(1)
[4]=>
int(72)
}
[5]=>
array(5) {
[0]=>
int(37)
[1]=>
int(97)
[2]=>
int(15)
[3]=>
int(68)
[4]=>
int(8)
}
[6]=>
array(5) {
[0]=>
int(34)
[1]=>
int(14)
[2]=>
int(33)
[3]=>
int(24)
[4]=>
int(65)
}
[7]=>
array(5) {
[0]=>
int(4)
[1]=>
int(16)
[2]=>
int(13)
[3]=>
int(41)
[4]=>
int(86)
}
[8]=>
array(5) {
[0]=>
int(95)
[1]=>
int(12)
[2]=>
int(44)
[3]=>
int(66)
[4]=>
int(83)
}
[9]=>
array(5) {
[0]=>
int(67)
[1]=>
int(48)
[2]=>
int(91)
[3]=>
int(27)
[4]=>
int(79)
}
[10]=>
array(5) {
[0]=>
int(56)
[1]=>
int(25)
[2]=>
int(2)
[3]=>
int(64)
[4]=>
int(78)
}
[11]=>
array(5) {
[0]=>
int(57)
[1]=>
int(17)
[2]=>
int(74)
[3]=>
int(42)
[4]=>
int(69)
}
[12]=>
array(5) {
[0]=>
int(96)
[1]=>
int(20)
[2]=>
int(9)
[3]=>
int(28)
[4]=>
int(7)
}
[13]=>
array(5) {
[0]=>
int(30)
[1]=>
int(75)
[2]=>
int(21)
[3]=>
int(6)
[4]=>
int(89)
}
[14]=>
array(5) {
[0]=>
int(51)
[1]=>
int(36)
[2]=>
int(62)
[3]=>
int(58)
[4]=>
int(23)
}
[15]=>
array(5) {
[0]=>
int(85)
[1]=>
int(32)
[2]=>
int(100)
[3]=>
int(61)
[4]=>
int(49)
}
[16]=>
array(5) {
[0]=>
int(39)
[1]=>
int(87)
[2]=>
int(76)
[3]=>
int(70)
[4]=>
int(22)
}
[17]=>
array(5) {
[0]=>
int(88)
[1]=>
int(77)
[2]=>
int(10)
[3]=>
int(99)
[4]=>
int(53)
}
[18]=>
array(5) {
[0]=>
int(94)
[1]=>
int(35)
[2]=>
int(92)
[3]=>
int(90)
[4]=>
int(84)
}
[19]=>
array(5) {
[0]=>
int(81)
[1]=>
int(82)
[2]=>
int(31)
[3]=>
int(29)
[4]=>
int(11)
}
}
You can try it like this as well.
// Array
$NumberArray = array();
$TempArray = array(); // Used for storing randoms while we check via if statement.
// Loop 5 times.
for ($x=0; $x<=4; $x++) {
// Random Number 1-100
$RandomNumber = rand(1,100);
// We can use Array Push to add to the array and check using in_array.
array_push($TempArray, $RandomNumber);
// Check first thing in temp array and see if its already in number array. (If not then push)
if (!in_array(reset($TempArray), $NumberArray)) {
array_push($NumberArray, reset($TempArray)); // Add to NumberArray
// Clear Temp Array
$TempArray = array();
} else {
// Add to the loop
$x--;
}
}
print_r($NumberArray);
This outputs an array like such:
Array
(
[0] => 32
[1] => 26
[2] => 59
[3] => 96
[4] => 34
)

list() wrap array in another array php

Why list() method using two array (array of arrays) as values don't work (for one specific array ($subCategories) the list wrap in another array, the other array is output equals before list()).
list method:
list($subCategories, $subCategoriesName) = [$subCategories, $subCategoriesName]; //[array, array]
[$subCategories, $subCategoriesName] are the return of a function
$subCategories before list
array(4) {
[0]=>
array(3) {
["id"]=>
int(1)
["sub_category_name"]=>
string(14) "Industrilizado"
["category_id"]=>
int(1)
}
[1]=>
array(3) {
["id"]=>
int(2)
["sub_category_name"]=>
string(9) "In natura"
["category_id"]=>
int(1)
}
[2]=>
array(3) {
["id"]=>
int(3)
["sub_category_name"]=>
string(13) "Comida pronta"
["category_id"]=>
int(1)
}
[3]=>
array(3) {
["id"]=>
int(4)
["sub_category_name"]=>
string(11) "Desidratado"
["category_id"]=>
int(1)
}
}
$subCategories after list
array(1) {
[0]=>
array(4) {
[0]=>
array(3) {
["id"]=>
int(1)
["sub_category_name"]=>
string(14) "Industrilizado"
["category_id"]=>
int(1)
}
[1]=>
array(3) {
["id"]=>
int(2)
["sub_category_name"]=>
string(9) "In natura"
["category_id"]=>
int(1)
}
[2]=>
array(3) {
["id"]=>
int(3)
["sub_category_name"]=>
string(13) "Comida pronta"
["category_id"]=>
int(1)
}
[3]=>
array(3) {
["id"]=>
int(4)
["sub_category_name"]=>
string(11) "Desidratado"
["category_id"]=>
int(1)
}
}

PHP: Extracting a value from a multidimensional array

In this array, I am attempting to extract the value int(2128) from the ["member_count"] key.
array(3) {
["total"]=>
int(1)
["data"]=>
array(1) {
[0]=>
array(17) {
["id"]=>
string(10) "999999999999"
["web_id"]=>
int(421097)
["name"]=>
string(21) "Name Of List"
["date_created"]=>
string(19) "2014-07-14 13:51:01"
["email_type_option"]=>
bool(false)
["use_awesomebar"]=>
bool(true)
["default_from_name"]=>
string(14) "Department Name"
["default_from_email"]=>
string(24) "user#email.com"
["default_subject"]=>
string(0) ""
["default_language"]=>
string(2) "en"
["list_rating"]=>
float(3.5)
["subscribe_url_short"]=>
string(23) "website/4545"
["subscribe_url_long"]=>
string(91) "website"
["beamer_address"]=>
string(47) "key goes here"
["visibility"]=>
string(3) "pub"
["stats"]=>
array(16) {
["member_count"]=>
int(2128)
["unsubscribe_count"]=>
int(20)
["cleaned_count"]=>
int(57)
["member_count_since_send"]=>
int(1)
["unsubscribe_count_since_send"]=>
int(9)
["cleaned_count_since_send"]=>
int(29)
["campaign_count"]=>
int(2)
["grouping_count"]=>
int(1)
["group_count"]=>
int(4)
["merge_var_count"]=>
int(6)
["avg_sub_rate"]=>
int(17)
["avg_unsub_rate"]=>
int(3)
["target_sub_rate"]=>
int(17)
["open_rate"]=>
float(38.672399892502)
["click_rate"]=>
float(14.932747735383)
["date_last_campaign"]=>
string(19) "2014-11-07 19:14:30"
}
["modules"]=>
array(0) {
}
}
}
["errors"]=>
array(0) {
}
}
Here is the PHP that I have so far but I'm unable to get just the value "int(2128)"
foreach($result as $row){
foreach($row[0] as $row1){
echo '<pre>';
var_dump($row1['member_count']);
echo '</pre>';
}
}
The Results I get:
string(1) "6"
NULL
string(1) "F"
string(1) "2"
NULL
NULL
string(1) "B"
string(1) "d"
string(0) ""
string(1) "e"
NULL
string(1) "h"
string(1) "h"
string(1) "u"
string(1) "p"
int(2128)
NULL
I greatly appreciate any help to narrow the results down to just the value "int(2128)".
Thanks.
If you only want the value 2128 use this:
$array['data'][0]['stats']['member_count']
($array is the name of your multidimensional array)

Why can't I numerically sort a nested array?

I have a 2D array that looks like this:
array(2) {
[45]=>
array(5) {
[0]=>
int(2)
[1]=>
int(5)
[2]=>
int(1)
[3]=>
int(3)
[4]=>
int(4)
}
[42]=>
array(5) {
[0]=>
int(5)
[1]=>
int(4)
[2]=>
int(3)
[3]=>
int(2)
[4]=>
int(1)
}
}
The key values of the outer array are numerical, but do not start at 0, and are not sequential. I want to sort the outer array by ascending keys, and the inner arrays by ascending values, so I try this:
ksort($arr);
foreach ($arr as $a) {
sort($a);
}
var_dump($arr);
Which sorts the outer array as expected, but doesn't seem to touch the inner arrays at all:
array(2) {
[42]=>
array(5) {
[0]=>
int(5)
[1]=>
int(4)
[2]=>
int(3)
[3]=>
int(2)
[4]=>
int(1)
}
[45]=>
array(5) {
[0]=>
int(2)
[1]=>
int(5)
[2]=>
int(1)
[3]=>
int(3)
[4]=>
int(4)
}
}
Why is this, and how can I achieve what I want? I think it's something to do with the array being nested, because the following works as expected:
$test = array(5,2,3,1,4);
sort($test);
var_dump($test);
array(5) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
[3]=>
int(4)
[4]=>
int(5)
}
foreach iterates over a copy of the array. If you want to modify the actual values, you have to reference them:
// v
foreach ($arr as &$a) {
sort($a);
}
unset($a);
From the documentation:
As of PHP 5, you can easily modify array's elements by preceding $value with &. This will assign reference instead of copying the value.

Bug or hack? $GLOBALS

$GLOBALS["items"] = array('one', 'two', 'three', 'four', 'five' ,'six', 'seven');
$alter = &$GLOBALS["items"]; // Comment this line
foreach($GLOBALS["items"] as $item) {
echo get_item_id();
}
function get_item_id(){
var_dump(key($GLOBALS["items"]));
}
Check output of this code, with commented and uncommented second line.
My result(PHP 5.3.0).
With second line
int(1) int(2) int(3) int(4) int(5) int(6) NULL
Without second line:
int(1) int(1) int(1) int(1) int(1) int(1) int(1)
Why so strange result?
Here is a possible explanation:
We know that foreach always loops over a copy of the array if it is not referenced:
Unless the array is referenced, foreach operates on a copy of the specified array and not the array itself. foreach has some side effects on the array pointer.
That means that the internal pointer of the original array is not changed and key() will always return the same value (as we can see when we comment out the line). And indeed if we do a var_dump($GLOBALS), we get:
["items"]=>
array(7) {
[0]=>
string(3) "one"
[1]=>
string(3) "two"
[2]=>
string(5) "three"
[3]=>
string(4) "four"
[4]=>
string(4) "five"
[5]=>
string(3) "six"
[6]=>
string(5) "seven"
}
(no reference)
But as soon as we generate a reference to the array (with $alter), $GLOBALS['items'] becomes a reference too, because both entries have to point to the same array:
["items"]=>
&array(7) {
[0]=>
string(3) "one"
[1]=>
string(3) "two"
[2]=>
string(5) "three"
[3]=>
string(4) "four"
[4]=>
string(4) "five"
[5]=>
string(3) "six"
[6]=>
string(5) "seven"
}
["alter"]=>
&array(7) {
[0]=>
string(3) "one"
[1]=>
string(3) "two"
[2]=>
string(5) "three"
[3]=>
string(4) "four"
[4]=>
string(4) "five"
[5]=>
string(3) "six"
[6]=>
string(5) "seven"
}
Hence, the foreach loop does iterate over the original array and changes the internal pointer, which affects key().
To sum up: It is a problem with references, not with $GLOBALS.

Categories