php list() undefined behaviour - php

I came across this line:
list($diff, $current, $concurrent) = $diff;
Documentation states that this should result in undefined behaviour. What are the possible variants of this behaviour? Variable $diff is array, containing 3 elements with variable content.
This line is part of application that contains a bug and author of this line is unavailable. Though I am almost sure that it is not what I am looking for, it would be nice to be 100% sure.
I am using PHP 5.6.25 as FPM/FastCGI.
Thanks in advance.

As the documentation for list() also states:
In PHP 5, list() assigns the values starting with the right-most parameter. In PHP 7, list() starts with the left-most parameter.
In other words: This line might work as intended in PHP 5, because the variable $diff that appears on both sides is the last variable to get assigned. However, in PHP 7 the $diff variable gets assigned first, so $diff has already changed by the time the assignments for $current and $concurrent are done.
In general I think the hint about undefined behaviour relates to the fact that you cannot rely on certain assignments to yield the expected results, if a variable appears on both sides of the = sign. A workaround for the issue could look like this:
list($temp, $current, $concurrent) = $diff;
$diff = $temp;
unset($temp);
This way you avoid the undefined behaviour.

Related

Does PHP 7 have an equivalent of array_value_last()?

This is genuine question (but also code golf for PHP devs).
I know array_key_last() exists in PHP.
I would like to use array_value_last() on explode('/', __FILE__) but... array_value_last() doesn't exist.
I am working inside a constrained environment (an included file which contains and runs a function, before returning data) and I have tried both:
array_pop(explode('/', __FILE__))
end(explode('/', __FILE__))
and neither of these work. (I don't know why they are not working).
N.B. The sole purpose of the environment is to return a variable (either an array or a string). In this environment array_pop(explode('/', __FILE__)) and end(explode('/', __FILE__)) both result in a browser error: Content Encoding Error An error occurred during a connection to example.com. Please contact the website owners to inform them of this problem.
Statements such asechoand other executing statements produce the same Encoding Error.
Here is my current code, which is working:
$My_Filename = explode('/', __FILE__)[(count(explode('/', __FILE__)) - 1)];
Is there a shorthand in PHP 7 to get the last value of an array?
You could do it with end
$latestValue = end($array); // Returns latest value in $array, setting the internal pointer to the last element
reset($array); // Don't forget to reset the pointer!!!
Ok... if it doesn't work (that's very strange dude, big problem xD) you could try something like:
$aux = array_values($array); //No need to use this if it isn't an associative array
$size = count($aux);
$latestValue = $array[$size-1]; //Be careful with empty arrays
You can use basename(__FILE__); to get last part of the path. In general don't strive for this kind of one-liners - you won't gain anything in terms of performance, but loose code readability.
Both array_pop() and end() functions with expressions will give you a notice, because they will also try to modify variable (passed by reference) in current scope, and the message you get is coming from server or error handler (hidden details in production environment) - default interpreter display (better for dev) would give you: <b>Notice</b>: Only variables should be passed by reference in....
Here's a possible candidate for the code golf:
Original: explode('/', __FILE__)[(count(explode('/', __FILE__)) - 1)] // 59 characters
Alternative: array_reverse(explode('/', __FILE__))[0] // 40 characters
That represents a reduction of nearly 33%.
I'm curious to know if there might be an even better shorthand.

PHP: list() giving unused local variable error in PhpStorm

I am using the answer found at https://stackoverflow.com/a/25749660 in order to sort the $_SERVER['HTTP_ACCEPT_LANGUAGE'] array by the most preferred language.
In that answer (which is working great by the way), one line is:
list($a, $b) = explode('-', $match[1]) + array('', '');
Within PhpStorm, I get the following error for that line:
"Unused local variable $b: The value of the variable is overwritten immediately".
I'm a little confused as to what this line is doing exactly, so I don't know if I should just keep it the same, or if I should modify it to:
list($a) = explode('-', $match[1]) + array('', '');
... which also seems to be working fine.
Should it be changed?
The wording is confusing because it's a full explanation for an inspection that covers two situations and the initial tooltip only displays the first line, which happens to describe the other situation. If you hit Ctl+F1 you can read the complete text, which kind of makes more sense (emphasis mine):
Unused local variable 'b'. The value of the variable is overwritten
immediately. less... (Ctrl+F1)
Inspection info: A variable is considered unused in the following
cases:
The value of the variable is not used anywhere or is overwritten immediately.
The reference stored in the variable is not used anywhere or is overwritten immediately.
That's exactly what happens here:
list($a, $b) = …
$a is used later but $b is not. Since $b is never used, this works as well:
list($a) = explode('-', $match[1]) + array('', '');
(Remember these inspections are hints to prevent potential bugs, not necessarily errors.)
You can't join arrays with the arithmetic operator +. Basically you are telling PHP to convert the arrays to scalar types and then sum them, which yields you a number (probably arrays evaluate to 1 if it has elements and 0 if it doesn't).
The result is that effectively you are doing something like:
list($a, $b) = 2;
And the conclusion PHP reaches is that you haven't specified enough elements to define all the variables in the list.
To join two arrays together, use array_merge().
list($a, $b) = array_merge(explode('-', $match[1]), array('', ''));

