What Is ≪=≫ (The 'Spaceship' Operator) in PHP 7

What is = (the 'Spaceship' Operator) in PHP 7?

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

How the PHP spaceship operator = exactly works on strings?

I read all the answers but it was insufficient for me as the topic for me is how PHP does things internally. So I searched and searched and it turns out that PHP uses the comparison with the operator <=> on strings, character by character and it stops when there is only one different character and this with ASCII codes as follows:

<?php

$str1 = "aaaaaaa";
$str2 = "bbbb";

echo $str1 <=> $str2, PHP_EOL; // -1

print_r(unpack("C*", $str1));
print_r(unpack("C*", $str2));


// output
Array
(
[1] => 97
[2] => 97
[3] => 97
[4] => 97
[5] => 97
[6] => 97
[7] => 97
)
Array
(
[1] => 98
[2] => 98
[3] => 98
[4] => 98
)

which explains why $str1 <=> $str2 === -1.

How does PHP's spaceship operator = handle incomparable operands?

Simple! The pull request that implemented the operator defers to a function called compare_function in PHP's internals, so you just need to read compare_function's 200-line implementation of macro-heavy C and you'll be able to understand the behaviour for yourself. The control structures are only 4 levels deep and there's only a few dozen macros and other functions being invoked; it should be easy!

...

Okay, I admit it, I'm not clever enough to unpick all of that, either. So let's just do some experiments, instead.

If <, == and > can handle the operands consistently, then so can <=>

Most types in PHP can be meaningfully compared to other types thanks to type juggling. Wherever this is the case, the spaceship operator will behave in a way that is consistent with <, == and >.

For instance, let's try with a string and an int:

php > var_dump(3 < 'bla');
bool(false)
php > var_dump(3 == 'bla');
bool(false)
php > var_dump(3 > 'bla');
bool(true)
php > var_dump(3 <=> 'bla');
int(1)

Or with null and a resource:

php > $fp = fopen('test', 'r');
php > var_dump(null > $fp);
bool(false)
php > var_dump(null == $fp);
bool(false)
php > var_dump(null < $fp);
bool(true)
php > var_dump(null <=> $fp);
int(-1)

Or with a float and an array:

php > var_dump(1.0 > []);
bool(false)
php > var_dump(1.0 == []);
bool(false)
php > var_dump(1.0 < []);
bool(true)
php > var_dump(1.0 <=> []);
int(-1)

In all of these cases, the result of $a <=> $b is exactly what the docs say it should be: -1 if $a < $b, 0 if $a == $b, and 1 if $a > $b.

But if none of <, == and > return true, spaceship gets confused and returns 1

Although type coercion allows most values of different types to be compared to each other, such that exactly one of $a < $b, $a == $b and $a > $b is true, there are a few edge cases in which this doesn't hold. In those cases, the result of $a <=> $b is 1, which isn't very meaningful or useful.

For instance, let's compare some objects of different classes:

php > class Foo {}
php > class Bar {}
php > $a = new Foo;
php > $b = new Bar;
php > var_dump($a < $b);
bool(false)
php > var_dump($a == $b);
bool(false)
php > var_dump($a > $b);
bool(false)
php > var_dump($a <=> $b);
int(1)
php > var_dump($b <=> $a);
int(1)

or some non-equal arrays where neither is strictly greater than the other:

php > $a = ['foo' => 'bar'];
php > $b = ['bar' => 'foo'];
php > var_dump($a < $b);
bool(false)
php > var_dump($a == $b);
bool(false)
php > var_dump($a > $b);
bool(false)
php > var_dump($a <=> $b);
int(1)
php > var_dump($b <=> $a);
int(1)

<=> can also throw warnings

If we compare an object with an int, we get a warning, just like if we did so with any other comparison operator:

php > $a = new stdclass;
php > var_dump($a <=> 1);
PHP Notice: Object of class stdClass could not be converted to int in php shell code on line 1
int(0)

The results for incomparable types are obviously unhelpful and will confuse the heck out of anything that's expecting the spaceship operator to consistently order values of different types. So don't use it in circumstances where it might be making meaningless comparisons like these.

What is the difference between strcmp() and Spaceship Operator ( = )

strcmp - it is function for "binary safe" string comparison

The spaceship operator (<=>) returns -1 if the left side is smaller, 0 if the values are equal and 1 if the left side is larger. It can be used on all generic PHP values with the same semantics as < , <=, ==, >=, >. This operator is similar in behavior to strcmp() or version_compare(). This operator can be used with integers, floats, strings, arrays, objects, etc.

For example you can compare arrays or objects, and by float you get different result:

$var1 = 1.3;
$var2 = 3.2;
var_dump($var1 <=> $var2); // int(-1)
var_dump(strcmp($var1, $var2)); // int(-2)

And other differences...

More example this

usort array by two parameters using spaceship operator

usort($products, function ($a, $b) {
if ( $a['brand_order'] == $b["brand_order"] ) { //brand_order are same
return $a['title'] <=> $b['title']; //sort by title
}
return $a['brand_order'] <=> $b['brand_order']; //else sort by brand_order
});

Test here

What is the Ruby = (spaceship) operator?

The spaceship operator will return 1, 0, or −1 depending on the value of the left argument relative to the right argument.

a <=> b :=
if a < b then return -1
if a = b then return 0
if a > b then return 1
if a and b are not comparable then return nil

It's commonly used for sorting data.

It's also known as the Three-Way Comparison Operator. Perl was likely the first language to use it. Some other languages that support it are Apache Groovy, PHP 7+, and C++20.

Spaceship operator confusion in PHP7 (return -1)

You have three possibilities when comparing the two values that are passed to a comparison function: $a < $b, $a == $b, or $a > $b. So you need three distinct return values and PHP has chosen the integers: -1, 0, and 1. I guess it could just as easily be strings lesser, equal and greater or integers 5, 7 and 9 or any combination, but it's not.

From the manual usort()

The comparison function must return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.

  • $a < $b return -1
  • $a == $b return 0
  • $a > $b return 1

This is NOT how types work in PHP, but you can think of it like this: is $a > $b? where -1 means false, 1 means true and 0 means neither (equal).

How operator = compare the objects?

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 funcfunction bool2str($v){if($v === NULL)return "NULL";elseif($v === FALSE)return "FALSE";elseif($v === TRUE)return "TRUE";else return "UNEXPECTED: '$v'";}

uasort with = (spaceship operator)

return $b['total'] <=> $a['total'] ?: $b['vat'] <=> $a['vat'];

If total are equal, <=> returns 0, which is falsey, so the ?: operator will return the result of the vat comparison instead. The first non-0 result will be returned.



Related Topics



Leave a reply



Submit