From ac3f83acbdaa24ece6fa894d35bf32d848eb8337 Mon Sep 17 00:00:00 2001 From: Valentin Udaltsov Date: Wed, 4 Sep 2024 12:35:52 +0300 Subject: [PATCH] Improve PerfectHasher and its coverage --- src/Internal/PerfectHasher.php | 19 ++++++------ tests/Internal/PerfectHasherTest.php | 43 +++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/Internal/PerfectHasher.php b/src/Internal/PerfectHasher.php index 99e1340..671dea1 100644 --- a/src/Internal/PerfectHasher.php +++ b/src/Internal/PerfectHasher.php @@ -49,7 +49,7 @@ public static function global(): self private \Closure $defaultObjectHasher; /** - * @var array + * @var array */ private array $objectHashers = []; @@ -110,7 +110,7 @@ public function hash(mixed $value): int|string } if (\is_object($value)) { - return $this->objectHashes[$value] ??= $this->hashObject($value); + return $this->objectHashes[$value] ??= $this->objectHasher($value::class)($value, $this); } if ($value === null) { @@ -153,28 +153,27 @@ public function hash(mixed $value): int|string } /** - * @return non-empty-string + * @param class-string $class + * @return \Closure(object, self): non-empty-string */ - private function hashObject(object $object): string + private function objectHasher(string $class): \Closure { - $class = $object::class; - if (isset($this->objectHashers[$class])) { - return $this->objectHashers[$class]($object, $this); + return $this->objectHashers[$class]; } foreach (class_parents($class) as $parent) { if (isset($this->objectHashers[$parent])) { - return ($this->objectHashers[$class] = $this->objectHashers[$parent])($object, $this); + return $this->objectHashers[$class] = $this->objectHashers[$parent]; } } foreach (class_implements($class) as $interface) { if (isset($this->objectHashers[$interface])) { - return ($this->objectHashers[$class] = $this->objectHashers[$interface])($object, $this); + return $this->objectHashers[$class] = $this->objectHashers[$interface]; } } - return ($this->objectHashers[$class] = $this->defaultObjectHasher)($object); + return $this->objectHashers[$class] = $this->defaultObjectHasher; } } diff --git a/tests/Internal/PerfectHasherTest.php b/tests/Internal/PerfectHasherTest.php index 667276a..faa9c61 100644 --- a/tests/Internal/PerfectHasherTest.php +++ b/tests/Internal/PerfectHasherTest.php @@ -60,7 +60,7 @@ public function testObject(): void self::assertSame('#' . spl_object_id($this), $hash); } - public function testObjectWithCustomEncoder(): void + public function testObjectWithCustomNormalizer(): void { $hasher = new PerfectHasher(); $hasher->registerObjectNormalizer(\Throwable::class, static fn(\Throwable $exception): string => $exception->getMessage()); @@ -74,6 +74,41 @@ public function testObjectWithCustomEncoder(): void self::assertSame('RangeException|UnexpectedValueException@`unexpected_value`', $hasher->hash(new \UnexpectedValueException('unexpected_value'))); } + public function testObjectHashIsMemoizedForSameInstance(): void + { + $hasher = new PerfectHasher(); + $hasher->registerObjectNormalizer(\stdClass::class, static function (): int { + static $hash = 0; + + return $hash++; + }); + $object = new \stdClass(); + + $hash1 = $hasher->hash($object); + $hash2 = $hasher->hash($object); + + self::assertSame('stdClass@0', $hash1); + self::assertSame('stdClass@0', $hash2); + } + + public function testMemoizedObjectHashIsNotSameForDifferentInstancesOfTheSameClass(): void + { + $hasher = new PerfectHasher(); + $hasher->registerObjectNormalizer(\stdClass::class, static function (): int { + static $hash = 0; + + return $hash++; + }); + $object1 = new \stdClass(); + $object2 = clone $object1; + + $hash1 = $hasher->hash($object1); + $hash2 = $hasher->hash($object2); + + self::assertSame('stdClass@0', $hash1); + self::assertSame('stdClass@1', $hash2); + } + /** * @param non-empty-string $prefix */ @@ -81,7 +116,7 @@ public function testObjectWithCustomEncoder(): void #[TestWith([self::class])] #[TestWith(['123'])] #[TestWith(['a.b.c'])] - public function testRegisterObjectEncoderAcceptsValidPrefix(string $prefix): void + public function testRegisterObjectNormalizerAcceptsValidPrefix(string $prefix): void { $hasher = new PerfectHasher(); @@ -98,7 +133,7 @@ public function testRegisterObjectEncoderAcceptsValidPrefix(string $prefix): voi #[TestWith(['#'])] #[TestWith(['@'])] #[TestWith([','])] - public function testRegisterObjectEncoderThrowsOnInvalidPrefix(string $prefix): void + public function testRegisterObjectNormalizerThrowsOnInvalidPrefix(string $prefix): void { $hasher = new PerfectHasher(); @@ -107,7 +142,7 @@ public function testRegisterObjectEncoderThrowsOnInvalidPrefix(string $prefix): $hasher->registerObjectNormalizer(self::class, static fn(): bool => true, $prefix); } - public function testRegisterObjectEncoderThrowsAfterEncoding(): void + public function testRegisterObjectNormalizerThrowsAfterHashing(): void { $hasher = new PerfectHasher(); $hasher->hash(1);