Skip to content

[Uid] use one class per type of UUID #36066

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/Symfony/Component/Uid/NullUuid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Uid;

/**
* @experimental in 5.1
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class NullUuid extends Uuid
{
protected const TYPE = UUID_TYPE_NULL;

public function __construct()
{
$this->uuid = '00000000-0000-0000-0000-000000000000';
}
}
2 changes: 1 addition & 1 deletion src/Symfony/Component/Uid/Tests/UlidTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public function testBinary()
$ulid = new Ulid('3zzzzzzzzzzzzzzzzzzzzzzzzz');
$this->assertSame('7fffffffffffffffffffffffffffffff', bin2hex($ulid->toBinary()));

$this->assertTrue($ulid->equals(Ulid::fromBinary(hex2bin('7fffffffffffffffffffffffffffffff'))));
$this->assertTrue($ulid->equals(Ulid::fromString(hex2bin('7fffffffffffffffffffffffffffffff'))));
}

/**
Expand Down
58 changes: 31 additions & 27 deletions src/Symfony/Component/Uid/Tests/UuidTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
namespace Symfony\Tests\Component\Uid;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Uid\NullUuid;
use Symfony\Component\Uid\Uuid;
use Symfony\Component\Uid\UuidV1;
use Symfony\Component\Uid\UuidV3;
use Symfony\Component\Uid\UuidV4;
use Symfony\Component\Uid\UuidV5;

class UuidTest extends TestCase
{
Expand All @@ -24,12 +29,12 @@ public function testConstructorWithInvalidUuid()
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid UUID: "this is not a uuid".');

new Uuid('this is not a uuid');
Uuid::fromString('this is not a uuid');
}

public function testConstructorWithValidUuid()
{
$uuid = new Uuid(self::A_UUID_V4);
$uuid = new UuidV4(self::A_UUID_V4);

$this->assertSame(self::A_UUID_V4, (string) $uuid);
$this->assertSame('"'.self::A_UUID_V4.'"', json_encode($uuid));
Expand All @@ -39,56 +44,56 @@ public function testV1()
{
$uuid = Uuid::v1();

$this->assertSame(Uuid::TYPE_1, $uuid->getType());
$this->assertInstanceOf(UuidV1::class, $uuid);

$uuid = new UuidV1(self::A_UUID_V1);

$this->assertSame(1583245966.746458, $uuid->getTime());
$this->assertSame('3499710062d0', $uuid->getNode());
}

public function testV3()
{
$uuid = Uuid::v3(new Uuid(self::A_UUID_V4), 'the name');
$uuid = Uuid::v3(new UuidV4(self::A_UUID_V4), 'the name');

$this->assertSame(Uuid::TYPE_3, $uuid->getType());
$this->assertInstanceOf(UuidV3::class, $uuid);
}

public function testV4()
{
$uuid = Uuid::v4();

$this->assertSame(Uuid::TYPE_4, $uuid->getType());
$this->assertInstanceOf(UuidV4::class, $uuid);
}

public function testV5()
{
$uuid = Uuid::v5(new Uuid(self::A_UUID_V4), 'the name');
$uuid = Uuid::v5(new UuidV4(self::A_UUID_V4), 'the name');

$this->assertSame(Uuid::TYPE_5, $uuid->getType());
$this->assertInstanceOf(UuidV5::class, $uuid);
}

public function testBinary()
{
$uuid = new Uuid(self::A_UUID_V4);
$uuid = new UuidV4(self::A_UUID_V4);
$uuid = Uuid::fromString($uuid->toBinary());

$this->assertSame(self::A_UUID_V4, (string) Uuid::fromBinary($uuid->toBinary()));
$this->assertInstanceOf(UuidV4::class, $uuid);
$this->assertSame(self::A_UUID_V4, (string) $uuid);
}

public function testIsValid()
{
$this->assertFalse(Uuid::isValid('not a uuid'));
$this->assertTrue(Uuid::isValid(self::A_UUID_V4));
}

public function testIsNull()
{
$uuid = new Uuid(self::A_UUID_V1);
$this->assertFalse($uuid->isNull());

$uuid = new Uuid('00000000-0000-0000-0000-000000000000');
$this->assertTrue($uuid->isNull());
$this->assertFalse(UuidV4::isValid(self::A_UUID_V1));
$this->assertTrue(UuidV4::isValid(self::A_UUID_V4));
}

public function testEquals()
{
$uuid1 = new Uuid(self::A_UUID_V1);
$uuid2 = new Uuid(self::A_UUID_V4);
$uuid1 = new UuidV1(self::A_UUID_V1);
$uuid2 = new UuidV4(self::A_UUID_V4);

$this->assertTrue($uuid1->equals($uuid1));
$this->assertFalse($uuid1->equals($uuid2));
Expand All @@ -99,7 +104,7 @@ public function testEquals()
*/
public function testEqualsAgainstOtherType($other)
{
$this->assertFalse((new Uuid(self::A_UUID_V4))->equals($other));
$this->assertFalse((new UuidV4(self::A_UUID_V4))->equals($other));
}

