Updated Symfony/Uid to version 7.1.4

This commit is contained in:
netkas 2024-09-19 15:10:40 -04:00
parent d59f5fb6e0
commit 910477df8e
19 changed files with 170 additions and 70 deletions

View file

@ -68,6 +68,7 @@ This update introduces a refactored code-base, code quality improvements, and be
- Rename 'semver' directory to 'Semver' in composer package
- Refactor project constants handling in NccCompiler
- Updated Symfony/Yaml to version 7.1.4
- Updated Symfony/Uid to version 7.1.4
### Fixed
- Fixed Division by zero in PackageManager

View file

@ -14,12 +14,12 @@ namespace ncc\ThirdParty\Symfony\Uid;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
abstract class AbstractUid implements \JsonSerializable
abstract class AbstractUid implements \JsonSerializable, \Stringable
{
/**
* The identifier in its canonic representation.
*/
protected $uid;
protected string $uid;
/**
* Whether the passed value is valid for the constructor of the current class.
@ -70,6 +70,8 @@ abstract class AbstractUid implements \JsonSerializable
}
/**
* @param string $uid A valid RFC 9562/4122 uid
*
* @throws \InvalidArgumentException When the passed value is not valid
*/
public static function fromRfc4122(string $uid): static
@ -87,7 +89,9 @@ abstract class AbstractUid implements \JsonSerializable
abstract public function toBinary(): string;
/**
* Returns the identifier as a base58 case sensitive string.
* Returns the identifier as a base58 case-sensitive string.
*
* @example 2AifFTC3zXgZzK5fPrrprL (len=22)
*/
public function toBase58(): string
{
@ -95,7 +99,11 @@ abstract class AbstractUid implements \JsonSerializable
}
/**
* Returns the identifier as a base32 case insensitive string.
* Returns the identifier as a base32 case-insensitive string.
*
* @see https://tools.ietf.org/html/rfc4648#section-6
*
* @example 09EJ0S614A9FXVG9C5537Q9ZE1 (len=26)
*/
public function toBase32(): string
{
@ -114,7 +122,11 @@ abstract class AbstractUid implements \JsonSerializable
}
/**
* Returns the identifier as a RFC4122 case insensitive string.
* Returns the identifier as a RFC 9562/4122 case-insensitive string.
*
* @see https://tools.ietf.org/html/rfc4122#section-3
*
* @example 09748193-048a-4bfb-b825-8528cf74fdc1 (len=36)
*/
public function toRfc4122(): string
{
@ -129,6 +141,8 @@ abstract class AbstractUid implements \JsonSerializable
/**
* Returns the identifier as a prefixed hexadecimal case insensitive string.
*
* @example 0x09748193048a4bfbb8258528cf74fdc1 (len=34)
*/
public function toHex(): string
{
@ -152,6 +166,11 @@ abstract class AbstractUid implements \JsonSerializable
return (\strlen($this->uid) - \strlen($other->uid)) ?: ($this->uid <=> $other->uid);
}
final public function toString(): string
{
return $this->__toString();
}
public function __toString(): string
{
return $this->uid;

View file

@ -36,7 +36,7 @@ class BinaryUtil
'u' => 52, 'v' => 53, 'w' => 54, 'x' => 55, 'y' => 56, 'z' => 57,
];
// https://tools.ietf.org/html/rfc4122#section-4.1.4
// https://datatracker.ietf.org/doc/html/rfc9562#section-5.1
// 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;
@ -118,8 +118,10 @@ class BinaryUtil
/**
* @param string $time Count of 100-nanosecond intervals since the UUID epoch 1582-10-15 00:00:00 in hexadecimal
*
* @return string Count of 100-nanosecond intervals since the UUID epoch 1582-10-15 00:00:00 as a numeric string
*/
public static function hexToDateTime(string $time): \DateTimeImmutable
public static function hexToNumericString(string $time): string
{
if (\PHP_INT_SIZE >= 8) {
$time = (string) (hexdec($time) - self::TIME_OFFSET_INT);
@ -140,7 +142,17 @@ class BinaryUtil
$time = '-' === $time[0] ? '-'.str_pad(substr($time, 1), 8, '0', \STR_PAD_LEFT) : str_pad($time, 8, '0', \STR_PAD_LEFT);
}
return \DateTimeImmutable::createFromFormat('U.u?', substr_replace($time, '.', -7, 0));
return $time;
}
/**
* Sub-microseconds are lost since they are not handled by \DateTimeImmutable.
*
* @param string $time Count of 100-nanosecond intervals since the UUID epoch 1582-10-15 00:00:00 in hexadecimal
*/
public static function hexToDateTime(string $time): \DateTimeImmutable
{
return \DateTimeImmutable::createFromFormat('U.u?', substr_replace(self::hexToNumericString($time), '.', -7, 0));
}
/**

View file

@ -1,6 +1,12 @@
CHANGELOG
=========
7.1
---
* Add `UuidV1::toV6()`, `UuidV1::toV7()` and `UuidV6::toV7()`
* Add `AbstractUid::toString()`
6.2
---

View file

@ -25,12 +25,9 @@ use ncc\ThirdParty\Symfony\Uid\Factory\UlidFactory;
#[AsCommand(name: 'ulid:generate', description: 'Generate a ULID')]
class GenerateUlidCommand extends Command
{
private UlidFactory $factory;
public function __construct(UlidFactory $factory = null)
{
$this->factory = $factory ?? new UlidFactory();
public function __construct(
private UlidFactory $factory = new UlidFactory(),
) {
parent::__construct();
}
@ -79,7 +76,7 @@ EOF
$formatOption = $input->getOption('format');
if (\in_array($formatOption, $this->getAvailableFormatOptions())) {
if (\in_array($formatOption, $this->getAvailableFormatOptions(), true)) {
$format = 'to'.ucfirst($formatOption);
} else {
$io->error(sprintf('Invalid format "%s", supported formats are "%s".', $formatOption, implode('", "', $this->getAvailableFormatOptions())));

View file

@ -26,12 +26,9 @@ use ncc\ThirdParty\Symfony\Uid\Uuid;
#[AsCommand(name: 'uuid:generate', description: 'Generate a UUID')]
class GenerateUuidCommand extends Command
{
private UuidFactory $factory;
public function __construct(UuidFactory $factory = null)
{
$this->factory = $factory ?? new UuidFactory();
public function __construct(
private UuidFactory $factory = new UuidFactory(),
) {
parent::__construct();
}
@ -168,7 +165,7 @@ EOF
$formatOption = $input->getOption('format');
if (\in_array($formatOption, $this->getAvailableFormatOptions())) {
if (\in_array($formatOption, $this->getAvailableFormatOptions(), true)) {
$format = 'to'.ucfirst($formatOption);
} else {
$io->error(sprintf('Invalid format "%s", supported formats are "%s".', $formatOption, implode('", "', $this->getAvailableFormatOptions())));

View file

@ -49,7 +49,6 @@ EOF
$io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
try {
/** @var Uuid $uuid */
$uuid = Uuid::fromString($input->getArgument('uuid'));
} catch (\InvalidArgumentException $e) {
$io->error($e->getMessage());
@ -62,7 +61,7 @@ EOF
} elseif (new MaxUuid() == $uuid) {
$version = 'max';
} else {
$version = uuid_type($uuid);
$version = hexdec($uuid->toRfc4122()[14]);
}
$rows = [

View file

@ -17,13 +17,10 @@ use ncc\ThirdParty\Symfony\Uid\UuidV5;
class NameBasedUuidFactory
{
private string $class;
private Uuid $namespace;
public function __construct(string $class, Uuid $namespace)
{
$this->class = $class;
$this->namespace = $namespace;
public function __construct(
private string $class,
private Uuid $namespace,
) {
}
public function create(string $name): UuidV5|UuidV3

View file

@ -15,11 +15,12 @@ use ncc\ThirdParty\Symfony\Uid\UuidV4;
class RandomBasedUuidFactory
{
private string $class;
public function __construct(string $class)
{
$this->class = $class;
/**
* @param class-string $class
*/
public function __construct(
private string $class,
) {
}
public function create(): UuidV4

View file

@ -16,16 +16,16 @@ use ncc\ThirdParty\Symfony\Uid\Uuid;
class TimeBasedUuidFactory
{
private string $class;
private ?Uuid $node;
public function __construct(string $class, Uuid $node = null)
{
$this->class = $class;
$this->node = $node;
/**
* @param class-string<Uuid&TimeBasedUidInterface> $class
*/
public function __construct(
private string $class,
private ?Uuid $node = null,
) {
}
public function create(\DateTimeInterface $time = null): Uuid&TimeBasedUidInterface
public function create(?\DateTimeInterface $time = null): Uuid&TimeBasedUidInterface
{
$class = $this->class;

View file

@ -15,7 +15,7 @@ use ncc\ThirdParty\Symfony\Uid\Ulid;
class UlidFactory
{
public function create(\DateTimeInterface $time = null): Ulid
public function create(?\DateTimeInterface $time = null): Ulid
{
return new Ulid(null === $time ? null : Ulid::generate($time));
}

View file

@ -26,7 +26,7 @@ class UuidFactory
private ?Uuid $timeBasedNode;
private ?Uuid $nameBasedNamespace;
public function __construct(string|int $defaultClass = UuidV6::class, string|int $timeBasedClass = UuidV6::class, string|int $nameBasedClass = UuidV5::class, string|int $randomBasedClass = UuidV4::class, Uuid|string $timeBasedNode = null, Uuid|string $nameBasedNamespace = null)
public function __construct(string|int $defaultClass = UuidV6::class, string|int $timeBasedClass = UuidV6::class, string|int $nameBasedClass = UuidV5::class, string|int $randomBasedClass = UuidV4::class, Uuid|string|null $timeBasedNode = null, Uuid|string|null $nameBasedNamespace = null)
{
if (null !== $timeBasedNode && !$timeBasedNode instanceof Uuid) {
$timeBasedNode = Uuid::fromString($timeBasedNode);
@ -56,7 +56,7 @@ class UuidFactory
return new RandomBasedUuidFactory($this->randomBasedClass);
}
public function timeBased(Uuid|string $node = null): TimeBasedUuidFactory
public function timeBased(Uuid|string|null $node = null): TimeBasedUuidFactory
{
$node ??= $this->timeBasedNode;
@ -67,7 +67,7 @@ class UuidFactory
return new TimeBasedUuidFactory($this->timeBasedClass, $node);
}
public function nameBased(Uuid|string $namespace = null): NameBasedUuidFactory
public function nameBased(Uuid|string|null $namespace = null): NameBasedUuidFactory
{
$namespace ??= $this->nameBasedNamespace;

View file

@ -26,7 +26,7 @@ class Ulid extends AbstractUid implements TimeBasedUidInterface
private static string $time = '';
private static array $rand = [];
public function __construct(string $ulid = null)
public function __construct(?string $ulid = null)
{
if (null === $ulid) {
$this->uid = static::generate();
@ -59,7 +59,7 @@ class Ulid extends AbstractUid implements TimeBasedUidInterface
public static function fromString(string $ulid): static
{
if (36 === \strlen($ulid) && preg_match('{^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$}Di', $ulid)) {
$ulid = uuid_parse($ulid);
$ulid = hex2bin(str_replace('-', '', $ulid));
} elseif (22 === \strlen($ulid) && 22 === strspn($ulid, BinaryUtil::BASE58[''])) {
$ulid = str_pad(BinaryUtil::fromBase($ulid, BinaryUtil::BASE58), 16, "\0", \STR_PAD_LEFT);
}
@ -114,6 +114,13 @@ class Ulid extends AbstractUid implements TimeBasedUidInterface
return hex2bin($ulid);
}
/**
* Returns the identifier as a base32 case insensitive string.
*
* @see https://tools.ietf.org/html/rfc4648#section-6
*
* @example 09EJ0S614A9FXVG9C5537Q9ZE1 (len=26)
*/
public function toBase32(): string
{
return $this->uid;
@ -141,7 +148,7 @@ class Ulid extends AbstractUid implements TimeBasedUidInterface
return \DateTimeImmutable::createFromFormat('U.u', substr_replace($time, '.', -3, 0));
}
public static function generate(\DateTimeInterface $time = null): string
public static function generate(?\DateTimeInterface $time = null): string
{
if (null === $mtime = $time) {
$time = microtime(false);

View file

@ -14,7 +14,7 @@ namespace ncc\ThirdParty\Symfony\Uid;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @see https://tools.ietf.org/html/rfc4122#appendix-C for details about namespaces
* @see https://datatracker.ietf.org/doc/html/rfc9562/#section-6.6 for details about namespaces
*/
class Uuid extends AbstractUid
{
@ -149,9 +149,16 @@ class Uuid extends AbstractUid
public function toBinary(): string
{
return uuid_parse($this->uid);
return hex2bin(str_replace('-', '', $this->uid));
}
/**
* Returns the identifier as a RFC4122 case insensitive string.
*
* @see https://tools.ietf.org/html/rfc4122#section-3
*
* @example 09748193-048a-4bfb-b825-8528cf74fdc1 (len=36)
*/
public function toRfc4122(): string
{
return $this->uid;

View file

@ -22,10 +22,10 @@ class UuidV1 extends Uuid implements TimeBasedUidInterface
private static string $clockSeq;
public function __construct(string $uuid = null)
public function __construct(?string $uuid = null)
{
if (null === $uuid) {
$this->uid = uuid_create(static::TYPE);
$this->uid = strtolower(uuid_create(static::TYPE));
} else {
parent::__construct($uuid, true);
}
@ -38,10 +38,22 @@ class UuidV1 extends Uuid implements TimeBasedUidInterface
public function getNode(): string
{
return uuid_mac($this->uid);
return substr($this->uid, -12);
}
public static function generate(\DateTimeInterface $time = null, Uuid $node = null): string
public function toV6(): UuidV6
{
$uuid = $this->uid;
return new UuidV6(substr($uuid, 15, 3).substr($uuid, 9, 4).$uuid[0].'-'.substr($uuid, 1, 4).'-6'.substr($uuid, 5, 3).substr($uuid, 18, 6).substr($uuid, 24));
}
public function toV7(): UuidV7
{
return $this->toV6()->toV7();
}
public static function generate(?\DateTimeInterface $time = null, ?Uuid $node = null): string
{
$uuid = !$time || !$node ? uuid_create(static::TYPE) : parent::NIL;

View file

@ -20,15 +20,22 @@ class UuidV4 extends Uuid
{
protected const TYPE = 4;
public function __construct(string $uuid = null)
public function __construct(?string $uuid = null)
{
if (null === $uuid) {
$uuid = random_bytes(16);
$uuid[6] = $uuid[6] & "\x0F" | "\x40";
$uuid[8] = $uuid[8] & "\x3F" | "\x80";
$uuid = bin2hex($uuid);
$this->uid = substr($uuid, 0, 8).'-'.substr($uuid, 8, 4).'-'.substr($uuid, 12, 4).'-'.substr($uuid, 16, 4).'-'.substr($uuid, 20, 12);
// Generate 36 random hex characters (144 bits)
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
$uuid = bin2hex(random_bytes(18));
// Insert dashes to match the UUID format
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
$uuid[8] = $uuid[13] = $uuid[18] = $uuid[23] = '-';
// Set the UUID version to 4
// xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx
$uuid[14] = '4';
// Set the UUID variant: the 19th char must be in [8, 9, a, b]
// xxxxxxxx-xxxx-4xxx-?xxx-xxxxxxxxxxxx
$uuid[19] = ['8', '9', 'a', 'b', '8', '9', 'a', 'b', 'c' => '8', 'd' => '9', 'e' => 'a', 'f' => 'b'][$uuid[19]] ?? $uuid[19];
$this->uid = $uuid;
} else {
parent::__construct($uuid, true);
}

View file

@ -24,7 +24,7 @@ class UuidV6 extends Uuid implements TimeBasedUidInterface
private static string $node;
public function __construct(string $uuid = null)
public function __construct(?string $uuid = null)
{
if (null === $uuid) {
$this->uid = static::generate();
@ -43,7 +43,34 @@ class UuidV6 extends Uuid implements TimeBasedUidInterface
return substr($this->uid, 24);
}
public static function generate(\DateTimeInterface $time = null, Uuid $node = null): string
public function toV7(): UuidV7
{
$uuid = $this->uid;
$time = BinaryUtil::hexToNumericString('0'.substr($uuid, 0, 8).substr($uuid, 9, 4).substr($uuid, 15, 3));
if ('-' === $time[0]) {
throw new \InvalidArgumentException('Cannot convert UUID to v7: its timestamp is before the Unix epoch.');
}
$ms = \strlen($time) > 4 ? substr($time, 0, -4) : '0';
$time = dechex(10000 * hexdec(substr($uuid, 20, 3)) + substr($time, -4));
if (\strlen($time) > 6) {
$uuid[29] = dechex(hexdec($uuid[29]) ^ hexdec($time[0]));
$time = substr($time, 1);
}
return new UuidV7(substr_replace(sprintf(
'%012s-7%s-%s%s-%s%06s',
\PHP_INT_SIZE >= 8 ? dechex($ms) : bin2hex(BinaryUtil::fromBase($ms, BinaryUtil::BASE10)),
substr($uuid, -6, 3),
$uuid[19],
substr($uuid, -3),
substr($uuid, -12, 6),
$time
), '-', 8, 0));
}
public static function generate(?\DateTimeInterface $time = null, ?Uuid $node = null): string
{
$uuidV1 = UuidV1::generate($time, $node);
$uuid = substr($uuidV1, 15, 3).substr($uuidV1, 9, 4).$uuidV1[0].'-'.substr($uuidV1, 1, 4).'-6'.substr($uuidV1, 5, 3).substr($uuidV1, 18, 6);

View file

@ -28,7 +28,7 @@ class UuidV7 extends Uuid implements TimeBasedUidInterface
private static array $seedParts;
private static int $seedIndex = 0;
public function __construct(string $uuid = null)
public function __construct(?string $uuid = null)
{
if (null === $uuid) {
$this->uid = static::generate();
@ -49,7 +49,7 @@ class UuidV7 extends Uuid implements TimeBasedUidInterface
return \DateTimeImmutable::createFromFormat('U.v', substr_replace($time, '.', -3, 0));
}
public static function generate(\DateTimeInterface $time = null): string
public static function generate(?\DateTimeInterface $time = null): string
{
if (null === $mtime = $time) {
$time = microtime(false);
@ -64,6 +64,17 @@ class UuidV7 extends Uuid implements TimeBasedUidInterface
self::$rand[1] &= 0x03FF;
self::$time = $time;
} else {
// Within the same ms, we increment the rand part by a random 24-bit number.
// Instead of getting this number from random_bytes(), which is slow, we get
// it by sha512-hashing self::$seed. This produces 64 bytes of entropy,
// which we need to split in a list of 24-bit numbers. unpack() first splits
// them into 16 x 32-bit numbers; we take the first byte of each of these
// numbers to get 5 extra 24-bit numbers. Then, we consume those numbers
// one-by-one and run this logic every 21 iterations.
// self::$rand holds the random part of the UUID, split into 5 x 16-bit
// numbers for x86 portability. We increment this random part by the next
// 24-bit number in the self::$seedParts list and decrement self::$seedIndex.
if (!self::$seedIndex) {
$s = unpack('l*', self::$seed = hash('sha512', self::$seed, true));
$s[] = ($s[1] >> 8 & 0xFF0000) | ($s[2] >> 16 & 0xFF00) | ($s[3] >> 24 & 0xFF);
@ -75,7 +86,7 @@ class UuidV7 extends Uuid implements TimeBasedUidInterface
self::$seedIndex = 21;
}
self::$rand[5] = 0xFFFF & $carry = self::$rand[5] + (self::$seedParts[self::$seedIndex--] & 0xFFFFFF);
self::$rand[5] = 0xFFFF & $carry = self::$rand[5] + 1 + (self::$seedParts[self::$seedIndex--] & 0xFFFFFF);
self::$rand[4] = 0xFFFF & $carry = self::$rand[4] + ($carry >> 16);
self::$rand[3] = 0xFFFF & $carry = self::$rand[3] + ($carry >> 16);
self::$rand[2] = 0xFFFF & $carry = self::$rand[2] + ($carry >> 16);

View file

@ -1 +1 @@
6.3.5
7.1.4