PHP $$var['index'] = something; fails, although I thought it would work just like $$var = something; does

I was trying to use the $$ syntax in PHP for accessing arrays where we can put name of a variable inside another variable and access that variable.
I have used this syntax many times before in different ways, but to my surprise this didn't work for me and lost lot of time over it.
Here is sample code to replicate my problem:
$test=array(
'a'=>array(array(1,2,3),array(4,5,6),array(7,8,9))
);
$var = 'test';
var_dump($$var);
var_dump($$var['a']);
The line var_dump($$var) works as expected, but I'm getting a Warning: Illegal string offset 'a' at line var_dump($$var['a']); and the var_dump prints just null
Why doesn't this work? What am I doing wrong here?
Is there any work around if the syntax is not supported for arrays?
Your $$var['a'] is equivalent to ${$var['a']}. Not ${$var}['a']. The latter being the workaround syntax you are looking for.
Quoting the PHP Manual on Variable Variables:
In order to use variable variables with arrays, you have to resolve an ambiguity problem. That is, if you write $$a[1] then the parser needs to know if you meant to use $a[1] as a variable, or if you wanted $$a as the variable and then the [1] index from that variable. The syntax for resolving this ambiguity is: ${$a[1]} for the first case and ${$a}[1] for the second.
See http://codepad.org/lR7QJygX

Anyone ever used PHP's (unset) casting?

I just noticed PHP has an type casting to (unset), and I'm wondering what it could possibly be used for. It doesn't even really unset the variable, it just casts it to NULL, which means that (unset)$anything should be exactly the same as simply writing NULL.
# Really unsetting the variable results in a notice when accessing it
nadav#shesek:~$ php -r '$foo = 123; unset($foo); echo $foo;'
PHP Notice: Undefined variable: foo in Command line code on line 1
PHP Stack trace:
PHP 1. {main}() Command line code:0
# (unset) just set it to NULL, and it doesn't result in a notice
nadav#shesek:~$ php -r '$foo = 123; $foo=(unset)$foo; echo $foo;'
Anyone ever used it for anything? I can't think of any possible usage for it...
Added:
Main idea of question is:
What is reason to use (unset)$smth instead of just NULL?
As far as I can tell, there's really no point to using
$x = (unset)$y;
over
$x = NULL;
The (unset)$y always evaluates to null, and unlike calling unset($y), the cast doesn't affect $y at all.
The only difference is that using the cast will still generate an "undefined variable" notice if $y is not defined.
There's a PHP bug about a related issue. The bug is actually about a (in my mind) misleading passage elsewhere in the documentation which says:
Casting a variable to null will remove the variable and unset its value.
And that clearly isn't the case.
I’d guess (knowing PHP and it’s notaribly... interesting choices for different things, I may be completely wrong) that it is so that the value does not need setting to a var. For exact reason to use it for a code, I can’t think of an example, but something like this:
$foo = bar((unset) baz());
There you want or need to have null as argument for bar and still needs to call baz() too. Syntax of function has changed and someone did a duck tape fix, like what seems to be hot with PHP.
So I’d say: no reason to use it in well-thought architecture; might be used for solutions that are so obscure that I’d vote against them in first place.
As of PHP 8.0.X, (unset) casting is now removed and cannot be used.
For example it can be used like this
function fallback()
{
// some stuff here
return 'zoo';
}
var_dump(false ? 'foo' : fallback()); // zoo
var_dump(false ? 'foo' : (unset) fallback()); // null
Even if fallback() returns "zoo" (unset) will clear that value.

Why does PHP's call_user_func() function not support passing by reference?

