This code:
<?php
declare(strict_types=1);
class Foo
{
public string $foo;
public function __construct(
string $foo
) {
$this->foo = $foo;
}
}
$a = new Foo('bar');
$b = new Foo('buzz');
$c = new Foo('bar');
echo "$a == $b: "; var_dump($a == $b);
echo "$a == $c: "; var_dump($a == $c);
echo "$a != $b: "; var_dump($a != $b);
echo "$a != $c: "; var_dump($a != $c);
echo "$a === $b: "; var_dump($a === $b);
echo "$a === $c: "; var_dump($a === $c);
echo "$a !== $b: "; var_dump($a !== $b);
echo "$a !== $c: "; var_dump($a !== $c);
?>
Will output:
<pre> $a == $b: bool(false) $a == $c: bool(true) $a != $b: bool(true) $a != $c: bool(false) $a === $b: bool(false) $a === $c: bool(false) $a !== $b: bool(true) $a !== $c: bool(true) </pre>Using:
php -v
PHP 8.0.7 (cli) (built: Jun 2 2021 04:04:16) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.7, Copyright (c) Zend Technologies
with Zend OPcache v8.0.7, Copyright (c), by Zend Technologies
with Xdebug v3.0.2, Copyright (c) 2002-2021, by Derick Rethans
How is that even possible ?
- I declared strict_types to avoid type coercion
- My data structure is opaque (all properties are private),
How does the engine succeeds in comparing my objects ? Isn't it supposed
to be always false for == and === and always true for != and !== ?
I am missing something ?
Regards,
--
Pierre
OK sorry for the noise, I missed this
https://www.php.net/manual/en/language.oop5.object-comparison.php
Fun fact, writing PHP since PHP 3, and I never knew this.
Doesn't it seems like a dangerous behavior ?
Regards,
--
Pierre
This code:
<?php
declare(strict_types=1);
class Foo
{
public string $foo;public function __construct( string $foo ) { $this->foo = $foo; }
}
$a = new Foo('bar');
$b = new Foo('buzz');
$c = new Foo('bar');echo "$a == $b: "; var_dump($a == $b);
echo "$a == $c: "; var_dump($a == $c);echo "$a != $b: "; var_dump($a != $b);
echo "$a != $c: "; var_dump($a != $c);echo "$a === $b: "; var_dump($a === $b);
echo "$a === $c: "; var_dump($a === $c);echo "$a !== $b: "; var_dump($a !== $b);
echo "$a !== $c: "; var_dump($a !== $c);?>
Will output:
<pre> $a == $b: bool(false) $a == $c: bool(true) $a != $b: bool(true) $a != $c: bool(false) $a === $b: bool(false) $a === $c: bool(false) $a !== $b: bool(true) $a !== $c: bool(true) </pre>Using:
php -v
PHP 8.0.7 (cli) (built: Jun 2 2021 04:04:16) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.7, Copyright (c) Zend Technologies
with Zend OPcache v8.0.7, Copyright (c), by Zend Technologies
with Xdebug v3.0.2, Copyright (c) 2002-2021, by Derick RethansHow is that even possible ?
- I declared strict_types to avoid type coercion
strict_types applies to type checks only. It has no impact on other type
coercions.
- My data structure is opaque (all properties are private),
== comparison on objects is a == comparison on the properties. Whether the
properties are private doesn't (and shouldn't) matter.
Regards,
Nikita
Le 18/06/2021 à 18:08, Nikita Popov a écrit :
== comparison on objects is a == comparison on the properties. Whether the
properties are private doesn't (and shouldn't) matter.Regards,
Nikita
Thanks a lot for your answer. This raises more question in my mind: I
need to rephrase here.
My use case is the following, we have a custom identifier class in a
project which does look like the code above:
<?php
use Ramsey\Uuid\UuidInterface;
interface Comparable
{
public function equals(/* mixed */ $othjer): bool;
}
interface Identifier extends Comparable
{
}
interface UuidIdentifier implements Identifier
{
public function getInternalUuid(): UuidIdentifier;
}
class FooId implements UuidIdentifier
{
private UuidInterface $uuid;
// Constructor doesn't matter here.
public function getInternalUuid(): UuidIdentifier
{
return $this->uuid;
}
public function equals($other): bool
{
return $other instanceof UuidIdentifier &&
$this->uuid->equals($other->getInternalUuid());
}
}
?>
Actually, use case is not exactly the same, but somehow looks like this.
Using the object equality, using ==
and !=
some code of a colleague
did work gracefully in unit tests, and in some other integration tests,
but coverage did not reflect the real possible scenarios.
Fact is:
- UuidInterface from ramsey/uuid is an opaque structure, that gives a
contract but no guarantees about the internal reprensentation,
- it could be a LazyUuid, and Uuid4 or any other implementation, which
all yield the same value, but not the same instance class,
- any UuidInterface instance could have been decorated and not be the
same class name,
- all unit tests did build its objects using the same method,
guaranting the same deep-equality, but the final application doesn't
guarantee that all objects will be created the same way.
I spotted this during a review, and told him "that's weird, it should
not work" and we had an infernal discussion until he found the
https://www.php.net/manual/en/language.oop5.object-comparison.php page,
but I finally dissuaded him to proceed this way and use the ->equals()
method instead, which is an explicit and clear contract.
This implicit deep object equality in PHP could have caused some serious
damage later in production, if, for any reason, we would have decorated
some UuidInterface instances, or just instanciated those in many ways.
Wouldn't it be a good reason to implement an Equatable/Comparable
interface in PHP, or a magic __compareTo() or __equals() method to allow
us, in such scenario, to write a robust and predictible == and != behavior ?
This already existing RFC https://wiki.php.net/rfc/comparator_interface
might not be the best one, but it's one to start with.
Regards,
--
Pierre