This question already has an answer here:
Assigning variables by reference and ternary operator?
(1 answer)
Closed 1 year ago.
I have two arrays and I want to append to one or another based on a certain condition. The problem is that the array_push function should be called inside cases of a switch case inside a foreach loop, so using if-else means 20 extra lines of code. I thought I could do this using the Ternary Operator, but for some reason it doesn't work.
$arr1 = ['1', '2'];
$arr2 = ['a', 'b'];
$condition = False;
array_push($condition ? $arr1 : $arr2, 'new');
which produces this error:
array_push(): Argument #1 ($array) cannot be passed by reference
I thought $arr1 and $arr2 are references, and therefore you should be able to pass them, but something is not as it should be. At least that's how it behaves in C.
Have a look at the function signature for array_push():
array_push(array &$array, mixed ...$values): int
Notice the & in the first argument. That indicates that a reference must be passed.
Only variables can be passed by reference. A ternary expression is, well, an expression.
If you want to do this, you will need to first establish the reference, then pass it. And there's no easy way to shortcut that.
if( $condition) $ref = &$arr1; else $ref = &$arr2;
array_push($ref, 'new');
$ref = null; // clear reference
Related
This question already has answers here:
PHP in_array() / array_search() odd behaviour
(2 answers)
Closed 4 months ago.
I have an array of lower case hex strings. For some reason, in_array is finding matches where none exist:
$x = '000e286592';
$y = '0e06452425';
$testarray = [];
$testarray[] = $x;
if (in_array($y, $testarray)) {
echo "Array element ".$y." already exists in text array ";
}
print_r($testarray);exit();
The in_array comes back as true, as if '0e06452425' is already in the array. Which it is not. Why does this happen and how do I stop it from happening?
Odd. This has something to do with the way that PHP compares the values in "non-strict" mode; passing the third argument (strict) as true no longer results in a false positive:
if (in_array($y, $testarray, true)) {
echo "Array element ".$y." already exists in text array ";
}
I'm still trying to figure out why, technically speaking, this doesn't work without strict checking enabled.
You can pass a boolean as third parameter to in_array() to enable PHP strict comparison instead of the default loose comparison.
Try this code
$x = '000e286592';
$y = '0e06452425';
$testarray = [];
$testarray[] = $x;
if (in_array($y, $testarray, true)) {
echo "Array element ".$y." already exists in text array ";
}
print_r($testarray);exit();
On the syntax level, these two approaches work identicaly:
<?php
function provideGenerator(): \Generator {
echo "[gInit]";
yield 0=>1;
echo "[gMid]";
yield 1=>2;
echo "[gEnd]";
}
function provideArray(): array {
return [1,2];
}
function provideIterator(): Iterator {
return new ArrayIterator(provideArray());
}
function acceptIterable(iterable $iterable) {
echo "iterable ". \gettype($iterable). ": ";
foreach ($iterable as $item) {
echo $item;
}
echo PHP_EOL;
}
function acceptVariadic(...$variadic) {
echo "variadic ". \gettype($variadic). ": ";
foreach ($variadic as $item) {
echo $item;
}
echo PHP_EOL;
}
acceptIterable(provideGenerator());
acceptIterable(provideArray());
acceptIterable(provideIterator());
acceptIterable([1,2]);
acceptVariadic(...provideGenerator());
acceptVariadic(...provideArray());
acceptVariadic(...provideIterator());
acceptVariadic(1,2);
Resulting:
iterable object: [gInit]1[gMid]2[gEnd]
iterable array: 12
iterable object: 12
iterable array: 12
[gInit][gMid][gEnd]variadic array: 12
variadic array: 12
variadic array: 12
variadic array: 12
Obviously the iterable approach has a lot of benefits:
is not unrolling upon passing of the parameter
allowing to pass keys
spread operator unrolls and converts to array, not only the original type is lost, it takes O(n) time.
spread operator has the quirk of Error: Cannot unpack Traversable with string keys
variadic parameter must be the last parameter, less flexible in refactoring/API extending
What are the reasons to ever use the variadic parameter?
apart from "can be type hinted"?
Chris Haas is right, you're confusing accepting variadic parameters and passing parameters with a spread operator. That's not just a vocabulary mistake, it's at the heart of your question.
A variadic function is one which can receive any number of parameters. You can call it just like any other function, you don't need to know it's variadic:
acceptVariadic(1, 2, 3);
A common example would be printf - note that you don't need an array to call it, but the number of parameters is variable and unlimited:
printf('%s %s', 'a', 'b');
The spread operator is for the opposite case: when you have an array or iterable, but the function takes separate arguments:
$params = [1,2,3];
someFunc(...$params);
// equivalent to
soneFunc(1,2,3);
Here, the function doesn't know that you're using an iterable, it sees the individual parameters.
Using both features at once somewhat defeats the point: if you know you always have an iterable, there's no point spreading it and then collecting it back together in a variadic parameter. So, I'm going to ignore all the discussion in your question of the cost of the spread operator, as irrelevant to real-world uses of variadic functions.
More specifically, variadic functions make most sense when the caller knows exactly how many arguments they want to pass, but the definition wants to support a variety of use cases. Collecting the parameters into an array is an implementation detail of how the definition supports that, not something the caller is interested in.
Taking the printf example, someone writing printf('%s %s %s', $x, $y, $z); can simply read it as a normal function call with 4 parameters. There is a version which instead expects the parameters as a list - vprintf('%s %s %s', [$x, $y, $z]); - but it's rarely used in my experience.
For some functions, there is a common case such as 1 or 2 values, but no distinction between those and further arguments - this is common in PHP's array functions:
$foo = array_merge($array1, $array2); // common case
$foo = array_merge($array1, $array2, $array3); // variadic case
array_push($array, $value); // common case
array_push($array, $value1, $value2); // variadic case
$foo = array_map($callback, $array); // common case
$foo = array_map($callback, $array1, $array2); // variadic case
Passing a list of arrays instead would just be clutter for the common cases, which users would have to remember to add even if they never passed any extra arguments:
$foo = array_merge([$array1, $array2]);
array_push($array, [$value]);
$foo = array_map($callback, [$array]);
In the end, it's mostly a decision of style, not practical benefit. As you've mentioned, PHP's current implementation does have the advantage of allowing you to type hint the variadic arguments, but mostly it's about allowing direct calls to read more naturally.
I'm a bit curious about why does this work:
$arr1[] = $arr2[] = $value;
If I'm correct, first the value is pushed to the end of arr2. Then $arr2[] pushed to the end of $arr1. This is where I'm confused. If I do it separately like:
$var = $arr2[];
I get "Fatal error: Cannot use [] for reading..."
But how come [] works for reading when I'm doing multiple assignments in the same statement?
Because $var is not an array. To make $var an array:
$var = array();
or,
$var = $arr2;
If I'm correct, first the value is pushed to the end of $arr2. Then
$arr2[] pushed to the end of $arr1.
That's not correct. What happens is that $value is pushed to the end of both arrays, one at a time. The phrase "$arr2[] is pushed" is meaningless because the expression $arr2[] is not valid in a read context (i.e. it does not "have a value"), which is exactly what the error message you got in your second attempt says.
But how come [] works for reading when I'm doing multiple assignments
in the same statement?
Chaining the assignment operator in PHP does not work quite as it does in certain other languages. In particular,
$x = $y = (expr);
is treated in the same way as if you had written
// the purpose of $temp is to prevent (expr) from being evaluated twice,
// because evaluating it might have side effects (e.g. think $i++)
$temp = (expr);
$x = $temp;
$y = $temp;
That is, $arr1[] is not being assigned the value $arr2[] but rather $value directly because the assignment operator evaluates to its right-hand-side operand.
This suggestion is wrong:
Then $arr2[] pushed to the end of $arr1.
From the manual:
The value of an assignment expression is the value assigned. That is,
the value of "$a = 3" is 3. This allows you to do some tricky things...
So, first assignment operator $arr2[] = $value returns value of $value, which can be assigned further.
But how come [] works for reading when I'm doing multiple assignments in the same statement?
it doesnt.
try var dumping it
$a = array();
var_dump ($a[] = 2);
which returns
int(2)
That value is then passed to the second so $b = $a = 2
does $a=2, which returns 2, then assigns that value to $b[]
var_dump($a[]);
on the other hand doesnt make sense as var_dump($a[]) doesnt exist, it can only be used for writing to an array
$arr1[] = $arr2[] = $value; this works because you are first assigning a variable or string to an array $arr2[] and then again assiging that array to another array $arr1[].
But in case $var = $arr2[]; you are assing array to a normal variable which is not permitted.
For doing this you need to make $var as an array as $var = array();
Is there any more efficient,cleaner and shorter form for following:
if($x==null or $y==null or $z==null or $w==null) return true;
In general how can I also get name of null variables?
function any_null(array $a){
foreach($a as $v) if ($v==null) return //variable name;
return false;
}
var_dump(any_null(array($x,$y,$z,$w)));
isset can take multiple arguments.
If multiple parameters are supplied then isset() will return TRUE only
if all of the parameters are set.
Since this is the opposite of what you want a simple negation is needed:
!isset($x, $y, $z, $w)
An additional bonus is that isset is not a regular function and will not raise any notices on undefined variables.
It's harder to get the name from the variables. You will need to check every one of them in some way. I think the best and most scalable solution is to use an associative array with the name of the variable as key:
function any_null(array $a){
foreach($a as $k => $v) if ($v === null) return $k;
return false;
}
any_null(array('x' => $x, 'y' => $y));
Note the strict comparison (===). Otherwise false, "", 0 and array() would be counted as "null" as well.
Another solution is to only pass the name of your variables and make use of the $GLOBALS array. Here I've coupled it with a call to func_get_args to get a little less verbose calling convention:
function any_null(){
$a = func_get_args();
foreach($a as $k) if (!isset($GLOBALS[$k])) return $k;
return false;
}
any_null('x', 'y', 'z');
This last solution have a lot of shortcomings though. You can only test variables in the global scope, and you can only test standard variables (not elements in an array, not objects and so on). The syntax for those would be very clunky.
This code
<?php
$a = 10;
$arr1 = array(&$a);
$arr1[0] = 20;
echo $a; echo "\n";
$arr2 = $arr1;
$arr2[0] = 30;
echo $a;
produces
20
30
Obviously reference array members are "preserved", which can lead, for example, to some interesting/strange behavior, like
<?php
function f($arr) {
$arr[0] = 20;
}
$val = 10;
$a = array(&$val);
f($a);
echo $a[0];
?>
outputting
20
My question is: what is it for, where is it documented (except for a user comment at http://www.php.net/manual/en/language.types.array.php#50036) and the Zend Engine source code itself?
PHP's assignment by reference behavior is documented on the manual page "PHP: What References Do". You'll find a paragraph on array value references there, too, starting with:
While not being strictly an assignment by reference, expressions created with the language construct array() can also behave as such by prefixing & to the array element to add.
The page also explains your why your first code behaves like it does:
Note, however, that references inside arrays are potentially dangerous. Doing a normal (not by reference) assignment with a reference on the right side does not turn the left side into a reference, but references inside arrays are preserved in these normal assignments. This also applies to function calls where the array is passed by value.