Why don't the function handling functions like call_user_func() support passing parameters by reference?
The docs say terse things like "Note that the parameters for call_user_func() are not passed by reference." I assume the PHP devs had some kind of reason for disabling that capability in this case.
Were they facing a technical limitation? Was it a language design choice? How did this come about?
EDIT:
In order to clarify this, here is an example.
<?php
function more(&$var){ $var++; }
$count = 0;
print "The count is $count.\n";
more($count);
print "The count is $count.\n";
call_user_func('more', $count);
print "The count is $count.\n";
// Output:
// The count is 0.
// The count is 1.
// The count is 1.
This is functioning normally; call_user_func does not pass $count by reference, even though more() declared it as a referenced variable. The call_user_func documentation clearly says that this is the way it's supposed to work.
I am well aware that I can get the effect I need by using call_user_func_array('more', array(&$count)).
The question is: why was call_user_func designed to work this way? The passing by reference documentation says that "Function definitions alone are enough to correctly pass the argument by reference." The behavior of call_user_func is an exception to that. Why?
The answer is embedded deep down in the way references work in PHP's model - not necessarily the implementation, because that can vary a lot, particularly in the 5.x versions. I'm sure you've heard the lines, they're not like C pointers, or C++ references, etc etc... Basically when a variable is assigned or bound, it can happen in two ways - either by value (in which case the new variable is bound to a new 'box' containing a copy of the old value), or by reference (in which case the new variable is bound to the same value box as the old value). This is true whether we're talking about variables, or function arguments, or cells in arrays.
Things start to get a bit hairy when you start passing references into functions - obviously the intent is to be able to modify the original variables. Quite some time ago, call-time pass-by-reference (the ability to pass a reference into a function that wasn't expecting one) got deprecated, because a function that wasn't aware it was dealing with a reference might 'accidentally' modify the input. Taking it to another level, if that function calls a second function, that itself wasn't expecting a reference... then everything ends up getting disconnected. It might work, but it's not guaranteed, and may break in some PHP version.
This is where call_user_func() comes in. Suppose you pass a reference into it (and get the associated the call-time pass-by-reference warning). Then your reference gets bound to a new variable - the parameters of call_user_func() itself. Then when your target function is called, its parameters are not bound where you expect. They're not bound to the original parameters at all. They're bound to the local variables that are in the call_user_func() declaration. call_user_func_array() requires caution too. Putting a reference in an array cell could be trouble - since PHP passes that array with "copy-on-write" semantics, you can't be sure if the array won't get modified underneath you, and the copy won't get detached from the original reference.
The most insightful explanation I've seen (which helped me get my head around references) was in a comment on the PHP 'passing by reference' manual:
http://ca.php.net/manual/en/language.references.pass.php#99549
Basically the logic goes like this. How would you write your own version of call_user_func() ? - and then explain how that breaks with references, and how it fails when you avoid call-time pass-by-reference. In other words, the right way to call functions (specify the value, and let PHP decide from the function declaration whether to pass value or reference) isn't going to work when you use call_user_func() - you're calling two functions deep, the first by value, and the second by reference to the values in the first.
Get your head around this, and you'll have a much deeper understanding of PHP references (and a much greater motivation to steer clear if you can).
See this:
http://hakre.wordpress.com/2011/03/09/call_user_func_array-php-5-3-and-passing-by-reference/
Is it possible to pass parameters by reference using call_user_func_array()?
http://bugs.php.net/bug.php?id=17309&edit=1
Passing references in an array works correctly.
Updated Answer:
You can use:
call_user_func('more', &$count)
to achieve the same effect as:
call_user_func_array('more', array(&$count))
For this reason I believe (unfoundedly) that call_user_func is just a compiler time short cut. (i.e. it gets replaced with the later at compile time)
To give my view on you actual question "Why was call_user_func designed to work this way?":
It probably falls under the same lines as "Why is some methods strstr and other str_replace?, why is array functions haystack, needle and string functions needle, haystack?
Its because PHP was designed, by many different people, over a long period of time, and with no strict standards in place at the time.
Original Answer:
You must make sure you set the variable inside the array to a reference as well.
Try this and take note of the array(&$t) part:
function test(&$t) {
$t++;
echo '$t is '.$t.' inside function'.PHP_EOL;
}
$t = 0;
echo '$t is '.$t.' in global scope'.PHP_EOL;
test($t);
$t++;
echo '$t is '.$t.' in global scope'.PHP_EOL;
call_user_func_array('test', array(&$t));
$t++;
echo '$t is '.$t.' in global scope'.PHP_EOL;
Should output:
$t is 0 in global scope
$t is 1 inside function
$t is 2 in global scope
$t is 3 inside function
$t is 4 in global scope
Another possible way - the by-reference syntax stays the 'right' way:
$data = 'some data';
$func = 'more';
$func($more);
function more(&$data) {
// Do something with $data here...
}

Categories