foreach loop scope with multidimensional arrays - php

Can someone explain the output of this code?
Why is it "fb" instead of "100100"?
$items = array();
$items[] = "foo";
$items[] = "bar";
foreach($items as $item) {
$item['points'] = 100;
}
foreach($items as $item) {
echo $item['points']; //output: "fb"
}

You loop though the $items array, which has two elements.
First: foo and second: bar. E.g.
Array (
[0] => foo
[1] => bar
)
Now you access them like this:
echo $item['points'];
PHP will convert points which is a string into an integer, as you can see from the manual warning:
Warning: [...] Non-integer types are converted to integer. [...]
Which in your case will be 0.
And so you access the two values (strings) as array:
string: f o o
index: 0 1 2 //$index["points"] -> $index[0]
string: b a r
index: 0 1 2 //$index["points"] -> $index[0]
So you print the first character of both strings (e.g. foo and bar), which are:
fb
EDIT:
Also worth to note here is, that PHP only silently converts it with PHP <5.4 from newer version you will get a warning, as from the manual:
As of PHP 5.4 string offsets have to either be integers or integer-like strings, otherwise a warning will be thrown. Previously an offset like "foo" was silently cast to 0.
Which in your case with PHP >=5.4 you would get:
Warning: Illegal string offset 'points' ...

I found this question intriguing.
I had my own walk-through and here is the result.
$items is defined as follows.
$items = [
0 => "foo",
1 => "bar"
];
Then, goes into the foreach loop.
foreach($items as $item) {
$item['points'] = 100;
}
At the beginning, $item contains a string "foo". The [] syntax is dominantly used for associative arrays, so it tricks us that $item might be an array, which is not the case. A less well-known usage of the [] is to get/set a single character in a string via [int] or {int} expression, as #Rizier123 has noted in his answer. For example, a "string"[0] gives "s". So, the following code
$item['points'] = 100;
is virtually similar to
"foo"['points'] = 100;
Now, a non-integer value given as a character position of a string, raises a PHP warning, and the position (here 'points') will be force-converted to an integer.
// Converting a string to integer:
echo intval('points'); // gives 0
As a result, the "foo"['points']" statement becomes "foo"[0], so
"foo"[0] = 100;
Now, the assignment part. The [] syntax operates on a single character. The numeric 100 is first converted to a string "100" and then only the first character is taken out for the assignment operation(=). The expression is now similar to
"foo"[0] = "1"; // result: "1oo"
To make things a bit twisted, the modified value of $item( which is "1oo") is not preserved. It's because the $item is not a reference. See https://stackoverflow.com/a/9920684/760211 for more information.
So, all the previous operations are negligible in terms of the end result. The $items are intact in the original state.
Now, in the last loop, we can see that the $item['point'] statement tries to read a character out of a string, in an erroneous way.
foreach($items as $item) {
echo $item['points']; //output: "fb"
}
echo "foo"[0]; // "f"
echo "boo"[0]; // "b"

You're not actually modifying the array by doing $items as $item. $item is its own variable, so it would make sense that you get the correct output when printing within that loop.

Related

Access array element indexed by numerical string

