Skip to content

Commit d310802

Browse files
feature symfony#57940 [Uid] Add support for binary, base-32 and base-58 representations in Uuid::isValid() (alexandre-daubois)
This PR was merged into the 7.2 branch. Discussion ---------- [Uid] Add support for binary, base-32 and base-58 representations in `Uuid::isValid()` | Q | A | ------------- | --- | Branch? | 7.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Fix symfony#57398 | License | MIT Allows to use a bitfield in `Uuid::isValid()` to accept one or many formats: ```php Uuid::isValid('...', Uuid::FORMAT_BASE_32 | Uuid::FORMAT_RFC_4122); Uuid::isValid('...', Uuid::FORMAT_ALL); UUid::isValid('...'); // by default, FORMAT_RFC_4122 is used to match the current behavior ``` Commits ------- 64aa006 [Uid] Add support for binary, base-32 and base-58 representations in `Uuid::isValid()`
2 parents 5df2af1 + 64aa006 commit d310802

File tree

3 files changed

+84
-17
lines changed

3 files changed

+84
-17
lines changed

src/Symfony/Component/Uid/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Make `AbstractUid` implement `Ds\Hashable` if available
8+
* Add support for binary, base-32 and base-58 representations in `Uuid::isValid()`
89

910
7.1
1011
---

src/Symfony/Component/Uid/Tests/UuidTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,38 @@ public function testIsValid()
213213
$this->assertTrue(UuidV4::isValid(self::A_UUID_V4));
214214
}
215215

216+
public function testIsValidWithVariousFormat()
217+
{
218+
$uuid = Uuid::v4();
219+
220+
$this->assertTrue(Uuid::isValid($uuid->toBase32(), Uuid::FORMAT_BASE_32));
221+
$this->assertFalse(Uuid::isValid($uuid->toBase58(), Uuid::FORMAT_BASE_32));
222+
$this->assertFalse(Uuid::isValid($uuid->toBinary(), Uuid::FORMAT_BASE_32));
223+
$this->assertFalse(Uuid::isValid($uuid->toRfc4122(), Uuid::FORMAT_BASE_32));
224+
225+
$this->assertFalse(Uuid::isValid($uuid->toBase32(), Uuid::FORMAT_BASE_58));
226+
$this->assertTrue(Uuid::isValid($uuid->toBase58(), Uuid::FORMAT_BASE_58));
227+
$this->assertFalse(Uuid::isValid($uuid->toBinary(), Uuid::FORMAT_BASE_58));
228+
$this->assertFalse(Uuid::isValid($uuid->toRfc4122(), Uuid::FORMAT_BASE_58));
229+
230+
$this->assertFalse(Uuid::isValid($uuid->toBase32(), Uuid::FORMAT_BINARY));
231+
$this->assertFalse(Uuid::isValid($uuid->toBase58(), Uuid::FORMAT_BINARY));
232+
$this->assertTrue(Uuid::isValid($uuid->toBinary(), Uuid::FORMAT_BINARY));
233+
$this->assertFalse(Uuid::isValid($uuid->toRfc4122(), Uuid::FORMAT_BINARY));
234+
235+
$this->assertFalse(Uuid::isValid($uuid->toBase32(), Uuid::FORMAT_RFC_4122));
236+
$this->assertFalse(Uuid::isValid($uuid->toBase58(), Uuid::FORMAT_RFC_4122));
237+
$this->assertFalse(Uuid::isValid($uuid->toBinary(), Uuid::FORMAT_RFC_4122));
238+
$this->assertTrue(Uuid::isValid($uuid->toRfc4122(), Uuid::FORMAT_RFC_4122));
239+
240+
$this->assertTrue(Uuid::isValid($uuid->toBase32(), Uuid::FORMAT_ALL));
241+
$this->assertTrue(Uuid::isValid($uuid->toBase58(), Uuid::FORMAT_ALL));
242+
$this->assertTrue(Uuid::isValid($uuid->toBinary(), Uuid::FORMAT_ALL));
243+
$this->assertTrue(Uuid::isValid($uuid->toRfc4122(), Uuid::FORMAT_ALL));
244+
245+
$this->assertFalse(Uuid::isValid('30J7CNpDMfXPZrCsn4Cgey', Uuid::FORMAT_BASE_58), 'Fake base-58 string with the "O" forbidden char is not valid');
246+
}
247+
216248
public function testIsValidWithNilUuid()
217249
{
218250
$this->assertTrue(Uuid::isValid('00000000-0000-0000-0000-000000000000'));

src/Symfony/Component/Uid/Uuid.php

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ class Uuid extends AbstractUid
2323
public const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8';
2424
public const NAMESPACE_X500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8';
2525

26+
public const FORMAT_BINARY = 1;
27+
public const FORMAT_BASE_32 = 1 << 1;
28+
public const FORMAT_BASE_58 = 1 << 2;
29+
public const FORMAT_RFC_4122 = 1 << 3;
30+
public const FORMAT_ALL = -1;
31+
2632
protected const TYPE = 0;
2733
protected const NIL = '00000000-0000-0000-0000-000000000000';
2834
protected const MAX = 'ffffffff-ffff-ffff-ffff-ffffffffffff';
@@ -44,22 +50,7 @@ public function __construct(string $uuid, bool $checkVariant = false)
4450

4551
public static function fromString(string $uuid): static
4652
{
47-
if (22 === \strlen($uuid) && 22 === strspn($uuid, BinaryUtil::BASE58[''])) {
48-
$uuid = str_pad(BinaryUtil::fromBase($uuid, BinaryUtil::BASE58), 16, "\0", \STR_PAD_LEFT);
49-
}
50-
51-
if (16 === \strlen($uuid)) {
52-
// don't use uuid_unparse(), it's slower
53-
$uuid = bin2hex($uuid);
54-
$uuid = substr_replace($uuid, '-', 8, 0);
55-
$uuid = substr_replace($uuid, '-', 13, 0);
56-
$uuid = substr_replace($uuid, '-', 18, 0);
57-
$uuid = substr_replace($uuid, '-', 23, 0);
58-
} elseif (26 === \strlen($uuid) && Ulid::isValid($uuid)) {
59-
$ulid = new NilUlid();
60-
$ulid->uid = strtoupper($uuid);
61-
$uuid = $ulid->toRfc4122();
62-
}
53+
$uuid = self::transformToRfc4122($uuid, self::FORMAT_ALL);
6354

6455
if (__CLASS__ !== static::class || 36 !== \strlen($uuid)) {
6556
return new static($uuid);
@@ -130,8 +121,19 @@ final public static function v8(string $uuid): UuidV8
130121
return new UuidV8($uuid);
131122
}
132123

133-
public static function isValid(string $uuid): bool
124+
/**
125+
* @param int-mask-of<Uuid::FORMAT_*> $format
126+
*/
127+
public static function isValid(string $uuid /*, int $format = self::FORMAT_RFC_4122 */): bool
134128
{
129+
$format = 1 < \func_num_args() ? func_get_arg(1) : self::FORMAT_RFC_4122;
130+
131+
if (36 === \strlen($uuid) && !($format & self::FORMAT_RFC_4122)) {
132+
return false;
133+
}
134+
135+
$uuid = self::transformToRfc4122($uuid, $format);
136+
135137
if (self::NIL === $uuid && \in_array(static::class, [__CLASS__, NilUuid::class], true)) {
136138
return true;
137139
}
@@ -182,4 +184,36 @@ private static function format(string $uuid, string $version): string
182184

183185
return substr_replace($uuid, '-', 23, 0);
184186
}
187+
188+
/**
189+
* Transforms a binary string, a base-32 string or a base-58 string to a RFC4122 string.
190+
*
191+
* @param int-mask-of<Uuid::FORMAT_*> $format
192+
*
193+
* @return non-empty-string
194+
*/
195+
private static function transformToRfc4122(string $uuid, int $format): string
196+
{
197+
$fromBase58 = false;
198+
if (22 === \strlen($uuid) && 22 === strspn($uuid, BinaryUtil::BASE58['']) && $format & self::FORMAT_BASE_58) {
199+
$uuid = str_pad(BinaryUtil::fromBase($uuid, BinaryUtil::BASE58), 16, "\0", \STR_PAD_LEFT);
200+
$fromBase58 = true;
201+
}
202+
203+
// base-58 are always transformed to binary string, but they must only be valid when the format is FORMAT_BASE_58
204+
if (16 === \strlen($uuid) && $format & self::FORMAT_BINARY || $fromBase58 && $format & self::FORMAT_BASE_58) {
205+
// don't use uuid_unparse(), it's slower
206+
$uuid = bin2hex($uuid);
207+
$uuid = substr_replace($uuid, '-', 8, 0);
208+
$uuid = substr_replace($uuid, '-', 13, 0);
209+
$uuid = substr_replace($uuid, '-', 18, 0);
210+
$uuid = substr_replace($uuid, '-', 23, 0);
211+
} elseif (26 === \strlen($uuid) && Ulid::isValid($uuid) && $format & self::FORMAT_BASE_32) {
212+
$ulid = new NilUlid();
213+
$ulid->uid = strtoupper($uuid);
214+
$uuid = $ulid->toRfc4122();
215+
}
216+
217+
return $uuid;
218+
}
185219
}

0 commit comments

Comments
 (0)