From 6ca8b83f1796bb847869171512e6817ec45e4c2a Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 26 Aug 2026 14:32:30 +0200 Subject: [PATCH] Introduce `UUID` behavior --- composer.json | 3 +- src/Behavior/UUID.php | 73 +++++++++++++++++++ src/Contract/QueryAwareBehavior.php | 2 +- tests/Behavior/UUIDTest.php | 109 ++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 src/Behavior/UUID.php create mode 100644 tests/Behavior/UUIDTest.php diff --git a/composer.json b/composer.json index 15227808..ba9a7989 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "php": ">=8.2", "ext-pdo": "*", "ipl/sql": ">=0.9.1", - "ipl/stdlib": ">=0.15.0" + "ipl/stdlib": ">=0.15.0", + "ramsey/uuid": "^4.9.2" }, "require-dev": { "ext-pdo_sqlite": "*", diff --git a/src/Behavior/UUID.php b/src/Behavior/UUID.php new file mode 100644 index 00000000..85072e1b --- /dev/null +++ b/src/Behavior/UUID.php @@ -0,0 +1,73 @@ +isPostgres) { + return RamseyUuid::fromBytes($value); + } + + return RamseyUuid::fromString($value); + } catch (InvalidArgumentException $e) { + throw new ValueConversionException($e->getMessage()); + } + } + + public function toDb($value, $key, $_) + { + if ($value === null || $value instanceof ExpressionInterface) { + return $value; + } + + if (! $value instanceof UuidInterface) { + if (! is_string($value)) { + throw new UnexpectedValueException(sprintf("Unexpected value '%s' for key '%s'", $value, $key)); + } + + try { + $value = RamseyUuid::fromString($value); + } catch (InvalidArgumentException $_) { + throw new ValueConversionException(sprintf("Invalid UUID value provided: %s", $value)); + } + } + + if (! $this->isPostgres) { + return $value->getBytes(); + } + + return $value->toString(); + } + + public function setQuery(Query $query) + { + $this->isPostgres = $query->getDb()->getAdapter() instanceof Pgsql; + + return $this; + } +} diff --git a/src/Contract/QueryAwareBehavior.php b/src/Contract/QueryAwareBehavior.php index b67bf515..1d143c1f 100644 --- a/src/Contract/QueryAwareBehavior.php +++ b/src/Contract/QueryAwareBehavior.php @@ -10,7 +10,7 @@ interface QueryAwareBehavior extends Behavior /** * Set the query * - * @param Query $query + * @param Query<*> $query * * @return $this */ diff --git a/tests/Behavior/UUIDTest.php b/tests/Behavior/UUIDTest.php new file mode 100644 index 00000000..39af4363 --- /dev/null +++ b/tests/Behavior/UUIDTest.php @@ -0,0 +1,109 @@ +assertNull($this->behavior()->retrieveProperty(null, static::TEST_COLUMN)); + $this->assertNull($this->behavior(true)->retrieveProperty(null, static::TEST_COLUMN)); + } + + public function testRetrievePropertyReturnsUuidInstance(): void + { + $this->assertSame( + static::TEST_UUID_VALUE, + (string) $this->behavior()->retrieveProperty( + RamseyUuid::fromString(static::TEST_UUID_VALUE)->getBytes(), + static::TEST_COLUMN, + ) + ); + $this->assertSame( + static::TEST_UUID_VALUE, + (string) $this->behavior(true)->retrieveProperty(static::TEST_UUID_VALUE, static::TEST_COLUMN) + ); + } + + public function testPersistPropertyReturnsNullWhenValueIsNull(): void + { + $this->assertNull($this->behavior()->persistProperty(null, static::TEST_COLUMN)); + $this->assertNull($this->behavior(true)->persistProperty(null, static::TEST_COLUMN)); + } + + public function testPersistPropertyReturnsValidValueFromString(): void + { + $uuid = RamseyUuid::fromString(static::TEST_UUID_VALUE); + $this->assertSame( + $uuid->getBytes(), + $this->behavior()->persistProperty(static::TEST_UUID_VALUE, static::TEST_COLUMN) + ); + $this->assertSame( + static::TEST_UUID_VALUE, + $this->behavior(true)->persistProperty(static::TEST_UUID_VALUE, static::TEST_COLUMN) + ); + } + + public function testPersistPropertyReturnsValidValueFromUuid(): void + { + $uuid = RamseyUuid::fromString(static::TEST_UUID_VALUE); + $this->assertSame( + $uuid->getBytes(), + $this->behavior()->persistProperty($uuid, static::TEST_COLUMN) + ); + $this->assertSame( + static::TEST_UUID_VALUE, + $this->behavior(true)->persistProperty($uuid, static::TEST_COLUMN), + ); + } + + public function testRetrievePropertyThrowsWithInvalidValueType(): void + { + $this->expectException(UnexpectedValueException::class); + $this->behavior()->retrieveProperty(true, static::TEST_COLUMN); + $this->behavior(true)->retrieveProperty(false, static::TEST_COLUMN); + } + + public function testPersistPropertyThrowsWithInvalidValueType(): void + { + $this->expectException(UnexpectedValueException::class); + $this->behavior()->persistProperty(true, static::TEST_COLUMN); + $this->behavior(true)->persistProperty(false, static::TEST_COLUMN); + } + + public function testRetrievePropertyThrowsWithInvalidUuid(): void + { + $this->expectException(ValueConversionException::class); + $this->behavior()->retrieveProperty('something', static::TEST_COLUMN); + $this->behavior(true)->retrieveProperty('something', static::TEST_COLUMN); + } + + public function testPersistPropertyThrowsWithInvalidUuid(): void + { + $this->expectException(ValueConversionException::class); + $this->behavior()->persistProperty('something', static::TEST_COLUMN); + $this->behavior(true)->persistProperty('something', static::TEST_COLUMN); + } + + protected function behavior(bool $postgres = false): UUID + { + return (new UUID([static::TEST_COLUMN])) + ->setQuery( + (new Query()) + ->setDb($postgres ? new Connection(['db' => 'pgsql']) : new TestConnection()) + ); + } +}