public function provideInvalidEqualType()
Expand Down Expand Up @@ -128,12 +133,11 @@ public function testCompare()
$this->assertSame([$a, $b, $c, $d], $uuids);
}

public function testExtraMethods()
public function testNullUuid()
{
$uuid = new Uuid(self::A_UUID_V1);
$uuid = Uuid::fromString('00000000-0000-0000-0000-000000000000');

$this->assertSame(1583245966.746458, $uuid->getTime());
$this->assertSame('3499710062d0', $uuid->getMac());
$this->assertSame(self::A_UUID_V1, (string) $uuid);
$this->assertInstanceOf(NullUuid::class, $uuid);
$this->assertSame('00000000-0000-0000-0000-000000000000', (string) $uuid);
}
}
4 changes: 2 additions & 2 deletions src/Symfony/Component/Uid/Ulid.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ public static function isValid(string $ulid): bool
return $ulid[0] <= '7';
}

public static function fromBinary(string $ulid): self
public static function fromString(string $ulid): self
{
if (16 !== \strlen($ulid)) {
throw new \InvalidArgumentException('Invalid binary ULID.');
return new static($ulid);
}

$ulid = bin2hex($ulid);
Expand Down
109 changes: 40 additions & 69 deletions src/Symfony/Component/Uid/Uuid.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,74 +18,78 @@
*/
class Uuid implements \JsonSerializable
{
public const TYPE_1 = UUID_TYPE_TIME;
public const TYPE_3 = UUID_TYPE_MD5;
public const TYPE_4 = UUID_TYPE_RANDOM;
public const TYPE_5 = UUID_TYPE_SHA1;
protected const TYPE = UUID_TYPE_DEFAULT;

// https://tools.ietf.org/html/rfc4122#section-4.1.4
// 0x01b21dd213814000 is the number of 100-ns intervals between the
// UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
private const TIME_OFFSET_INT = 0x01b21dd213814000;
private const TIME_OFFSET_COM = "\xfe\x4d\xe2\x2d\xec\x7e\xc0\x00";
protected $uuid;

private $uuid;

public function __construct(string $uuid = null)
public function __construct(string $uuid)
{
if (null === $uuid) {
$this->uuid = uuid_create(self::TYPE_4);

return;
}

if (!uuid_is_valid($uuid)) {
throw new \InvalidArgumentException(sprintf('Invalid UUID: "%s".', $uuid));
if (static::TYPE !== uuid_type($uuid)) {
throw new \InvalidArgumentException(sprintf('Invalid UUID%s: "%s".', static::TYPE ? 'v'.static::TYPE : '', $uuid));
}

$this->uuid = strtr($uuid, 'ABCDEF', 'abcdef');
}

public static function v1(): self
/**
* @return static
*/
public static function fromString(string $uuid): self
{
return new self(uuid_create(self::TYPE_1));
if (16 === \strlen($uuid)) {
$uuid = uuid_unparse($uuid);
}

if (__CLASS__ !== static::class) {
return new static($uuid);
}

switch (uuid_type($uuid)) {
case UuidV1::TYPE: return new UuidV1($uuid);
case UuidV3::TYPE: return new UuidV3($uuid);
case UuidV4::TYPE: return new UuidV4($uuid);
case UuidV5::TYPE: return new UuidV5($uuid);
case NullUuid::TYPE: return new NullUuid();
case self::TYPE: return new self($uuid);
}

throw new \InvalidArgumentException(sprintf('Invalid UUID: "%s".', $uuid));
}

public static function v3(self $uuidNamespace, string $name): self
final public static function v1(): UuidV1
{
return new self(uuid_generate_md5($uuidNamespace->uuid, $name));
return new UuidV1();
}

public static function v4(): self
final public static function v3(self $namespace, string $name): UuidV3
{
return new self(uuid_create(self::TYPE_4));
return new UuidV3(uuid_generate_md5($namespace->uuid, $name));
}

public static function v5(self $uuidNamespace, string $name): self
final public static function v4(): UuidV4
{
return new self(uuid_generate_sha1($uuidNamespace->uuid, $name));
return new UuidV4();
}

public static function fromBinary(string $uuidAsBinary): self
final public static function v5(self $namespace, string $name): UuidV5
{
return new self(uuid_unparse($uuidAsBinary));
return new UuidV5(uuid_generate_sha1($namespace->uuid, $name));
}

public static function isValid(string $uuid): bool
{
return uuid_is_valid($uuid);
if (__CLASS__ === static::class) {
return uuid_is_valid($uuid);
}

return static::TYPE === uuid_type($uuid);
}

public function toBinary(): string
{
return uuid_parse($this->uuid);
}

public function isNull(): bool
{
return uuid_is_null($this->uuid);
}

/**
* Returns whether the argument is of class Uuid and contains the same value as the current instance.
*/
Expand All @@ -103,39 +107,6 @@ public function compare(self $other): int
return uuid_compare($this->uuid, $other->uuid);
}

public function getType(): int
{
return uuid_type($this->uuid);
}

public function getTime(): float
{
if (self::TYPE_1 !== $t = uuid_type($this->uuid)) {
throw new \LogicException("UUID of type $t doesn't contain a time.");
}

$time = '0'.substr($this->uuid, 15, 3).substr($this->uuid, 9, 4).substr($this->uuid, 0, 8);

if (\PHP_INT_SIZE >= 8) {
return (hexdec($time) - self::TIME_OFFSET_INT) / 10000000;
}

$time = str_pad(hex2bin($time), 8, "\0", STR_PAD_LEFT);
$time = BinaryUtil::add($time, self::TIME_OFFSET_COM);
$time[0] = $time[0] & "\x7F";

return BinaryUtil::toBase($time, BinaryUtil::BASE10) / 10000000;
}

public function getMac(): string
{
if (self::TYPE_1 !== $t = uuid_type($this->uuid)) {
throw new \LogicException("UUID of type $t doesn't contain a MAC.");
}

return uuid_mac($this->uuid);
}

public function __toString(): string
{
return $this->uuid;
Expand Down
59 changes: 59 additions & 0 deletions src/Symfony/Component/Uid/UuidV1.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Uid;

/**
* A v1 UUID contains a 60-bit timestamp and ~60 extra unique bits.
*
* @experimental in 5.1
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class UuidV1 extends Uuid
{
protected const TYPE = UUID_TYPE_TIME;

// https://tools.ietf.org/html/rfc4122#section-4.1.4
// 0x01b21dd213814000 is the number of 100-ns intervals between the
// UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
private const TIME_OFFSET_INT = 0x01b21dd213814000;
private const TIME_OFFSET_COM = "\xfe\x4d\xe2\x2d\xec\x7e\xc0\x00";

public function __construct(string $uuid = null)
{
if (null === $uuid) {
$this->uuid = uuid_create(static::TYPE);
} else {
parent::__construct($uuid);
}
}

public function getTime(): float
{
$time = '0'.substr($this->uuid, 15, 3).substr($this->uuid, 9, 4).substr($this->uuid, 0, 8);

if (\PHP_INT_SIZE >= 8) {
return (hexdec($time) - self::TIME_OFFSET_INT) / 10000000;
}

$time = str_pad(hex2bin($time), 8, "\0", STR_PAD_LEFT);
$time = BinaryUtil::add($time, self::TIME_OFFSET_COM);
$time[0] = $time[0] & "\x7F";

return BinaryUtil::toBase($time, BinaryUtil::BASE10) / 10000000;
}

public function getNode(): string
{
return uuid_mac($this->uuid);
}
}
Loading