Asked  7 Months ago    Answers:  5   Viewed   41 times

PHP 7, which will come out in November this year will introduce the Spaceship (<=>) operator. What is it and how does it work?

This question already has an answer in our general reference question about PHP operators.

 Answers

50

The <=> ("Spaceship") operator will offer combined comparison in that it will :

Return 0 if values on either side are equal
Return 1 if the value on the left is greater
Return -1 if the value on the right is greater

The rules used by the combined comparison operator are the same as the currently used comparison operators by PHP viz. <, <=, ==, >= and >. Those who are from Perl or Ruby programming background may already be familiar with this new operator proposed for PHP7.

   //Comparing Integers

    echo 1 <=> 1; //output  0
    echo 3 <=> 4; //output -1
    echo 4 <=> 3; //output  1

    //String Comparison

    echo "x" <=> "x"; //output  0
    echo "x" <=> "y"; //output -1
    echo "y" <=> "x"; //output  1
Wednesday, March 31, 2021
 
jwegner
answered 7 Months ago
58

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:

The null coalescing operator (??) has been added as syntactic sugar for the common case of needing to use a ternary in conjunction with isset(). It returns its first operand if it exists and is not NULL; otherwise it returns its second operand.

Here's some example code to demonstrate this:

<?php

$a = null;

print $a ?? 'b'; // b
print "n";

print $a ?: 'b'; // b
print "n";

print $c ?? 'a'; // a
print "n";

print $c ?: 'a'; // Notice: Undefined variable: c in /in/apAIb on line 14
print "n";

$b = array('a' => null);

print $b['a'] ?? 'd'; // d
print "n";

print $b['a'] ?: 'd'; // d
print "n";

print $b['c'] ?? 'e'; // e
print "n";

print $b['c'] ?: 'e'; // Notice: Undefined index: c in /in/apAIb on line 33
print "n";

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:

$a = false ?? 'f'; // false
$b = false ?: 'g'; // 'g'

would then have $a be equal to false and $b equal to 'g'.

Wednesday, March 31, 2021
 
Shibbir
answered 7 Months ago
96

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 ==.

Array comparison operators

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

Object comparison operators

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


Conclusion

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.

Wednesday, March 31, 2021
 
ClmentM
answered 7 Months ago
60

instanceof

The Left Hand Side (LHS) operand is the actual object being tested to the Right Hand Side (RHS) operand which is the actual constructor of a class. The basic definition is:

Checks the current object and returns true if the object
is of the specified object type.

Here are some good examples and here is an example taken directly from Mozilla's developer site:

var color1 = new String("green");
color1 instanceof String; // returns true
var color2 = "coral"; //no type specified
color2 instanceof String; // returns false (color2 is not a String object)

One thing worth mentioning is instanceof evaluates to true if the object inherits from the classe's prototype:

var p = new Person("Jon");
p instanceof Person

That is p instanceof Person is true since p inherits from Person.prototype.

Per the OP's request

I've added a small example with some sample code and an explanation.

When you declare a variable you give it a specific type.

For instance:

int i;
float f;
Customer c;

The above show you some variables, namely i, f, and c. The types are integer, float and a user defined Customer data type. Types such as the above could be for any language, not just JavaScript. However, with JavaScript when you declare a variable you don't explicitly define a type, var x, x could be a number / string / a user defined data type. So what instanceof does is it checks the object to see if it is of the type specified so from above taking the Customer object we could do:

var c = new Customer();
c instanceof Customer; //Returns true as c is just a customer
c instanceof String; //Returns false as c is not a string, it's a customer silly!

Above we've seen that c was declared with the type Customer. We've new'd it and checked whether it is of type Customer or not. Sure is, it returns true. Then still using the Customer object we check if it is a String. Nope, definitely not a String we newed a Customer object not a String object. In this case, it returns false.

It really is that simple!

Monday, June 7, 2021
 
mertak
answered 5 Months ago
39

That's not one operator, that's two operators written to look like one operator.

From the operator precedence table (highest to lowest):

[] []=
**
! ~ + - [unary]
[several more lines]
<=> == === != =~ !~

Also, the Regexp class has a unary ~ operator:

~ rxp → integer or nil
Match—Matches rxp against the contents of $_. Equivalent to rxp =~ $_.

So your expression is equivalent to:

"abc" != (/abc/ =~ $_)

And the Regexp#=~ operator (not the same as the more familiar String#=~) returns a number:

rxp =~ str → integer or nil
Match—Matches rxp against str.

So you get true as your final result because comparing a string to a number is false.

For example:

>> $_ = 'Where is pancakes house?'
=> "Where is pancakes house?"
>> 9 !=~ /pancakes/
=> false
>> ~ /pancakes/
=> 9
Friday, July 30, 2021
 
Ultimater
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :