Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Magic methods for class comparison #13780

Closed
dfidler opened this issue Mar 21, 2024 · 4 comments
Closed

Magic methods for class comparison #13780

dfidler opened this issue Mar 21, 2024 · 4 comments

Comments

@dfidler
Copy link

dfidler commented Mar 21, 2024

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.

@SakiTakamachi
Copy link
Member

By defining the result of <=>, only one magic method is needed.

This is not that difficult, but at least requires an RFC.

@TimWolla
Copy link
Member

Previous RFC: https://wiki.php.net/rfc/user_defined_operator_overloads

@SakiTakamachi
Copy link
Member

The RFC Tim mentioned is several years old, so plenty of time has passed since then to create and vote on another RFC.

And since the previous one was a large RFC that included overloads such as add and sub, if it were a proposal that only involved comparison, it is possible that we would get different results.

@iluuu1994
Copy link
Member

I don't think a GH issue is useful here. The issue has been proposed and rejected multiple times. If you'd like to pick the discussion back up, the mailing list + a new RFC is the place to do it. 🙂

@iluuu1994 iluuu1994 closed this as not planned Won't fix, can't repro, duplicate, stale Mar 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants