Strange PHP loop scope what am i missing? [duplicate] - php

I'm seeing some confusing behavior related to reference/assignment in PHP...
private function doSomething($things)
{
foreach ($things as $thing) {
echo $thing->property; // 'foobar'
$copyThing = $thing;
unset($copyThing->property);
echo $thing->property; // undefined
I expect this behavior when passing variables by reference (&$thing) but I'm not trying to do that here and it seems to be happening anyway. What am I missing?

Just explaining my comment:
objects in foreach loops are always passed by reference
When you use a foreach loop for an array of objects the variable that you are using inside the loop is a pointer to that object so it works as a reference, any change on the object inside the loop is a change on the object outside.
This is because:
objects are always passed by reference (#user3137702 quote)
Detailed and official explanation here.
When you copy and unset your variable:
$copyThing = $thing;
unset($copyThing->property);
you are creating another pointer and unseting it, so the original value is a gone. As a matter of fact, since the foreach loop also uses a pointer the $things array is also affected.
check this ideone (notice the vardump [where the 'a' property is gone], as the output is the same as you got)
I do not know in which version it changed, if ever, as it seems like default object/pointer behavior
As a workaround (some ideas):
Copy your initial array
Use clone: $x = clone($obj); (As long as the default copy constructor works for your objects)

Related

PHP: strange reference variable

As I understand, when you pass a variable to a function, and if you don't use reference sign (&) , it means any changes inside your function will not affect to your variable outside the function. In other words, it means the compiler will make a copy of the outside variable to use inside function, doesn't it?
But when I run these testing code, it does not happen like that.
Can anyone explain me what I miss here? Thank you
My test code: the expected result should be 3, but it becomes 1?
function test($arr2) {
foreach($arr2 as &$item) {
$item = 1;
}
}
$arr = array(2);
foreach($arr as &$item2) {
$item2 = 3;
}
test($arr);
print_r($arr);
This issue has been solved a few times before you've asked this (#1). The issue is due to the fact that:
Reference of a $value and the last array element remain even after the
foreach loop. It is recommended to destroy it by unset().
Reference: PHP foreach()
You need to unset the last $item2 after your foreach:
foreach ($arr as &$item2) {
$item2 = 3;
}
unset($item2);
This is quite interesting, it seems like the behavior of array are the same as objects in php in which new array still holds the copy of the members identifier (which points to the same value as the array it was copied from).
As of PHP 5, an object variable doesn't contain the object itself as value anymore. It only contains an object identifier which allows object accessors to find the actual object. When an object is sent by argument, returned or assigned to another variable, the different variables are not aliases: they hold a copy of the identifier, which points to the same object.
PHP Manual - Objects and references
Even though you are not passing $arr as Reference variable, you are still accessing $arr elements as References in function test(). So anything that changes in function will effect outside function too.
If you are looking to change $arr ( which has been passed as $arr2 in test function ) only in test function, then remove &from $item

Is it safe to add new keys to an array while iterating over it?

For example, is this safe?
foreach($opps_data as $k=>$v) {
$opps_data[$k.'_mixed'] = WXU::MixedCase($v);
}
It seems to work fine. Does that mean PHP makes a copy of the array before it starts looping?
Yes, foreach loop operates on a copy of original array. More info about internal behaviour of foreach can be found in this great blog.
foreach() uses iterators. Array is called then iterator is used for pointing to array which was called.
In this case $opps_data is called only once. Iterator will not reference original array it will be working on copy of $opps_data which was called.

multidimensional arrays with __set() and __get() [duplicate]

Having done a bit of research, I eventually came across the answer to a question I was soon to ask here anyways; How do you work with arrays via the __get and __set magic methods in PHP? Whenever I was trying to set a value using something like $object->foo['bar'] = 42; it seemed to silently discard it.
Anyways, the answer is simple; The __get method simply needs to return by reference. And after tossing an ampersand in front of it, sure enough it works.
My question actually, is why? I can't seem to understand why this is working. How does __get returning by reference affect __set working with multidimensional arrays?
Edit: By the way, running PHP 5.3.1
In PHP when you return a value from a function you can consider it making a copy of that value (unless it's a class). In the case of __get unless you return the actual thing you want to edit, all the changes are made to a copy which is then discarded.
In this particular case, __set is not actually getting called. If you break down what it happening, it should make a bit more sense:
$tmp = $object->__get('foo');
$tmp['bar'] = 42
If __get did not return a reference, then instead of assigning 42 to the 'bar' index of the original object, you're be assigning to the 'bar' index of a copy of the original object.
maybe more clear:
//PHP will try to interpret this:
$object->foo['bar'] = 42
//The PHP interpreter will try to evaluate first
$object->foo
//To do this, it will call
$object->__get('foo')
// and not __get("foo['bar']"). __get() has no idea about ['bar']
//If we have get defined as &__get(), this will return $_data['foo'] element
//by reference.
//This array element has some value, like a string:
$_data['foo'] = 'value';
//Then, after __get returns, the PHP interpreter will add ['bar'] to that
//reference.
$_data['foo']['bar']
//Array element $_data['foo'] becomes an array with one key, 'bar'.
$_data['foo'] = array('bar' => null)
//That key will be assigned the value 42
$_data['foo']['bar'] = 42
//42 will be stored in $_data array because __get() returned a reference in that
//array. If __get() would return the array element by value, PHP would have to
//create a temporary variable for that element (like $tmp). Then we would make
//that variable an array with $tmp['bar'] and assign 42 to that key. As soon
//as php would continue to the next line of code, that $tmp variable would
//not be used any more and it will be garbage collected.

Using __set with arrays solved, but why?

Having done a bit of research, I eventually came across the answer to a question I was soon to ask here anyways; How do you work with arrays via the __get and __set magic methods in PHP? Whenever I was trying to set a value using something like $object->foo['bar'] = 42; it seemed to silently discard it.
Anyways, the answer is simple; The __get method simply needs to return by reference. And after tossing an ampersand in front of it, sure enough it works.
My question actually, is why? I can't seem to understand why this is working. How does __get returning by reference affect __set working with multidimensional arrays?
Edit: By the way, running PHP 5.3.1
In PHP when you return a value from a function you can consider it making a copy of that value (unless it's a class). In the case of __get unless you return the actual thing you want to edit, all the changes are made to a copy which is then discarded.
In this particular case, __set is not actually getting called. If you break down what it happening, it should make a bit more sense:
$tmp = $object->__get('foo');
$tmp['bar'] = 42
If __get did not return a reference, then instead of assigning 42 to the 'bar' index of the original object, you're be assigning to the 'bar' index of a copy of the original object.
maybe more clear:
//PHP will try to interpret this:
$object->foo['bar'] = 42
//The PHP interpreter will try to evaluate first
$object->foo
//To do this, it will call
$object->__get('foo')
// and not __get("foo['bar']"). __get() has no idea about ['bar']
//If we have get defined as &__get(), this will return $_data['foo'] element
//by reference.
//This array element has some value, like a string:
$_data['foo'] = 'value';
//Then, after __get returns, the PHP interpreter will add ['bar'] to that
//reference.
$_data['foo']['bar']
//Array element $_data['foo'] becomes an array with one key, 'bar'.
$_data['foo'] = array('bar' => null)
//That key will be assigned the value 42
$_data['foo']['bar'] = 42
//42 will be stored in $_data array because __get() returned a reference in that
//array. If __get() would return the array element by value, PHP would have to
//create a temporary variable for that element (like $tmp). Then we would make
//that variable an array with $tmp['bar'] and assign 42 to that key. As soon
//as php would continue to the next line of code, that $tmp variable would
//not be used any more and it will be garbage collected.

How to copy an object by value / clone an object in PHP 5

I have this code:
foreach ($this->configObjects as $k=>$object)
{
$configObject=$object;
//Here I will make a lot of changes to $configObject, however
// I want all those changes to be kept only to the local copy of $configObject,
// So the next time this foreach loop is run $this->configObjects array will contain
// a clean slate of configObject objects which don't have any traces of any
// earlier changes that were made to them
}
How can I accomplish this?
well, you simply clone your object.
Use a copy constructor.
Question 1: The for each loop by defualt should make a local copy of the value into your '$k=>$object' - any changes local to that foreach. Putting an '&' would pass it by reference instead of by value. Manual
Question 2: Can you var_dump the object? It make be casting your object in the foreach into an array or string - if the var_dump shows such, you are calling a method on a string.
Either using the clone construct or looping through the object to assign it to the secondary.
You do not have to clone it, because you cannot change the members of an Iterable in a foreach loop. So when you do something like
$configObject->name = "whatever";
it will not have any effect on $this-> configObjects by default.
Quote from the PHP page:
Note: Unless the array is referenced, foreach operates on a copy of the specified array and not the array itself.

Categories