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

2.9k views Asked by At

There will be the spaceship operator added in PHP 7. I am not sure about how it works in some edge cases.

$a <=> $b will return:

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

What will happen if the values are not comparable?

Which variable types can be compared?

1

There are 1 answers

0
Mark Amery On BEST ANSWER

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.