Is there a __equals method in PHP like there is in Java?

前端 未结 5 1013
执念已碎
执念已碎 2021-01-01 09:40

Is there a pattern or magic method you can use in PHP to define when to compare two instances of a class?

For example, in Java I could easily override the equa

相关标签:
5条回答
  • 2021-01-01 10:01

    In a word? No. There is no __equals magic method. There is a complete list of the magic methods in the manual.

    You can do

    $myObject1 == $myObject2
    

    which will consider them equal if they have the same attributes and values, and are instances of the same class.

    I have often wished for this type of method myself, but I think that a more useful one would be a __compare() method which would be called for any comparison operator <, >, ==, ===, etc it already exist for PHP's inbuilt classes as can be seen in the PHP internals wiki and there is an example of how it could be implemented in the PHPInternals book:-

    compare_objects

    int (*compare)(zval *object1, zval *object2 TSRMLS_DC)
    

    Compares two objects. Used for the operators ==, !=, <, >, ⇐ and >=. The implementations should follow these rules – for any objects a, b and c that share the same compare handler:

    One way I have used to achieve this is to implement a Comparable interface, something like:-

    interface Comparable
    {
        /**
         * @param Comparable $other
         * 
         * @return Int -1, 0 or 1 Depending on result of comparison
         */
        public function compareTo(Comparable $other);
    }
    

    The details of object comparison, and everything else OOP related can be found here http://www.php.net/manual/en/language.oop5.php.

    This may be implemented in PHP 7.

    There is now an implementation of this that you can install using composer. https://github.com/Fleshgrinder/php-comparable

    0 讨论(0)
  • 2021-01-01 10:05

    First off the == operator is sufficient in most cases, especially if we are talking about value objects. Just be sure to provide a __toString method if you want the ability to compare the instance with scalar values.

    <?php
    
    final class ValueObject {
    
        private $value;
    
        public function __construct($value) {
            $this->value = $value;
        }
    
        public function __toString() {
            return (string) $this->value;
        }
    
    }
    
    $a = new ValueObject(0);
    $b = new ValueObject(1);
    
    var_dump(
        $a == $b,  // bool(false)
        $a == $a,  // bool(true)
        $b == $b,  // bool(true)
        $a == '0', // bool(true)
        $b == '1'  // bool(true)
    );
    

    There is one gotcha with this, you cannot compare to scalar type other than string (at least not in PHP 5 and 7) because it will complain that the instance could not be converted to the desired value. Hence, you always need to ensure that the value type you compare to is a string.

    If you want more than that, e.g. you want to lowercase the input or handle float value up until a certain accuracy, you need a different approach. One way to handle this is as follows.

    <?php
    
    interface Comparable {
    
        function compareTo(Comparable $other): int;
    
    }
    
    function class_compare(Comparable $a, Comparable $b): int {
        return $a->compareTo($b);
    }
    
    final class C implements Comparable {
    
        private $value;
    
        public function __construct(int $value) {
            $this->value = $value;
        }
    
        public function compareTo(Comparable $other): int {
            assert($this instanceof $other && $other instanceof $this);
    
            return $this->value <=> $other->value;
        }
    
    }
    
    $c1 = new C(0);
    $c2 = new C(0);
    
    var_dump($c1->compareTo($c2)); // int(0)
    
    $c0 = new C(0);
    $c1 = new C(1);
    $c2 = new C(2);
    
    $actual = [$c2, $c1, $c0];
    usort($actual, 'class_compare');
    
    var_dump($actual === [$c0, $c1, $c2]); // bool(true)
    

    The assert is important since we have no generics in PHP. This is a pretty sad state of things and there is no pretty way to implement this.

    What I tend to do is the following.

    <?php
    
    final class SomeClass {
    
        private $value;
    
        public function __construct($value) {
            $this->value = $value;
        }
    
        public static function compare(SomeClass $a, SomeClass $b): int {
            return $a->compareTo($b);
        }
    
        public function compareTo(SomeClass $other): int {
            return $this->value <=> $other->value;
        }
    
        public function isEqual($other): bool {
            return $other instanceof $this && $other->value === $this->value;
        }
    
        public function isIdentical($other): bool {
            return $other instanceof $this && $this instanceof $other && $other->value === $this->value;
        }
    
    }
    

    Simply sticking to these method names by convention allows it to use them appropriately. One could even provide traits to implement the desired default behavior among multiple classes.

    0 讨论(0)
  • 2021-01-01 10:06

    If you want to compare your custom object, you can do it like this:

    $time1 = new MyTimeClass("09:35:12");
    $time2 = new MyTimeClass("09:36:09");
    
    if($time1 > $time2) echo "Time1 is bigger";
    else echo "Time2 is bigger";
    
    //result: Time2 is bigger
    

    It compares the first property found in the class, in my case an int value that holds the total number of seconds in the given time. If you put the $seconds property on top you'll notice that it gives an unexpected 'Time1 is bigger'.

    class MyTimeClass {
        public $intValue;
        public $hours;
        public $minutes;
        public $seconds;
    
        public function __construct($str){
            $array = explode(":",$str);
            $this->hours = $array[0];
            $this->minutes = $array[1];
            $this->seconds = $array[2];
            $this->intValue = ($this->hours * 3600) + ($this->minutes * 60) + $this->seconds;
        }
    }
    
    0 讨论(0)
  • 2021-01-01 10:18

    Basically, as everyone says, this will do:

    $object1 == $object2
    

    Compares type and properties.


    But what I do in this cases, when I want to personalize my equality methods, is implement the magic method __toString() in the classes I want to assert equality.

    class Car {
      private $name;
      private $model;
       ...
      public function __toString() {
         return $this->name.", ".$this->model;
      }
    }
    

    And then when I want to do the comparision I just do this:

    $car1->toString() === $car2->toString()
    

    And that will compare if the two instances have the same attributes.

    The other option (as halfer states in the comments) is implement an equal method that asserts equality of another instance of the same class. For example:

    class Car {
      private $name;
      private $model;
       ...
      public function equals(Car $anotherCar) {
             if($anotherCar->getName() !== $this->name) {
               return false;
             }
    
             if($anotherCar->getModel() !== $this->model) {
               return false;
             }
             ...
             return true;
      }
    }
    
    0 讨论(0)
  • 2021-01-01 10:22

    Sadly not, but you can quite easily replicate something close. For example:-

    <?php
    interface IComparable {
        public function compare(self $subject);
    }
    
    class Foo implements IComparable {
        public function compare(self $subject) {
            return $this->__toString() === $subject->__toString();
        }
        public function __toString() {
            return serialize($this);
        }
    }
    
    function compare(IComparable $a, IComparable $b) {
        return $a->compare($b);
    }
    
    $a = new Foo;
    $b = new Foo;
    
    var_dump(compare($a, $b)); //true
    
    $a->name = 'A';
    $b->name = 'B';
    
    var_dump(compare($a, $b)); //false
    

    It's not particularly elegant, but should get you on your way.

    Anthony.

    0 讨论(0)
提交回复
热议问题