Skip to content

Magic methods for class comparison #13780

Closed as not planned
Closed as not planned
@dfidler

Description

@dfidler

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions