Description
Description
Apologies if this is a duplicate. I've tried to find a dupe for this one (here in issues and in https://wiki.php.net/rfc but came up empty). There must be one as I can't be the first person to request this but here goes...
Request
PHP already has a wide variety of magic methods (https://www.php.net/manual/en/language.oop5.magic.php) and I think it would really benefit the language to add magic methods for comparison to allow class authors to override the comparator behaviour.
As a former python programmer I'll use the terms in that language (https://docs.python.org/2/reference/datamodel.html#special-method-names), I routinely found the following absolutely invaluable and now that I'm living in the PHP world, I'm finding the loss of these methods painful. The ones that I miss most are the comparison methods:
comparator method | semantics |
---|---|
__eq__($o):bool | $this == $o |
__lt__($o):bool | $this < $o |
__lte__($o):bool | $this <= $o |
__gt__($o):bool | $this > $o |
__gte__($o):bool | $this >= $o |
__cmp__($o):int | returns {-1, 0, 1} (less than, equal to, greater than) |
The operators (==, <, <=, >, >=) will call their corresponding comparator method if it exists, and if not, then it falls back to __cmp__($o) and if that isn't defined, then it falls back to it's default class comparison behaviour
Note: I'm not bothered about what the names are; PHP tends to be more long-hand than python for stuff like this (for instance, PHP uses __toString() whereas python uses __str__) so perhaps PHP should implement
- __equalTo
- __lessThan
- __lessThanOrEqual
- __greaterThan
- __greaterThanOrEqual
- __compareTo
I'm not bothered what they are called (although I do like python's brevity).
Reasoning
Without these methods, PHP forces class authors to implement comparison methods whose behaviour is likely to conflict with PHP's default comparison/identity logic, which is really bad because if you're comparing objects (for equivalence or for sorting) then
For those of us that need to do more than just class/property comparison/identity, PHP forces us to create multiple code/logic paths for comparison that conflict with each other and create one more method that developers have to remember to use (read - more time spent hunting bugs).
For instance, all three of these methods could have different results:
$a === $b;
$a == $b;
$a->equalTo( $b );
This is pretty much guaranteed to introduce bugs into software when the newbie comes into the team and starts throwing around "==" because the aren't aware that there's an equalTo() method.
As an aside, one of the most fundamental rules that I brow beat into my developers is "thou shalt have only ONE code path to perform any operation - duplication is the work of the devil" and I beat it into them regularly. So I can feel chills going down my spine when reading the above.
Having the ability to override the basic comparison methods (along with __repr__ and and even the logical ones too, so __and__ ,\ __or__, __xor__, etc) would also be awesome - more on that later).
Some Example Use Cases
1. Case insensitive string classes
class IString extends ...() {
...
public function equals($o) {
if ($o instanceof IString) return strtolower($this->value, $o->value);
elseif ($o instanceof ...)
{
...
} else { throw new UnsupportedComparisonException(self::class . ' cant compare to {$o::class}));
}
}
$a = new IString('A');
$b = new IString('a');
$a === $b # false
$a == $b # false
$a->equals($b) # true
But with the addition of a __equals method I can
2. Sorting
I can work around this by using things like __toString() to return a representation of the object (although I lose the ability to convert the object to a string - which is why python also has the\ __repr__ and __hash__ magic methods, which allow us to return a string representation of the object and a hash for the object, respectively).
But this is just a mess...
class IString {
public string $value;
public function __construct($v) { $this->value = $v; }
public function __toString() { return $this->value; }
}
class JClass {
public string $value;
public string $encoding;
public function __construct ($v) { $this->value = $v; $this->encoding = 'calculated'; }
public function __toString() { return "{$this->value}/{$this->encoding}"; }
}
$a = new IString('a');
$b = new JClass('B');
$arr = [ $a, $b ];
# print_r(sort($arr) + "\n"); // fails
usort($arr, fn ($a, $b) => strcmp(strval($a), strval($b)) );
print(json_encode($arr)); # [{"value":"B","encoding":"calculated"},{"value":"a"}]
And I'd need to reproduce the above ugliness every time I had to do any kind of comparison of the methods (ie - more "dual code path methods like equalTo, Sort, etc".
But given a simple magic comparator method (like the one in example #1) then code would simply be sort($arr);
which is infinitely more readable and is going to be much less prone to have bugs. And when a new class is introduced into the comparison chain, it's super easy to just add it to the __compare()/ method to handle it.
Summary
I only provide two use cases, above, but I have had to use these methods almost every time I've created a data class in python. I find that creating these types of classes to be ugly in PHP (for the aforementioned reasons) and I seem to spend more time debugging code in different areas because some nugget-brain has forgotten that the "equalTo" or "compareTo" method exists.
PHP has already embraced magic methods so I think that adding these (comparators and equivalents of __repr__ and __hash__) would complement the existing methods perfectly.