Can someone explain the differences between ternary operator shorthand (?:
) and null coalescing operator (??
) in PHP?
When do they behave differently and when in the same way (if that even happens)?
$a ?: $b
VS.
$a ?? $b
Can someone explain the differences between ternary operator shorthand (?:
) and null coalescing operator (??
) in PHP?
When do they behave differently and when in the same way (if that even happens)?
$a ?: $b
VS.
$a ?? $b
PHP 7 adds the null coalescing operator:
// Fetches the value of $_GET['user'] and returns 'nobody'
// if it does not exist.
$username = $_GET['user'] ?? 'nobody';
// This is equivalent to:
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';
You could also look at short way of writing PHP's ternary operator ?: (PHP >=5.3 only)
// Example usage for: Short Ternary Operator
$action = $_POST['action'] ?: 'default';
// The above is identical to
$action = $_POST['action'] ? $_POST['action'] : 'default';
And your comparison to C# is not fair. "in PHP you have to do something like" - In C# you will also have a runtime error if you try to access a non-existent array/dictionary item.
When reading the RFC we find out that it contradicts itself:
Add a new operator (expr) <=> (expr), it returns 0 if both operands are equal, 1 if the left is greater, and -1 if the right is greater. It uses exactly the same comparison rules as used by our existing comparison operators: <, <=, ==, >= and >. (See the manual for details)
Note: See the ==
, this means the spaceship operator does a loosely comparison.
And later down in the examples:
// only values are compared $a = (object) ["a" => "b"]; $b = (object) ["b" => "b"]; echo $a $b; // 0
The spaceship operator is just a combination of the operators <
, ==
and >
. And it gives respective return values depending on what it evaluates to:
operator(s): < = >
return value: -1 0 1
Now arrays and objects are a bit more complex types. To understand what the <=>
PHP spaceship operator does, we need to look and understand how <
, ==
and >
work for arrays and objects.
So let's look at the comparison operators <
, >
, ==
for each type. First we will look at <
and >
and then after that we also look at ==
.
Now as for arrays <
and >
are documented here:
???????????????????????????????????????????????????????????????????????????? ? type of ? type of ? ? ? Operand 1 ? Operand 2 ? Result ? ???????????????????????????????????????????????????????????????????????????? ? array ? array ? Array with fewer members is smaller, ? ? ? ? if key from operand 1 is not found in operand 2 ? ? ? ? then arrays are uncomparable, ? ? ? ? otherwise - compare value by value ? ????????????????????????????????????????????????????????????????????????????
This can also be written and represented by code:
Example #2 Transcription of standard array comparison
<?php // Arrays are compared like this with standard comparison operators function standard_array_compare($op1, $op2) { if (count($op1) < count($op2)) { return -1; // $op1 < $op2 } elseif (count($op1) > count($op2)) { return 1; // $op1 > $op2 } foreach ($op1 as $key => $val) { if (!array_key_exists($key, $op2)) { return null; // uncomparable } elseif ($val < $op2[$key]) { return -1; } elseif ($val > $op2[$key]) { return 1; } } return 0; // $op1 == $op2 } ?>
We can test this easily with some testing. Using methods like in math and always change only one thing, so we can make sure we are correct here:
/**
/*
/* Testing operators: < and >
/*
*/
//Test case
//Variations: amount, values and keys (order)
//Test count: 9
// Failed: 0
// Passed: 9
{
//Test case 1.1
$a = [1];
$b = [1];
//Passed
var_dump("Same amount of elements, keys and values: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.2
$a = [1];
$b = [1, 1];
//Passed
var_dump("NOT same amount of elements, but same values: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.3
$a = [10];
$b = [1, 1];
//Passed
var_dump("NOT same amount of elements nor values: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.4
$a = [1 => 1];
$b = [10 => 1];
//Passed
var_dump("Same amount of element and values, NOT same keys: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.5
$a = [10];
$b = [1];
//Passed
var_dump("Same amount of elements and keys, NOT same values: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.6
$a = [1 => 1, 2 => 1];
$b = [2 => 1, 1 => 1];
//Passed
var_dump("Same amount of elements and keys in different order: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.7
$a = [1 => 1, 2 => 5];
$b = [2 => 5];
//Passed
var_dump("Same values, NOT same amount of elements nor keys: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.8
$a = [10 => 1];
$b = [1 => 10];
//Passed
var_dump("NOT same keys nor values: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.9
$a = [1 => 1, 2 => 1];
$b = [2 => 10, 1 => 1];
//Passed
var_dump("Same amount of elements and values, NOT same keys nor order: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
}
echo PHP_EOL . PHP_EOL . PHP_EOL; //Test case separator
/**
/*
/* Test case end
/*
*/
//NULL, TRUE, FALSE 2 str func
function bool2str($v){if($v === NULL)return "NULL";elseif($v === FALSE)return "FALSE";elseif($v === TRUE)return "TRUE";else return "UNEXPECTED: '$v'";}
For the equality/identity operators ==
and ===
we find the documentation for arrays here:
??????????????????????????????????????????????????????????????????????????? ? Example ? Name ? Result ? ??????????????????????????????????????????????????????????????????????????? ? $a == $b ? Equality ? TRUE if $a and $b have the same key/value pairs. ? ? $a === $b ? Identity ? TRUE if $a and $b have the same key/value pairs ? ? ? ? in the same order and of the same types. ? ???????????????????????????????????????????????????????????????????????????
As before we can simply test this with some testing code:
/**
/*
/* Testing operators: == and ===
/*
*/
//Test case
//Variations: amount, values and keys (order)
//Test count: 5
// Failed: 0
// Passed: 5
{
//Test case 2.1
$a = [1];
$b = [1];
//Passed
var_dump("Same amount of elements, values and keys: " . "'==' -> " . bool2str($a == $b) . " '===' -> " . bool2str($a === $b));
//Test case 2.2
$a = [1];
$b = [10, 1];
//Passed
var_dump("NOT same amount of elements, but same values: " . "'==' -> " . bool2str($a == $b) . " '===' -> " . bool2str($a === $b));
//Test case 2.3
$a = [10];
$b = [1];
//Passed
var_dump("Same amount of elements, but not values: " . "'==' -> " . bool2str($a == $b) . " '===' -> " . bool2str($a === $b));
//Test case 2.4
$a = [1 => 1];
$b = [10 => 1];
//Passed
var_dump("Same amount of elements and values, but not keys: " . "'==' -> " . bool2str($a == $b) . " '===' -> " . bool2str($a === $b));
//Test case 2.5
$a = [1 => 1, 2 => 2];
$b = [2 => 2, 1 => 1];
//Passed
var_dump("Same amount of elements, key and values, but different order: " . "'==' -> " . bool2str($a == $b) . " '===' -> " . bool2str($a === $b));
}
echo PHP_EOL . PHP_EOL . PHP_EOL; //Test case separator
/**
/*
/* Test case end
/*
*/
//NULL, TRUE, FALSE 2 str func
function bool2str($v){if($v === NULL)return "NULL";elseif($v === FALSE)return "FALSE";elseif($v === TRUE)return "TRUE";else return "UNEXPECTED: '$v'";}
So we can see and confirm that the comparison operators for arrays work as expected and as documented!
Full Testing File
The documentation for <
and >
with objects is documented here:
???????????????????????????????????????????????????????????????????????????? ? type of ? type of ? ? ? Operand 1 ? Operand 2 ? Result ? ???????????????????????????????????????????????????????????????????????????? ? object ? object ? Built-in classes can define its own comparison, ? ? ? ? different classes are uncomparable, ? ? ? ? same class compare properties same as arrays ? ????????????????????????????????????????????????????????????????????????????
As before we can also test this:
/**
/*
/* Testing operators: < and >
/*
*/
//Test case
//Variations: amount, values and keys (order)
//Test count: 10
// Failed: 0
// Passed: 10
{
//Test case 1.1
$a = (object)["a" => 1];
$b = (object)["a" => 1];
//Passed
var_dump("Same amount of elements, keys and values: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.2
$a = (object)["a" => 1];
$b = (object)["a" => 1, "b" => 1];
//Passed
var_dump("NOT same amount of elements, but same values: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.3
$a = (object)["a" => 10];
$b = (object)["a" => 1, "b" => 1];
//Passed
var_dump("NOT same amount of elements nor values: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.4
$a = (object)["a" => 1];
$b = (object)["b" => 1];
//Passed
var_dump("Same amount of element and values, NOT same keys: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.5
$a = (object)["a" => 10];
$b = (object)["a" => 1];
//Passed
var_dump("Same amount of elements and keys, NOT same values: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.6
$a = (object)["a" => 1, "b" => 1];
$b = (object)["b" => 1, "a" => 1];
//Passed
var_dump("Same amount of elements and keys in different order: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.7
$a = (object)["a" => 1, "b" => 5];
$b = (object)["b" => 5];
//Passed
var_dump("Same values, NOT same amount of elements nor keys: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.8
$a = (object)["c" => 1];
$b = (object)["a" => 10];
//Passed
var_dump("NOT same keys nor values: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.9
$a = (object)["a" => 1, "b" => 1];
$b = (object)["b" => 10, "a" => 1];
//Passed
var_dump("Same amount of elements and values, NOT same keys nor order: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
//Test case 1.10
class A {public $a = 1;}
$a = new A;
class B {public $a = 1;}
$b = new B;
//Passed
var_dump("Same amount of elements and values and keys, but different not built-in class: " . "'<' -> " . bool2str($a < $b) . " '>' -> " . bool2str($a > $b));
}
echo PHP_EOL . PHP_EOL . PHP_EOL; //Test case separator
/**
/*
/* Test case end
/*
*/
//NULL, TRUE, FALSE 2 str func
function bool2str($v){if($v === NULL)return "NULL";elseif($v === FALSE)return "FALSE";elseif($v === TRUE)return "TRUE";else return "UNEXPECTED: '$v'";}
The documentation for ==
and ===
with objects has its own page here:
When using the comparison operator (==), object variables are compared in a simple manner, namely: Two object instances are equal if they have the same attributes and values, and are instances of the same class.
When using the identity operator (===), object variables are identical if and only if they refer to the same instance of the same class.
And again this can be tested:
/**
/*
/* Testing operators: == and ===
/*
*/
//Test case
//Variations: amount, values and keys (order)
//Test count: 7
// Failed: 0
// Passed: 7
{
//Test case 2.1
$a = (object)["a" => 1];
$b = (object)["a" => 1];
//Passed
var_dump("Same amount of elements, values and keys: " . "'==' -> " . bool2str($a == $b) . " '===' -> " . bool2str($a === $b));
//Test case 2.2
$a = (object)["a" => 1];
$b = (object)["a" => 10, "b" => 1];
//Passed
var_dump("NOT same amount of elements, but same values: " . "'==' -> " . bool2str($a == $b) . " '===' -> " . bool2str($a === $b));
//Test case 2.3
$a = (object)["a" => 10];
$b = (object)["a" => 1];
//Passed
var_dump("Same amount of elements, but not values: " . "'==' -> " . bool2str($a == $b) . " '===' -> " . bool2str($a === $b));
//Test case 2.4
$a = (object)["a" => 1];
$b = (object)["b" => 1];
//Passed
var_dump("Same amount of elements and values, but not keys: " . "'==' -> " . bool2str($a == $b) . " '===' -> " . bool2str($a === $b));
//Test case 2.5
$a = (object)["a" => 1, "b" => 2];
$b = (object)["b" => 2, "a" => 1];
//Passed
var_dump("Same amount of elements, key and values, but different order: " . "'==' -> " . bool2str($a == $b) . " '===' -> " . bool2str($a === $b));
//Test case 2.6
class C {public $a = 1;}
$a = new A;
class D {public $a = 1;}
$b = new B;
//Passed
var_dump("Same amount of elements and values and keys, but different not built-in class: " . "'==' -> " . bool2str($a == $b) . " '===' -> " . bool2str($a === $b));
//Test case 2.7
$a = (object)["a" => 1];
$b = $a;
//Passed
var_dump("Same exact instance: " . "'==' -> " . bool2str($a == $b) . " '===' -> " . bool2str($a === $b));
}
echo PHP_EOL . PHP_EOL . PHP_EOL; //Test case separator
/**
/*
/* Test case end
/*
*/
//NULL, TRUE, FALSE 2 str func
function bool2str($v){if($v === NULL)return "NULL";elseif($v === FALSE)return "FALSE";elseif($v === TRUE)return "TRUE";else return "UNEXPECTED: '$v'";}
So we see, that the comparison operators with objects behave exactly like expected and documented! Even with loose comparison the attributes and values are being considered.
Full Testing File
As this bug has been reported here, the bug report is probably based on the comment in the RFC, which says:
// only values are compared
But besides that this is the only example in the RFC with a comment, the RFC clearly states that it uses the same comparison rules as <
, ==
and >
.
This means that the code example provided would be uncomparable, because it doesn't have the same attributes/keys.
As for equality it would need same attributes/keys and values so it can't be equal, and for less- or greater- than it is uncomparable as shown in the code example above how the comparison works:
if (!array_key_exists($key, $op2)) {
return null; // uncomparable
}
We also see this if we try each comparison operator alone:
$a = (object)["b" => "b"];
$b = (object)["a" => "b"];
var_dump($a > $b); //FALSE
var_dump($a < $b); //FALSE
var_dump($a == $b); //FALSE
All return false, since it's uncomparable.
And just for the case StdClass
would have its own comparison, we can test it with our own class:
class A {
public $a = "";
public $b = "";
}
$a = new A;
$a->a = "b";
unset($a->b);
$b = new A;
$b->b = "b";
unset($b->a);
var_dump($a);
var_dump($b);
var_dump($a <=> $b); //1
Also same output: 1.
So I would say since it is uncomparable it shouldn't return 0
, 1
nor -1
. It should probably return FALSE
or NULL
or something like this.
Right now I would say this behaviour isn't documented correctly.
Ternary operators use three operands:
A condition followed by a ?
,
followed by an expression to evaluate if the condition is 'truthy', followed by a :
,
followed by an expression to evaluate if the condition is falsey
.
So in your case, what you'd want to do is this:
country1 = country1 != null ? 'Turkey' : 'ABD';
EDIT:
You seem a little confused about ??
operator. ??
is called Null Coalescing operator
x = x ?? 'foo';
is equivalent to
if( x == null )
x = 'foo';
else
x = *whatever the value previously was*;
so if we have x
set to bar
before the check, it won't change to foo
because bar
is not equal to null
. Also, note that the else
statement here is redundant.
so ??
will set the variable to some value only if it was previously null.
In your code, you are trying to assign one of the two values Turkey
or ABD
, and not a single value if the previous value was null. So you get a syntax error.
So, to summarize.
if() {}
else {}
can be shortened using the ternary operator ? :
.
and
if(){}
can be shortened using the ??
operator, because the else statement here will simply be redundant.
Thus, the equivalent of your code won't use ??
operator.
This happens as consequence of the assignment operator also returning the value:
The assignment operator (=) stores the value of its right-hand operand in the storage location, property, or indexer denoted by its left-hand operand and returns the value as its result.
The expression b = 12
not only assigns 12 to b
, but also returns this value.
When your first argument is null, they're basically the same except that the null coalescing won't output an
E_NOTICE
when you have an undefined variable. The PHP 7.0 migration docs has this to say:Here's some example code to demonstrate this:
The lines that have the notice are the ones where I'm using the shorthand ternary operator as opposed to the null coalescing operator. However, even with the notice, PHP will give the same response back.
Execute the code: https://3v4l.org/McavC
Of course, this is always assuming the first argument is
null
. Once it's no longer null, then you end up with differences in that the??
operator would always return the first argument while the?:
shorthand would only if the first argument was truthy, and that relies on how PHP would type-cast things to a boolean.So:
would then have
$a
be equal tofalse
and$b
equal to'g'
.