I have encountered something odd.
I have a php array, indexed with numerical keys.
However it appears impossible to access any of the elements because php automatically treats numerical strings as integers, causing an illegal offset notice.
Under normal circumstances its imposable to create a php array with numerical string indexes, but it can happen with type casting.
To reproduce:
$object = new stdClass();
$object->{'1'} = 'one';
$array = (array) $object;
var_dump($array);
/* produces
array(1) {
["1"]=>
string(3) "one"
}
*/
//none of the following will work
$key = '1';
echo $array[1], $array['1'], $array["1"], $array[(string)1], $array[$key];
Is this just an edge case bug? I only encountered the problem when attempting to improve my answer for another SO question
Live code example: http://codepad.viper-7.com/dFSlH1
Unbelievable but this is normal behavior in php, it was considered as a bug (link) in the year 2008.
But they just pointed out to the manual for the cast with (array):
If an object is converted to an array, the result is an array whose
elements are the object's properties. The keys are the member variable
names, with a few notable exceptions: integer properties are
unaccessible;
You can use get_object_vars() instead:
$object = new stdClass();
$object->{'1'} = 'one';
$array = get_object_vars( $object );
$key = '1';
echo $array[1]."<br>";
echo $array['1']."<br>";
echo $array["1"]."<br>";
echo $array[(string)1]."<br>";
echo $array[$key]."<br>";
Doesn't explain why this happens, but is a solution to avoid the cast problem.
Off topic but I thought maybe it is interesting. Found this in the manual.
To avoid these kind of problems, always use an integer OR a string as index, don't mix it up and don't use integers in a string.
Example of mixed array:
$array = array(
1 => "a",
"1" => "b",//overrides 1
1.5 => "c",//overrides "1"
true => "d",//overrides 1.5
);
var_dump($array);
You can use
$vars = get_object_vars($object);
echo $vars[1];
String keys containing valid integer values would be cast to integer keys automatically in “normal” array creation – but it seems casting from object to array doesn’t apply the same logic.
It can be fixed however, by using
$array = array_combine(array_keys($array), array_values($array));
after your line that creates the array from the object. http://codepad.viper-7.com/v5rGJa
Although, same as Dave already said in his comment, using get_object_vars looks like a “cleaner” solution to me as well.
foreach ($array as $key => $value){
var_dump($key);
var_dump($value);
}
shows
string(1) "1"
string(3) "one"
But echo $array['"1"']; gives
E_NOTICE : type 8 -- Undefined index: "1" -- at line 8
That's strange!

Unserialsed Array giving the weirdest results

