From 2bbfc7fa08c8411365b5e879046f384cb4595cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 20 Aug 2026 09:45:54 +0200 Subject: [PATCH] fix(shared): prevent cross-type equality in EntityId::equals (LSP) --- src/Shared/DDD/Model/EntityId.php | 2 +- tests/Unit/Shared/DDD/EntityIdTest.php | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Shared/DDD/Model/EntityId.php b/src/Shared/DDD/Model/EntityId.php index 0c1e390..897bc06 100644 --- a/src/Shared/DDD/Model/EntityId.php +++ b/src/Shared/DDD/Model/EntityId.php @@ -23,6 +23,6 @@ abstract readonly class EntityId implements \Stringable, Equatable #[\Override] public function equals(mixed $other): bool { - return $other instanceof self && $this->value === $other->value; + return $other instanceof static && $this->value === $other->value; } } diff --git a/tests/Unit/Shared/DDD/EntityIdTest.php b/tests/Unit/Shared/DDD/EntityIdTest.php index 53a8f36..d18545a 100644 --- a/tests/Unit/Shared/DDD/EntityIdTest.php +++ b/tests/Unit/Shared/DDD/EntityIdTest.php @@ -33,4 +33,28 @@ describe('EntityId', function (): void { { })->toThrow(\DomainException::class); }); + + it('correctly compares equality for identical and distinct instances', function (): void { + // GIVEN + $classA1 = new DummyEntityIdA('uuid-1'); + $classA2 = new DummyEntityIdA('uuid-1'); + $classA3 = new DummyEntityIdA('uuid-2'); + $classB = new DummyEntityIdB('uuid-1'); + + // WHEN & THEN + expect($classA1->equals($classA2))->toBeTrue() + ->and($classA1->equals($classA3))->toBeFalse() + ->and($classA1->equals($classB))->toBeFalse() + ->and($classB->equals($classA1))->toBeFalse() + ->and($classA1->equals('uuid-1'))->toBeFalse() + ->and($classA1->equals(null))->toBeFalse(); + }); }); + +final readonly class DummyEntityIdA extends EntityId +{ +} + +final readonly class DummyEntityIdB extends EntityId +{ +}