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

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.

Related

Can a POST value ever be an instanceof something in PHP?

I've come across PHP code with the a check which tests if a POST value is an instanceof a class:
if ($_POST['something'] instanceof SomeClass) {
// do something
}
This seems odd to me, because I wouldn't think that the check can ever be true. A POST value is a string after all, and a string isn't an instance of a class.
I tried passing the serialized version of an instance (O:9:"SomeClass":0:{}), but that doesn't work (which makes sense, as it's still a string, not an object).
Am I correct in thinking that this check can never be true? Or am I missing something here?
I think this is not a simple question. I think theoretically "yes".
As a $_POST is a array of variables, and there is no limitation what the elements of a array can be, you can for example create a array of objects.
$array[] = new stdClass;
$array[0]->variable = value;
etc ...
You see the code author is checking if $_POST['something'], that is a element of the array, and that easily can be a object, is a instance of a class.
Now I have not tested it, but theoretically one could put a object of a class in a array and send it via $_POST nicely encoded.

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

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)

Can you get value from an array without getting the array first?

Bear with me, I'm learning.
I often see snippets like the one below:
<?p
$imageArray = get_field('image_field');
$imageAlt = $imageArray['alt'];
$imageURL = $imageArray['url'];
?>
It is pedagogical and clear and organized. But is it necessary to get the entire array before querying the array for values? Can I not define the variable in just a single line? Something like the below (which doesn't work, neither the other variants I have tried):
$imageAlt = get_field('image_field', ['alt']);
$imageURL = get_field('image_field', ['url']);
Yes, you can.
As of PHP 5.4 it is possible to array dereference the result of a function or method call directly. Before it was only possible using a temporary variable. - Source
$imageAlt = get_field('image_field')['alt'];
https://eval.in/548036
The question you are asking can be answered by asking 2 questions:
Is it doable ?
Is it a good idea to do it that way ?
Is it doable ?
Yes! You do not have to store the array in a variable and re-use it later.
For instance, you could do:
$imageAlt = get_field('image_field')['alt'];
Note: This will work in PHP 5.4+ and is called: Array dereferencing.
But that is not the only consideration...
Is it a good idea to do it that way ?
No. It's not a good idea in many cases. The get_field() function, depending on your context, is probably doing a lot of work and, each time you call it, the same work is don multiple times.
Let's say you use the count() function. It will count the number of items in an array. To do that, it must iterate through all items to get the value.
If you use the count() function each time you need to validate number of items in an array, you are doing the task of counting each and every time. If you have 10 items in your array, you probably won't notice. But if you have thousands of items in your array, this may cause a delay problem to compute your code (a.k.a. it will be slow).
That is why you would want to do something like: $count = count($myArray); and use a variable instead of calling the function.
The same applies to your question.
While PHP 5.4+ allows you to directly dereference a function return value like this:
get_field('image_field')['alt']
...in this particular case I would not suggest you do so, since you're using two values from the resulting array. A function call has a certain overhead just in itself, and additionally you don't know what the function does behind the scenes before it returns a result. If you call the function twice, you may incur a ton of unnecessary work, where a single function call would have done just as well.
This is not to mention keeping your code DRY; if you need to change the particulars of the function call, you now need to change it twice...
PHP allows you to play around quite a bit:
function foo(){
return array('foo' => 1, 'bar' => 2);
}
Option 1
echo foo()['foo']; // 1
# Better do this if you plan to reuse the array value.
echo ($tmp = foo())['foo']; // 1
echo $tmp['bar']; // 2
It is not recommended to call a function that returns an array, to specifically fetch 1 key and on the next line doing the same thing.
So it is better to store the result of the function in a variable so you can use it afterwards.
Option 2
list($foo, $bar) = array_values(foo());
#foo is the first element of the array, and sets in $foo.
#bar is the second element, and will be set in $bar.
#This behavior is in PHP 7, previously it was ordered from right to left.
echo $foo, $bar; // 12
Option 3
extract(foo()); // Creates variable from the array keys.
echo $foo, $bar;
extract(get_field('image_field'));
echo $alt, $url;
Find more information on the list constructor and extract function.

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

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.

Categories