I've a really odd issue today. I've got a serialised array that looks like this:
a:4:{i:0;s:7:"Perfect";i:1;s:10:"jawel hoor";i:2;s:14:"Ach jawohl joh";i:3;s:2:"Ja";}
Then after I execute this code:
include '../../database/connect.php';
Class Calc {
function getPrice($id) {
$Db = new Db();
$sth = $Db->dbh->prepare("SELECT * FROM orders WHERE id = :id");
$sth->execute(array(':id'=>$id));
$are_you_serial = $sth->fetchAll();
foreach($are_you_serial as $row) {
$serialised = $row['reply_array'];
$product_id = $row['product_id'];
$user_id = $row['user_id'];
}
$array = unserialize($serialised);
foreach($array as $row) {
echo $row[1];
}
}
}
$calc = new Calc();
$calc->getPrice(11);
eaca comes out. When I call row 0 PjAJ comes out.
When I call row 2 this seems to be the error:
rwh
Notice: Uninitialized string offset: 2 in index.php on line 29
This is what the array looks like if I just print_r the $array:
Array
(
[0] => Perfect
[1] => jawel hoor
[2] => Ach jawohl joh
[3] => Ja
)
I can also call $array[0] and it'll show the right things but once I put it in the foreach it doesn't work anymore.
Your first foreach keeps reassigning the variables, so $serialised (should be $serialized BTW) will only ever hold the value of the last row by the time you're actually calling unserialize. I'll add what I think you want bellow, but for now, some more details on why you're getting the "weird" or unexpected output:
A little info on how to read the PHP serialized format:
a:4:{i:0;s:7:"Perfect";i:1;s:10:"jawel hoor";...
a:4:{: What follows is an array, containing 4 key-value pairs
i:0;: An integer, value 0. Because this is part of an array, all odd values are keys, even values are values, so the first index of the array is 0
s:7:"Perfect";: A string, 7 chars long, and the string itself is "Perfect" (without the quotes)
Same applies to objects that are serialized:
O:8:"stdClass":2:{s:3:"bar";i:123;s:3:"foo";i:456;}
o:8:"stdClass":2:{: An object, the class name of which is 8 chars long (stdClass), with 2 properties set
s:3:"bar";i:123;: The property name is a 3-char long string ("bar"), its value is an int (123)
s:3:"foo";i:456;: 3-char long property ("foo") with value 456
}: End of serialized object
Knowing this, you should be able to work out that what you're doing with the data after you've unserialized, ie this:
foreach ($array as $row) {
echo $row[1];
}
Is just all shades of wrong, the value of $row will be a string, and getting an index/offset of a string is possible (preferably using the {} notation, as in $row{1}), but it'll return the character at offset n, where n is the digit/key between brackets. Strings are, like arrays, zero-indexed BTW, so $string = 'foo'; echo $string{0}; will echo "f", and echo $string{1}; will echo "o".
What you want, then is to write:
foreach ($array as $row) {
echo $row;
}
or shorter:
echo implode(PHP_EOL, $array);//PHP_EOL to add linebreaks between the strings
Like I said at the beginning, you're only really processing the very last serialized value, what you actually want is probably something more like this:
$unserialized = [];
foreach ($sth->fetchAll() as $row) {
//add unserialized values to an array
$unserialized[] = unserialize($row['reply_array']);
}
//$unserialized is now an array of arrays
foreach ($unserialized as $rowNr => $array) {
echo 'Row #', $rowNr+1, ': ', PHP_EOL,
implode(PHP_EOL, $array);
}
That ought to get you started...

Indexing into an array returned by a variable variable in PHP

I'm just wondering about how variable variables that point to Arrays handle. Take the code below:
$a = new stdClass();
$a->b = array('start' => 2);
$c = 'b';
$d = 'start';
print $a->$c; // => Array
print $a->$c['start']; // => Array
print $a->$c[0]; // => Array
print $a->$c[0][0]; //=> PHP Fatal error: Cannot use string offset as an array in php shell code on line 1
The first print I expect, the second one I don't, or the 3rd. The 4th is expected after realizing that the evaluation of $a->$c is apparently a string. But then why does this work:
$t = $a->$c;
print $t['start']; //=> 2
I'm asking more because I'm curious than I need to know how to nicely do
$e = 'start';
$a->$c[$e]
Anyone know why I can't index directly into the array returned by the variable variable usage?
It comes down to order of operations and how PHP type juggles. Hint: var_dump is your friend, echo always casts to a string so it is the worst operation for analyzing variable values, esp in debug settings.
Consider the following:
var_dump($a->$c); // array (size=1) / 'start' => int 2
var_dump($a->$c['start']); // array (size=1) / 'start' => int 2
var_dump($a->b['start']); // int 2
var_dump($c['start']); // string 'b' (length=1)
The key here is how PHP interprets the part of $c['start'] (include $c[0] here as well). $c is the string 'b', and when attempting to get the 'start' index of string 'b' this simply returns the first character in the string, which happens to simply be the only letter (b) in the string. You can test this out by using $c = 'bcdefg'; - it'll yield the same result (in this specific case). Also, $c['bogus'] will yield the exact same thing as $c['start']; food for thought, and make sure you do the required reading I linked to.
So with this in mind (knowing that $c['start'] reluctantly returns 'b'), the expression $a->$c['start'] is interpreted at $a->b. That is, the order is $a->($c['start']) and not ($a->$c)['start'].
Unfortunately you can't use () nor {} to steer the parser (PHP SCREAMs), so you won't be able to accomplish what you want in a single line. The following will work:
$e = 'start';
$interim = $a->$c;
echo $interim[$e];
Alternatively, you can cast your arrays as objects (if you have the luxury):
$a->$c = (object) $a->$c; // mutate
var_dump($a->$c->$e);
$interim = (object) $a->$c; // clone
var_dump($interim->$e);
...by the way, referring back up to $c['start'] and $c[0], in regards to $c[0][0] you simply can't do this because $c[0] is the character b in string 'b'; when access the character/byte b it will not have a property of [0].
$a->$c[0]; is actually equal to: array('start' => 0)
so when you did:
print $a->$c[0][0];
You are trying to load an array element from $a->$c[0] at index 0 which does not exists.
however, this will work:
print $a->$c[0]['start'];

json in perl deserialization length count doesn't work

How do iterate through a deserialized json string? I can't get the right number of values right now it doesn't count right.
my $list = request("http://localhost/getjson.php");
my $deserialize = from_json( $list );
print Dumper($deserialize);
$VAR1 = [
'ab',
'cc',
'de',
'aer',
'ffe',
'cer',
'dad',
'efef',
'afaf',
'ege',
'grsc',
'cegg',
'cegg',
'cegg/aaa.html',
'eggt',
'ttt'
];
print length($deserialize);
13 ?? it should say 16
You are getting back an array reference not an array. You need to dereference the value.
my #array = #$deserialize; # or #{ $deserialize }
print scalar #array;
Moreover, if you want to iterate over the array you can just use for
for (#$deserialize) {
# do stuff
You are actually working with reference to the results. Since JSON can contain all sorts of different results, decode_json won't return a list specifically.
So you need to dereference the variable that you have: $deserialize
Additionally, you don't really want to be using the length function. If you print the integer value (or scalar value) of an array, it will return it's size.
So here's what you want:
my $list = request("http://localhost/getjson.php");
my $deserialize = from_json( $list );
print scalar (#{$deserialize});
That will print the size of the array.
If you want to just start by working with an array you can do:
my $list = request("http://localhost/getjson.php");
my $deserialize = from_json( $list );
my #json_array = #{$deserialize});
print scalar (#json_array);
From perldoc -f length:
This function cannot be used on an entire array or hash to find
out how many elements these have. For that, use "scalar
#array" and "scalar keys %hash", respectively.
You cannot use length to find out the size of an array. To do that, use the advice above.
Your bug gives you a false value because you are taking the length of the array reference, which in string context will be something like ARRAY(0x22d0a88), which in your case seemed to be 13 characters long. E.g. the equivalent of:
print length "ARRAY(0x22d0a88)";
As a curious side note, if you would do length(#array), it will actually return the length of the length of the array. E.g. an array of size 16 would return 2, because the string "16" is two characters long.

Which is proper form?

PHP.
$a['0']=1;
$a[0]=2;
Which is proper form?
In the first example you use a string to index the array which will be a hashtable "under the hood" which is slower. To access the value a "number" is computed from the string to locate the value you stored. This calculation takes time.
The second example is an array based on numbers which is faster. Arrays that use numbers will index the array according to that number. 0 is index 0; 1 is index 1. That is a very efficient way of accessing an array. No complex calculations are needed. The index is just an offset from the start of the array to access the value.
If you only use numbers, then you should use numbers, not strings. It's not a question of form, it's a question of how PHP will optimize your code. Numbers are faster.
However the speed differences are negligible when dealing with small sizes (arrays storing less than <10,000 elements; Thanks Paolo ;)
In the first you would have an array item:
Key: 0
Index: 0
In the second example, you have only an index set.
Index: 0
$arr = array();
$arr['Hello'] = 'World';
$arr['YoYo'] = 'Whazzap';
$arr[2] = 'No key'; // Index 2
The "funny" thing is, you will get exactly the same result.
PHP (for whatever reason) tests whether a string used as array index contains only digits. If it does the string is converted to int or double.
<?php
$x=array(); $x['0'] = 'foo';
var_dump($x);
$x=array(); $x[0] = 'foo';
var_dump($x);
For both arrays you get [0] => foo, not ["0"] => foo.
Or another test:<?php
$x = array();
$x[0] = 'a';
$x['1'] = 'b';
$x['01'] = 'c';
$x['foo'] = 'd';
foreach( $x as $k=>$v ) {
echo $k, ' ', gettype($k), "\n";
}0 integer
1 integer
01 string
foo string
If you still don't believe it take a look at #define HANDLE_NUMERIC(key, length, func) in zend_hash.h and when and where it is used.
You think that's weird? Pick a number and get in line...
If you plan to increment your keys use the second option. The first one is an associative array which contains the string "0" as the key.
They are both "proper" but have the different side effects as noted by others.
One other thing I'd point out, if you are just pushing items on to an array, you might prefer this syntax:
$a = array();
$a[] = 1;
$a[] = 2;
// now $a[0] is 1 and $a[1] is 2.
they are both good, they will both work.
the difference is that on the first, you set the value 1 to a key called '0'
on the second example, you set the value 2 on the first element in the array.
do not mix them up accidentally ;)

Categories