[Uid] Add UuidFactory to create Ulid and Uuid from timestamps, namespaces and nodes
This commit is contained in:
parent
37e1823598
commit
88a99ddbdf
@ -6,6 +6,7 @@ CHANGELOG
|
|||||||
|
|
||||||
* Deprecate `DoctrineTestHelper` and `TestRepositoryFactory`
|
* Deprecate `DoctrineTestHelper` and `TestRepositoryFactory`
|
||||||
* [BC BREAK] Remove `UuidV*Generator` classes
|
* [BC BREAK] Remove `UuidV*Generator` classes
|
||||||
|
* Add `UuidGenerator`
|
||||||
|
|
||||||
5.2.0
|
5.2.0
|
||||||
-----
|
-----
|
||||||
|
@ -13,12 +13,24 @@ namespace Symfony\Bridge\Doctrine\IdGenerator;
|
|||||||
|
|
||||||
use Doctrine\ORM\EntityManager;
|
use Doctrine\ORM\EntityManager;
|
||||||
use Doctrine\ORM\Id\AbstractIdGenerator;
|
use Doctrine\ORM\Id\AbstractIdGenerator;
|
||||||
|
use Symfony\Component\Uid\Factory\UlidFactory;
|
||||||
use Symfony\Component\Uid\Ulid;
|
use Symfony\Component\Uid\Ulid;
|
||||||
|
|
||||||
final class UlidGenerator extends AbstractIdGenerator
|
final class UlidGenerator extends AbstractIdGenerator
|
||||||
{
|
{
|
||||||
|
private $factory;
|
||||||
|
|
||||||
|
public function __construct(UlidFactory $factory = null)
|
||||||
|
{
|
||||||
|
$this->factory = $factory;
|
||||||
|
}
|
||||||
|
|
||||||
public function generate(EntityManager $em, $entity): Ulid
|
public function generate(EntityManager $em, $entity): Ulid
|
||||||
{
|
{
|
||||||
|
if ($this->factory) {
|
||||||
|
return $this->factory->create();
|
||||||
|
}
|
||||||
|
|
||||||
return new Ulid();
|
return new Ulid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
82
src/Symfony/Bridge/Doctrine/IdGenerator/UuidGenerator.php
Normal file
82
src/Symfony/Bridge/Doctrine/IdGenerator/UuidGenerator.php
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?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\Bridge\Doctrine\IdGenerator;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use Doctrine\ORM\Id\AbstractIdGenerator;
|
||||||
|
use Symfony\Component\Uid\Factory\UuidFactory;
|
||||||
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
|
||||||
|
final class UuidGenerator extends AbstractIdGenerator
|
||||||
|
{
|
||||||
|
private $protoFactory;
|
||||||
|
private $factory;
|
||||||
|
private $entityGetter;
|
||||||
|
|
||||||
|
public function __construct(UuidFactory $factory = null)
|
||||||
|
{
|
||||||
|
$this->protoFactory = $this->factory = $factory ?? new UuidFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generate(EntityManager $em, $entity): Uuid
|
||||||
|
{
|
||||||
|
if (null !== $this->entityGetter) {
|
||||||
|
if (\is_callable([$entity, $this->entityGetter])) {
|
||||||
|
return $this->factory->create($entity->{$this->entityGetter}());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->factory->create($entity->{$this->entityGetter});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->factory->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Uuid|string|null $namespace
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function nameBased(string $entityGetter, $namespace = null): self
|
||||||
|
{
|
||||||
|
$clone = clone $this;
|
||||||
|
$clone->factory = $clone->protoFactory->nameBased($namespace);
|
||||||
|
$clone->entityGetter = $entityGetter;
|
||||||
|
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function randomBased(): self
|
||||||
|
{
|
||||||
|
$clone = clone $this;
|
||||||
|
$clone->factory = $clone->protoFactory->randomBased();
|
||||||
|
$clone->entityGetter = null;
|
||||||
|
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Uuid|string|null $node
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function timeBased($node = null): self
|
||||||
|
{
|
||||||
|
$clone = clone $this;
|
||||||
|
$clone->factory = $clone->protoFactory->timeBased($node);
|
||||||
|
$clone->entityGetter = null;
|
||||||
|
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ namespace Symfony\Bridge\Doctrine\Tests\IdGenerator;
|
|||||||
use Doctrine\ORM\Mapping\Entity;
|
use Doctrine\ORM\Mapping\Entity;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
|
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
|
||||||
use Symfony\Component\Uid\AbstractUid;
|
use Symfony\Component\Uid\Factory\UlidFactory;
|
||||||
use Symfony\Component\Uid\Ulid;
|
use Symfony\Component\Uid\Ulid;
|
||||||
|
|
||||||
class UlidGeneratorTest extends TestCase
|
class UlidGeneratorTest extends TestCase
|
||||||
@ -25,8 +25,23 @@ class UlidGeneratorTest extends TestCase
|
|||||||
$generator = new UlidGenerator();
|
$generator = new UlidGenerator();
|
||||||
$ulid = $generator->generate($em, new Entity());
|
$ulid = $generator->generate($em, new Entity());
|
||||||
|
|
||||||
$this->assertInstanceOf(AbstractUid::class, $ulid);
|
|
||||||
$this->assertInstanceOf(Ulid::class, $ulid);
|
$this->assertInstanceOf(Ulid::class, $ulid);
|
||||||
$this->assertTrue(Ulid::isValid($ulid));
|
$this->assertTrue(Ulid::isValid($ulid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires function \Symfony\Component\Uid\Factory\UlidFactory::create
|
||||||
|
*/
|
||||||
|
public function testUlidFactory()
|
||||||
|
{
|
||||||
|
$ulid = new Ulid('00000000000000000000000000');
|
||||||
|
$em = new EntityManager();
|
||||||
|
$factory = $this->createMock(UlidFactory::class);
|
||||||
|
$factory->expects($this->any())
|
||||||
|
->method('create')
|
||||||
|
->willReturn($ulid);
|
||||||
|
$generator = new UlidGenerator($factory);
|
||||||
|
|
||||||
|
$this->assertSame($ulid, $generator->generate($em, new Entity()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
<?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\Bridge\Doctrine\Tests\IdGenerator;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
|
||||||
|
use Symfony\Component\Uid\Factory\UuidFactory;
|
||||||
|
use Symfony\Component\Uid\NilUuid;
|
||||||
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
use Symfony\Component\Uid\UuidV4;
|
||||||
|
use Symfony\Component\Uid\UuidV6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires function \Symfony\Component\Uid\Factory\UuidFactory::create
|
||||||
|
*/
|
||||||
|
class UuidGeneratorTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testUuidCanBeGenerated()
|
||||||
|
{
|
||||||
|
$em = new EntityManager();
|
||||||
|
$generator = new UuidGenerator();
|
||||||
|
$uuid = $generator->generate($em, new Entity());
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Uuid::class, $uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCustomUuidfactory()
|
||||||
|
{
|
||||||
|
$uuid = new NilUuid();
|
||||||
|
$em = new EntityManager();
|
||||||
|
$factory = $this->createMock(UuidFactory::class);
|
||||||
|
$factory->expects($this->any())
|
||||||
|
->method('create')
|
||||||
|
->willReturn($uuid);
|
||||||
|
$generator = new UuidGenerator($factory);
|
||||||
|
|
||||||
|
$this->assertSame($uuid, $generator->generate($em, new Entity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUuidfactory()
|
||||||
|
{
|
||||||
|
$em = new EntityManager();
|
||||||
|
$generator = new UuidGenerator();
|
||||||
|
$this->assertInstanceOf(UuidV6::class, $generator->generate($em, new Entity()));
|
||||||
|
|
||||||
|
$generator = $generator->randomBased();
|
||||||
|
$this->assertInstanceOf(UuidV4::class, $generator->generate($em, new Entity()));
|
||||||
|
|
||||||
|
$generator = $generator->timeBased();
|
||||||
|
$this->assertInstanceOf(UuidV6::class, $generator->generate($em, new Entity()));
|
||||||
|
|
||||||
|
$generator = $generator->nameBased('prop1', Uuid::NAMESPACE_OID);
|
||||||
|
$this->assertEquals(Uuid::v5(new Uuid(Uuid::NAMESPACE_OID), '3'), $generator->generate($em, new Entity()));
|
||||||
|
|
||||||
|
$generator = $generator->nameBased('prop2', Uuid::NAMESPACE_OID);
|
||||||
|
$this->assertEquals(Uuid::v5(new Uuid(Uuid::NAMESPACE_OID), '2'), $generator->generate($em, new Entity()));
|
||||||
|
|
||||||
|
$generator = $generator->nameBased('getProp4', Uuid::NAMESPACE_OID);
|
||||||
|
$this->assertEquals(Uuid::v5(new Uuid(Uuid::NAMESPACE_OID), '4'), $generator->generate($em, new Entity()));
|
||||||
|
|
||||||
|
$factory = new UuidFactory(6, 6, 5, 5, null, Uuid::NAMESPACE_OID);
|
||||||
|
$generator = new UuidGenerator($factory);
|
||||||
|
$generator = $generator->nameBased('prop1');
|
||||||
|
$this->assertEquals(Uuid::v5(new Uuid(Uuid::NAMESPACE_OID), '3'), $generator->generate($em, new Entity()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Entity
|
||||||
|
{
|
||||||
|
public $prop1 = 1;
|
||||||
|
public $prop2 = 2;
|
||||||
|
|
||||||
|
public function prop1()
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProp4()
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ CHANGELOG
|
|||||||
* Added the `dispatcher` option to `debug:event-dispatcher`
|
* Added the `dispatcher` option to `debug:event-dispatcher`
|
||||||
* Added the `event_dispatcher.dispatcher` tag
|
* Added the `event_dispatcher.dispatcher` tag
|
||||||
* Added `assertResponseFormatSame()` in `BrowserKitAssertionsTrait`
|
* Added `assertResponseFormatSame()` in `BrowserKitAssertionsTrait`
|
||||||
|
* Add support for configuring UUID factory services
|
||||||
|
|
||||||
5.2.0
|
5.2.0
|
||||||
-----
|
-----
|
||||||
|
@ -34,6 +34,7 @@ use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
|
|||||||
use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter;
|
use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter;
|
||||||
use Symfony\Component\Serializer\Serializer;
|
use Symfony\Component\Serializer\Serializer;
|
||||||
use Symfony\Component\Translation\Translator;
|
use Symfony\Component\Translation\Translator;
|
||||||
|
use Symfony\Component\Uid\Factory\UuidFactory;
|
||||||
use Symfony\Component\Validator\Validation;
|
use Symfony\Component\Validator\Validation;
|
||||||
use Symfony\Component\WebLink\HttpHeaderSerializer;
|
use Symfony\Component\WebLink\HttpHeaderSerializer;
|
||||||
use Symfony\Component\Workflow\WorkflowEvents;
|
use Symfony\Component\Workflow\WorkflowEvents;
|
||||||
@ -136,6 +137,7 @@ class Configuration implements ConfigurationInterface
|
|||||||
$this->addSecretsSection($rootNode);
|
$this->addSecretsSection($rootNode);
|
||||||
$this->addNotifierSection($rootNode);
|
$this->addNotifierSection($rootNode);
|
||||||
$this->addRateLimiterSection($rootNode);
|
$this->addRateLimiterSection($rootNode);
|
||||||
|
$this->addUidSection($rootNode);
|
||||||
|
|
||||||
return $treeBuilder;
|
return $treeBuilder;
|
||||||
}
|
}
|
||||||
@ -1891,4 +1893,37 @@ class Configuration implements ConfigurationInterface
|
|||||||
->end()
|
->end()
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function addUidSection(ArrayNodeDefinition $rootNode)
|
||||||
|
{
|
||||||
|
$rootNode
|
||||||
|
->children()
|
||||||
|
->arrayNode('uid')
|
||||||
|
->info('Uid configuration')
|
||||||
|
->{class_exists(UuidFactory::class) ? 'canBeDisabled' : 'canBeEnabled'}()
|
||||||
|
->addDefaultsIfNotSet()
|
||||||
|
->children()
|
||||||
|
->enumNode('default_uuid_version')
|
||||||
|
->defaultValue(6)
|
||||||
|
->values([6, 4, 1])
|
||||||
|
->end()
|
||||||
|
->enumNode('name_based_uuid_version')
|
||||||
|
->defaultValue(5)
|
||||||
|
->values([5, 3])
|
||||||
|
->end()
|
||||||
|
->scalarNode('name_based_uuid_namespace')
|
||||||
|
->cannotBeEmpty()
|
||||||
|
->end()
|
||||||
|
->enumNode('time_based_uuid_version')
|
||||||
|
->defaultValue(6)
|
||||||
|
->values([6, 1])
|
||||||
|
->end()
|
||||||
|
->scalarNode('time_based_uuid_node')
|
||||||
|
->cannotBeEmpty()
|
||||||
|
->end()
|
||||||
|
->end()
|
||||||
|
->end()
|
||||||
|
->end()
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,6 +160,8 @@ use Symfony\Component\String\Slugger\SluggerInterface;
|
|||||||
use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand;
|
use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand;
|
||||||
use Symfony\Component\Translation\PseudoLocalizationTranslator;
|
use Symfony\Component\Translation\PseudoLocalizationTranslator;
|
||||||
use Symfony\Component\Translation\Translator;
|
use Symfony\Component\Translation\Translator;
|
||||||
|
use Symfony\Component\Uid\Factory\UuidFactory;
|
||||||
|
use Symfony\Component\Uid\UuidV4;
|
||||||
use Symfony\Component\Validator\ConstraintValidatorInterface;
|
use Symfony\Component\Validator\ConstraintValidatorInterface;
|
||||||
use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader;
|
use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader;
|
||||||
use Symfony\Component\Validator\ObjectInitializerInterface;
|
use Symfony\Component\Validator\ObjectInitializerInterface;
|
||||||
@ -449,6 +451,14 @@ class FrameworkExtension extends Extension
|
|||||||
$loader->load('web_link.php');
|
$loader->load('web_link.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->isConfigEnabled($container, $config['uid'])) {
|
||||||
|
if (!class_exists(UuidFactory::class)) {
|
||||||
|
throw new LogicException('Uid support cannot be enabled as the Uid component is not installed. Try running "composer require symfony/uid".');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->registerUidConfiguration($config['uid'], $container, $loader);
|
||||||
|
}
|
||||||
|
|
||||||
$this->addAnnotatedClassesToCompile([
|
$this->addAnnotatedClassesToCompile([
|
||||||
'**\\Controller\\',
|
'**\\Controller\\',
|
||||||
'**\\Entity\\',
|
'**\\Entity\\',
|
||||||
@ -2322,6 +2332,27 @@ class FrameworkExtension extends Extension
|
|||||||
$container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter');
|
$container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function registerUidConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
|
||||||
|
{
|
||||||
|
$loader->load('uid.php');
|
||||||
|
|
||||||
|
$container->getDefinition('uuid.factory')
|
||||||
|
->setArguments([
|
||||||
|
$config['default_uuid_version'],
|
||||||
|
$config['time_based_uuid_version'],
|
||||||
|
$config['name_based_uuid_version'],
|
||||||
|
UuidV4::class,
|
||||||
|
$config['time_based_uuid_node'] ?? null,
|
||||||
|
$config['name_based_uuid_namespace'] ?? null,
|
||||||
|
])
|
||||||
|
;
|
||||||
|
|
||||||
|
if (isset($config['name_based_uuid_namespace'])) {
|
||||||
|
$container->getDefinition('name_based_uuid.factory')
|
||||||
|
->setArguments([$config['name_based_uuid_namespace']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function resolveTrustedHeaders(array $headers): int
|
private function resolveTrustedHeaders(array $headers): int
|
||||||
{
|
{
|
||||||
$trustedHeaders = 0;
|
$trustedHeaders = 0;
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
<xsd:element name="mailer" type="mailer" minOccurs="0" maxOccurs="1" />
|
<xsd:element name="mailer" type="mailer" minOccurs="0" maxOccurs="1" />
|
||||||
<xsd:element name="http-cache" type="http_cache" minOccurs="0" maxOccurs="1" />
|
<xsd:element name="http-cache" type="http_cache" minOccurs="0" maxOccurs="1" />
|
||||||
<xsd:element name="rate-limiter" type="rate_limiter" minOccurs="0" maxOccurs="1" />
|
<xsd:element name="rate-limiter" type="rate_limiter" minOccurs="0" maxOccurs="1" />
|
||||||
|
<xsd:element name="uid" type="uid" minOccurs="0" maxOccurs="1" />
|
||||||
</xsd:choice>
|
</xsd:choice>
|
||||||
|
|
||||||
<xsd:attribute name="http-method-override" type="xsd:boolean" />
|
<xsd:attribute name="http-method-override" type="xsd:boolean" />
|
||||||
@ -692,4 +693,35 @@
|
|||||||
<xsd:attribute name="interval" type="xsd:string" />
|
<xsd:attribute name="interval" type="xsd:string" />
|
||||||
<xsd:attribute name="amount" type="xsd:int" />
|
<xsd:attribute name="amount" type="xsd:int" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
|
<xsd:complexType name="uid">
|
||||||
|
<xsd:attribute name="enabled" type="xsd:boolean" />
|
||||||
|
<xsd:attribute name="default_uuid_version" type="default_uuid_version" />
|
||||||
|
<xsd:attribute name="name_based_uuid_version" type="name_based_uuid_version" />
|
||||||
|
<xsd:attribute name="time_based_uuid_version" type="time_based_uuid_version" />
|
||||||
|
<xsd:attribute name="name_based_uuid_namespace" type="xsd:string" />
|
||||||
|
<xsd:attribute name="time_based_uuid_node" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
|
||||||
|
<xsd:simpleType name="default_uuid_version">
|
||||||
|
<xsd:restriction base="xsd:int">
|
||||||
|
<xsd:enumeration value="6" />
|
||||||
|
<xsd:enumeration value="4" />
|
||||||
|
<xsd:enumeration value="1" />
|
||||||
|
</xsd:restriction>
|
||||||
|
</xsd:simpleType>
|
||||||
|
|
||||||
|
<xsd:simpleType name="name_based_uuid_version">
|
||||||
|
<xsd:restriction base="xsd:int">
|
||||||
|
<xsd:enumeration value="5" />
|
||||||
|
<xsd:enumeration value="3" />
|
||||||
|
</xsd:restriction>
|
||||||
|
</xsd:simpleType>
|
||||||
|
|
||||||
|
<xsd:simpleType name="time_based_uuid_version">
|
||||||
|
<xsd:restriction base="xsd:int">
|
||||||
|
<xsd:enumeration value="6" />
|
||||||
|
<xsd:enumeration value="1" />
|
||||||
|
</xsd:restriction>
|
||||||
|
</xsd:simpleType>
|
||||||
</xsd:schema>
|
</xsd:schema>
|
||||||
|
41
src/Symfony/Bundle/FrameworkBundle/Resources/config/uid.php
Normal file
41
src/Symfony/Bundle/FrameworkBundle/Resources/config/uid.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?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\DependencyInjection\Loader\Configurator;
|
||||||
|
|
||||||
|
use Symfony\Component\Uid\Factory\NameBasedUuidFactory;
|
||||||
|
use Symfony\Component\Uid\Factory\RandomBasedUuidFactory;
|
||||||
|
use Symfony\Component\Uid\Factory\TimeBasedUuidFactory;
|
||||||
|
use Symfony\Component\Uid\Factory\UlidFactory;
|
||||||
|
use Symfony\Component\Uid\Factory\UuidFactory;
|
||||||
|
|
||||||
|
return static function (ContainerConfigurator $container) {
|
||||||
|
$container->services()
|
||||||
|
->set('ulid.factory', UlidFactory::class)
|
||||||
|
->alias(UlidFactory::class, 'ulid.factory')
|
||||||
|
|
||||||
|
->set('uuid.factory', UuidFactory::class)
|
||||||
|
->alias(UuidFactory::class, 'uuid.factory')
|
||||||
|
|
||||||
|
->set('name_based_uuid.factory', NameBasedUuidFactory::class)
|
||||||
|
->factory([service('uuid.factory'), 'nameBased'])
|
||||||
|
->args([abstract_arg('Please set the "framework.uid.name_based_uuid_namespace" configuration option to use the "name_based_uuid.factory" service')])
|
||||||
|
->alias(NameBasedUuidFactory::class, 'name_based_uuid.factory')
|
||||||
|
|
||||||
|
->set('random_based_uuid.factory', RandomBasedUuidFactory::class)
|
||||||
|
->factory([service('uuid.factory'), 'randomBased'])
|
||||||
|
->alias(RandomBasedUuidFactory::class, 'random_based_uuid.factory')
|
||||||
|
|
||||||
|
->set('time_based_uuid.factory', TimeBasedUuidFactory::class)
|
||||||
|
->factory([service('uuid.factory'), 'timeBased'])
|
||||||
|
->alias(TimeBasedUuidFactory::class, 'time_based_uuid.factory')
|
||||||
|
;
|
||||||
|
};
|
@ -22,6 +22,7 @@ use Symfony\Component\Lock\Store\SemaphoreStore;
|
|||||||
use Symfony\Component\Mailer\Mailer;
|
use Symfony\Component\Mailer\Mailer;
|
||||||
use Symfony\Component\Messenger\MessageBusInterface;
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
use Symfony\Component\Notifier\Notifier;
|
use Symfony\Component\Notifier\Notifier;
|
||||||
|
use Symfony\Component\Uid\Factory\UuidFactory;
|
||||||
|
|
||||||
class ConfigurationTest extends TestCase
|
class ConfigurationTest extends TestCase
|
||||||
{
|
{
|
||||||
@ -563,6 +564,12 @@ class ConfigurationTest extends TestCase
|
|||||||
'enabled' => false,
|
'enabled' => false,
|
||||||
'limiters' => [],
|
'limiters' => [],
|
||||||
],
|
],
|
||||||
|
'uid' => [
|
||||||
|
'enabled' => class_exists(UuidFactory::class),
|
||||||
|
'default_uuid_version' => 6,
|
||||||
|
'name_based_uuid_version' => 5,
|
||||||
|
'time_based_uuid_version' => 6,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ class BinaryUtil
|
|||||||
/**
|
/**
|
||||||
* @param string $time Count of 100-nanosecond intervals since the UUID epoch 1582-10-15 00:00:00 in hexadecimal
|
* @param string $time Count of 100-nanosecond intervals since the UUID epoch 1582-10-15 00:00:00 in hexadecimal
|
||||||
*/
|
*/
|
||||||
public static function timeToDateTime(string $time): \DateTimeImmutable
|
public static function hexToDateTime(string $time): \DateTimeImmutable
|
||||||
{
|
{
|
||||||
if (\PHP_INT_SIZE >= 8) {
|
if (\PHP_INT_SIZE >= 8) {
|
||||||
$time = (string) (hexdec($time) - self::TIME_OFFSET_INT);
|
$time = (string) (hexdec($time) - self::TIME_OFFSET_INT);
|
||||||
@ -142,4 +142,34 @@ class BinaryUtil
|
|||||||
|
|
||||||
return \DateTimeImmutable::createFromFormat('U.u?', substr_replace($time, '.', -7, 0));
|
return \DateTimeImmutable::createFromFormat('U.u?', substr_replace($time, '.', -7, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Count of 100-nanosecond intervals since the UUID epoch 1582-10-15 00:00:00 in hexadecimal
|
||||||
|
*/
|
||||||
|
public static function dateTimeToHex(\DateTimeInterface $time): string
|
||||||
|
{
|
||||||
|
if (\PHP_INT_SIZE >= 8) {
|
||||||
|
if (-self::TIME_OFFSET_INT > $time = (int) $time->format('Uu0')) {
|
||||||
|
throw new \InvalidArgumentException('The given UUID date cannot be earlier than 1582-10-15.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return str_pad(dechex(self::TIME_OFFSET_INT + $time), 16, '0', \STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
$time = $time->format('Uu0');
|
||||||
|
$negative = '-' === $time[0];
|
||||||
|
if ($negative && self::TIME_OFFSET_INT < $time = substr($time, 1)) {
|
||||||
|
throw new \InvalidArgumentException('The given UUID date cannot be earlier than 1582-10-15.');
|
||||||
|
}
|
||||||
|
$time = self::fromBase($time, self::BASE10);
|
||||||
|
$time = str_pad($time, 8, "\0", \STR_PAD_LEFT);
|
||||||
|
|
||||||
|
if ($negative) {
|
||||||
|
$time = self::add($time, self::TIME_OFFSET_COM1) ^ "\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||||
|
} else {
|
||||||
|
$time = self::add($time, self::TIME_OFFSET_BIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bin2hex($time);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ CHANGELOG
|
|||||||
* Add `AbstractUid::fromBinary()`, `AbstractUid::fromBase58()`, `AbstractUid::fromBase32()` and `AbstractUid::fromRfc4122()`
|
* Add `AbstractUid::fromBinary()`, `AbstractUid::fromBase58()`, `AbstractUid::fromBase32()` and `AbstractUid::fromRfc4122()`
|
||||||
* [BC BREAK] Replace `UuidV1::getTime()`, `UuidV6::getTime()` and `Ulid::getTime()` by `UuidV1::getDateTime()`, `UuidV6::getDateTime()` and `Ulid::getDateTime()`
|
* [BC BREAK] Replace `UuidV1::getTime()`, `UuidV6::getTime()` and `Ulid::getTime()` by `UuidV1::getDateTime()`, `UuidV6::getDateTime()` and `Ulid::getDateTime()`
|
||||||
* Add `Uuid::NAMESPACE_*` constants from RFC4122
|
* Add `Uuid::NAMESPACE_*` constants from RFC4122
|
||||||
|
* Add `UlidFactory`, `UuidFactory`, `RandomBasedUuidFactory`, `TimeBasedUuidFactory` and `NameBasedUuidFactory`
|
||||||
|
|
||||||
5.2.0
|
5.2.0
|
||||||
-----
|
-----
|
||||||
|
47
src/Symfony/Component/Uid/Factory/NameBasedUuidFactory.php
Normal file
47
src/Symfony/Component/Uid/Factory/NameBasedUuidFactory.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?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\Factory;
|
||||||
|
|
||||||
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
use Symfony\Component\Uid\UuidV3;
|
||||||
|
use Symfony\Component\Uid\UuidV5;
|
||||||
|
|
||||||
|
class NameBasedUuidFactory
|
||||||
|
{
|
||||||
|
private $class;
|
||||||
|
private $namespace;
|
||||||
|
|
||||||
|
public function __construct(string $class, Uuid $namespace)
|
||||||
|
{
|
||||||
|
$this->class = $class;
|
||||||
|
$this->namespace = $namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return UuidV5|UuidV3
|
||||||
|
*/
|
||||||
|
public function create(string $name): Uuid
|
||||||
|
{
|
||||||
|
switch ($class = $this->class) {
|
||||||
|
case UuidV5::class: return Uuid::v5($this->namespace, $name);
|
||||||
|
case UuidV3::class: return Uuid::v3($this->namespace, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_subclass_of($class, UuidV5::class)) {
|
||||||
|
$uuid = Uuid::v5($this->namespace, $name);
|
||||||
|
} else {
|
||||||
|
$uuid = Uuid::v3($this->namespace, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new $class($uuid);
|
||||||
|
}
|
||||||
|
}
|
31
src/Symfony/Component/Uid/Factory/RandomBasedUuidFactory.php
Normal file
31
src/Symfony/Component/Uid/Factory/RandomBasedUuidFactory.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?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\Factory;
|
||||||
|
|
||||||
|
use Symfony\Component\Uid\UuidV4;
|
||||||
|
|
||||||
|
class RandomBasedUuidFactory
|
||||||
|
{
|
||||||
|
private $class;
|
||||||
|
|
||||||
|
public function __construct(string $class)
|
||||||
|
{
|
||||||
|
$this->class = $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(): UuidV4
|
||||||
|
{
|
||||||
|
$class = $this->class;
|
||||||
|
|
||||||
|
return new $class();
|
||||||
|
}
|
||||||
|
}
|
42
src/Symfony/Component/Uid/Factory/TimeBasedUuidFactory.php
Normal file
42
src/Symfony/Component/Uid/Factory/TimeBasedUuidFactory.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?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\Factory;
|
||||||
|
|
||||||
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
use Symfony\Component\Uid\UuidV1;
|
||||||
|
use Symfony\Component\Uid\UuidV6;
|
||||||
|
|
||||||
|
class TimeBasedUuidFactory
|
||||||
|
{
|
||||||
|
private $class;
|
||||||
|
private $node;
|
||||||
|
|
||||||
|
public function __construct(string $class, Uuid $node = null)
|
||||||
|
{
|
||||||
|
$this->class = $class;
|
||||||
|
$this->node = $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return UuidV6|UuidV1
|
||||||
|
*/
|
||||||
|
public function create(\DateTimeInterface $time = null): Uuid
|
||||||
|
{
|
||||||
|
$class = $this->class;
|
||||||
|
|
||||||
|
if (null === $time && null === $this->node) {
|
||||||
|
return new $class();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new $class($class::generate($time, $this->node));
|
||||||
|
}
|
||||||
|
}
|
22
src/Symfony/Component/Uid/Factory/UlidFactory.php
Normal file
22
src/Symfony/Component/Uid/Factory/UlidFactory.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?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\Factory;
|
||||||
|
|
||||||
|
use Symfony\Component\Uid\Ulid;
|
||||||
|
|
||||||
|
class UlidFactory
|
||||||
|
{
|
||||||
|
public function create(\DateTimeInterface $time = null): Ulid
|
||||||
|
{
|
||||||
|
return new Ulid(null === $time ? null : Ulid::generate($time));
|
||||||
|
}
|
||||||
|
}
|
104
src/Symfony/Component/Uid/Factory/UuidFactory.php
Normal file
104
src/Symfony/Component/Uid/Factory/UuidFactory.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?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\Factory;
|
||||||
|
|
||||||
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
use Symfony\Component\Uid\UuidV1;
|
||||||
|
use Symfony\Component\Uid\UuidV4;
|
||||||
|
use Symfony\Component\Uid\UuidV5;
|
||||||
|
use Symfony\Component\Uid\UuidV6;
|
||||||
|
|
||||||
|
class UuidFactory
|
||||||
|
{
|
||||||
|
private $defaultClass;
|
||||||
|
private $timeBasedClass;
|
||||||
|
private $nameBasedClass;
|
||||||
|
private $randomBasedClass;
|
||||||
|
private $timeBasedNode;
|
||||||
|
private $nameBasedNamespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|int $defaultClass
|
||||||
|
* @param string|int $timeBasedClass
|
||||||
|
* @param string|int $nameBasedClass
|
||||||
|
* @param string|int $randomBasedClass
|
||||||
|
* @param Uuid|string|null $timeBasedNode
|
||||||
|
* @param Uuid|string|null $nameBasedNamespace
|
||||||
|
*/
|
||||||
|
public function __construct($defaultClass = UuidV6::class, $timeBasedClass = UuidV6::class, $nameBasedClass = UuidV5::class, $randomBasedClass = UuidV4::class, $timeBasedNode = null, $nameBasedNamespace = null)
|
||||||
|
{
|
||||||
|
if (null !== $timeBasedNode && !$timeBasedNode instanceof Uuid) {
|
||||||
|
$timeBasedNode = Uuid::fromString($timeBasedNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $nameBasedNamespace && !$nameBasedNamespace instanceof Uuid) {
|
||||||
|
$nameBasedNamespace = Uuid::fromString($nameBasedNamespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->defaultClass = is_numeric($defaultClass) ? Uuid::class.'V'.$defaultClass : $defaultClass;
|
||||||
|
$this->timeBasedClass = is_numeric($timeBasedClass) ? Uuid::class.'V'.$timeBasedClass : $timeBasedClass;
|
||||||
|
$this->nameBasedClass = is_numeric($nameBasedClass) ? Uuid::class.'V'.$nameBasedClass : $nameBasedClass;
|
||||||
|
$this->randomBasedClass = is_numeric($randomBasedClass) ? Uuid::class.'V'.$randomBasedClass : $randomBasedClass;
|
||||||
|
$this->timeBasedNode = $timeBasedNode;
|
||||||
|
$this->nameBasedNamespace = $nameBasedNamespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return UuidV6|UuidV4|UuidV1
|
||||||
|
*/
|
||||||
|
public function create(): Uuid
|
||||||
|
{
|
||||||
|
$class = $this->defaultClass;
|
||||||
|
|
||||||
|
return new $class();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function randomBased(): RandomBasedUuidFactory
|
||||||
|
{
|
||||||
|
return new RandomBasedUuidFactory($this->randomBasedClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Uuid|string|null $node
|
||||||
|
*/
|
||||||
|
public function timeBased($node = null): TimeBasedUuidFactory
|
||||||
|
{
|
||||||
|
$node ?? $node = $this->timeBasedNode;
|
||||||
|
|
||||||
|
if (null === $node) {
|
||||||
|
$class = $this->timeBasedClass;
|
||||||
|
$node = $this->timeBasedNode = new $class();
|
||||||
|
} elseif (!$node instanceof Uuid) {
|
||||||
|
$node = Uuid::fromString($node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TimeBasedUuidFactory($this->timeBasedClass, $node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Uuid|string|null $namespace
|
||||||
|
*/
|
||||||
|
public function nameBased($namespace = null): NameBasedUuidFactory
|
||||||
|
{
|
||||||
|
$namespace ?? $namespace = $this->nameBasedNamespace;
|
||||||
|
|
||||||
|
if (null === $namespace) {
|
||||||
|
throw new \LogicException(sprintf('A namespace should be defined when using "%s()".', __METHOD__));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$namespace instanceof Uuid) {
|
||||||
|
$namespace = Uuid::fromString($namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NameBasedUuidFactory($this->nameBasedClass, $namespace);
|
||||||
|
}
|
||||||
|
}
|
44
src/Symfony/Component/Uid/Tests/Factory/UlidFactoryTest.php
Normal file
44
src/Symfony/Component/Uid/Tests/Factory/UlidFactoryTest.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?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\Tests\Factory;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Uid\Factory\UlidFactory;
|
||||||
|
|
||||||
|
final class UlidFactoryTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testCreate()
|
||||||
|
{
|
||||||
|
$ulidFactory = new UlidFactory();
|
||||||
|
|
||||||
|
$ulidFactory->create();
|
||||||
|
|
||||||
|
$ulid1 = $ulidFactory->create(new \DateTime('@999999.123000'));
|
||||||
|
$this->assertSame('999999.123000', $ulid1->getDateTime()->format('U.u'));
|
||||||
|
$ulid2 = $ulidFactory->create(new \DateTime('@999999.123000'));
|
||||||
|
$this->assertSame('999999.123000', $ulid2->getDateTime()->format('U.u'));
|
||||||
|
|
||||||
|
$this->assertFalse($ulid1->equals($ulid2));
|
||||||
|
$this->assertSame(-1, ($ulid1->compare($ulid2)));
|
||||||
|
|
||||||
|
$ulid3 = $ulidFactory->create(new \DateTime('@1234.162524'));
|
||||||
|
$this->assertSame('1234.162000', $ulid3->getDateTime()->format('U.u'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateWithInvalidTimestamp()
|
||||||
|
{
|
||||||
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage('The timestamp must be positive.');
|
||||||
|
|
||||||
|
(new UlidFactory())->create(new \DateTime('@-1000'));
|
||||||
|
}
|
||||||
|
}
|
93
src/Symfony/Component/Uid/Tests/Factory/UuidFactoryTest.php
Normal file
93
src/Symfony/Component/Uid/Tests/Factory/UuidFactoryTest.php
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?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\Tests\Factory;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Uid\Factory\UuidFactory;
|
||||||
|
use Symfony\Component\Uid\NilUuid;
|
||||||
|
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;
|
||||||
|
use Symfony\Component\Uid\UuidV6;
|
||||||
|
|
||||||
|
final class UuidFactoryTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testCreateNamedDefaultVersion()
|
||||||
|
{
|
||||||
|
$this->assertInstanceOf(UuidV5::class, (new UuidFactory())->nameBased('6f80c216-0492-4421-bd82-c10ab929ae84')->create('foo'));
|
||||||
|
$this->assertInstanceOf(UuidV3::class, (new UuidFactory(6, 6, 3))->nameBased('6f80c216-0492-4421-bd82-c10ab929ae84')->create('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateNamed()
|
||||||
|
{
|
||||||
|
$uuidFactory = new UuidFactory();
|
||||||
|
|
||||||
|
// Test custom namespace
|
||||||
|
$uuid1 = $uuidFactory->nameBased('6f80c216-0492-4421-bd82-c10ab929ae84')->create('foo');
|
||||||
|
$this->assertInstanceOf(UuidV5::class, $uuid1);
|
||||||
|
$this->assertSame('d521ceb7-3e31-5954-b873-92992c697ab9', (string) $uuid1);
|
||||||
|
|
||||||
|
// Test default namespace override
|
||||||
|
$uuid2 = $uuidFactory->nameBased(Uuid::v4())->create('foo');
|
||||||
|
$this->assertFalse($uuid1->equals($uuid2));
|
||||||
|
|
||||||
|
// Test version override
|
||||||
|
$uuidFactory = new UuidFactory(6, 6, 3, 4, new NilUuid(), '6f80c216-0492-4421-bd82-c10ab929ae84');
|
||||||
|
$uuid3 = $uuidFactory->nameBased()->create('foo');
|
||||||
|
$this->assertInstanceOf(UuidV3::class, $uuid3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateTimedDefaultVersion()
|
||||||
|
{
|
||||||
|
$this->assertInstanceOf(UuidV6::class, (new UuidFactory())->timeBased()->create());
|
||||||
|
$this->assertInstanceOf(UuidV1::class, (new UuidFactory(6, 1))->timeBased()->create());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateTimed()
|
||||||
|
{
|
||||||
|
$uuidFactory = new UuidFactory(6, 6, 5, 4, '6f80c216-0492-4421-bd82-c10ab929ae84');
|
||||||
|
|
||||||
|
// Test custom timestamp
|
||||||
|
$uuid1 = $uuidFactory->timeBased()->create(new \DateTime('@1611076938.057800'));
|
||||||
|
$this->assertInstanceOf(UuidV6::class, $uuid1);
|
||||||
|
$this->assertSame('1611076938.057800', $uuid1->getDateTime()->format('U.u'));
|
||||||
|
$this->assertSame('c10ab929ae84', $uuid1->getNode());
|
||||||
|
|
||||||
|
// Test default node override
|
||||||
|
$uuid2 = $uuidFactory->timeBased('7c1ede70-3586-48ed-a984-23c8018d9174')->create();
|
||||||
|
$this->assertInstanceOf(UuidV6::class, $uuid2);
|
||||||
|
$this->assertSame('23c8018d9174', $uuid2->getNode());
|
||||||
|
|
||||||
|
// Test version override
|
||||||
|
$uuid3 = (new UuidFactory(6, 1))->timeBased()->create();
|
||||||
|
$this->assertInstanceOf(UuidV1::class, $uuid3);
|
||||||
|
|
||||||
|
// Test negative timestamp and round
|
||||||
|
$uuid4 = $uuidFactory->timeBased()->create(new \DateTime('@-12219292800'));
|
||||||
|
$this->assertSame('-12219292800.000000', $uuid4->getDateTime()->format('U.u'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInvalidCreateTimed()
|
||||||
|
{
|
||||||
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage('The given UUID date cannot be earlier than 1582-10-15.');
|
||||||
|
|
||||||
|
(new UuidFactory())->timeBased()->create(new \DateTime('@-12219292800.001000'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateRandom()
|
||||||
|
{
|
||||||
|
$this->assertInstanceOf(UuidV4::class, (new UuidFactory())->randomBased()->create());
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,7 @@ class Ulid extends AbstractUid
|
|||||||
public function __construct(string $ulid = null)
|
public function __construct(string $ulid = null)
|
||||||
{
|
{
|
||||||
if (null === $ulid) {
|
if (null === $ulid) {
|
||||||
$this->uid = self::generate();
|
$this->uid = static::generate();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -124,10 +124,25 @@ class Ulid extends AbstractUid
|
|||||||
return \DateTimeImmutable::createFromFormat('U.u', substr_replace($time, '.', -3, 0));
|
return \DateTimeImmutable::createFromFormat('U.u', substr_replace($time, '.', -3, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function generate(): string
|
public static function generate(\DateTimeInterface $time = null): string
|
||||||
{
|
{
|
||||||
|
if (null === $time) {
|
||||||
|
return self::doGenerate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 > $time = substr($time->format('Uu'), 0, -3)) {
|
||||||
|
throw new \InvalidArgumentException('The timestamp must be positive.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::doGenerate($time);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function doGenerate(string $mtime = null): string
|
||||||
|
{
|
||||||
|
if (null === $time = $mtime) {
|
||||||
$time = microtime(false);
|
$time = microtime(false);
|
||||||
$time = substr($time, 11).substr($time, 2, 3);
|
$time = substr($time, 11).substr($time, 2, 3);
|
||||||
|
}
|
||||||
|
|
||||||
if ($time !== self::$time) {
|
if ($time !== self::$time) {
|
||||||
$r = unpack('nr1/nr2/nr3/nr4/nr', random_bytes(10));
|
$r = unpack('nr1/nr2/nr3/nr4/nr', random_bytes(10));
|
||||||
@ -139,9 +154,13 @@ class Ulid extends AbstractUid
|
|||||||
self::$rand = array_values($r);
|
self::$rand = array_values($r);
|
||||||
self::$time = $time;
|
self::$time = $time;
|
||||||
} elseif ([0xFFFFF, 0xFFFFF, 0xFFFFF, 0xFFFFF] === self::$rand) {
|
} elseif ([0xFFFFF, 0xFFFFF, 0xFFFFF, 0xFFFFF] === self::$rand) {
|
||||||
|
if (null === $mtime) {
|
||||||
usleep(100);
|
usleep(100);
|
||||||
|
} else {
|
||||||
|
self::$rand = [0, 0, 0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
return self::generate();
|
return self::doGenerate($mtime);
|
||||||
} else {
|
} else {
|
||||||
for ($i = 3; $i >= 0 && 0xFFFFF === self::$rand[$i]; --$i) {
|
for ($i = 3; $i >= 0 && 0xFFFFF === self::$rand[$i]; --$i) {
|
||||||
self::$rand[$i] = 0;
|
self::$rand[$i] = 0;
|
||||||
|
@ -31,11 +31,27 @@ class UuidV1 extends Uuid
|
|||||||
|
|
||||||
public function getDateTime(): \DateTimeImmutable
|
public function getDateTime(): \DateTimeImmutable
|
||||||
{
|
{
|
||||||
return BinaryUtil::timeToDateTime('0'.substr($this->uid, 15, 3).substr($this->uid, 9, 4).substr($this->uid, 0, 8));
|
return BinaryUtil::hexToDateTime('0'.substr($this->uid, 15, 3).substr($this->uid, 9, 4).substr($this->uid, 0, 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNode(): string
|
public function getNode(): string
|
||||||
{
|
{
|
||||||
return uuid_mac($this->uid);
|
return uuid_mac($this->uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function generate(\DateTimeInterface $time = null, Uuid $node = null): string
|
||||||
|
{
|
||||||
|
$uuid = uuid_create(static::TYPE);
|
||||||
|
|
||||||
|
if (null !== $time) {
|
||||||
|
$time = BinaryUtil::dateTimeToHex($time);
|
||||||
|
$uuid = substr($time, 8).'-'.substr($time, 4, 4).'-1'.substr($time, 1, 3).substr($uuid, 18);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($node) {
|
||||||
|
$uuid = substr($uuid, 0, 24).substr($node->uid, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $uuid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,30 @@ class UuidV6 extends Uuid
|
|||||||
public function __construct(string $uuid = null)
|
public function __construct(string $uuid = null)
|
||||||
{
|
{
|
||||||
if (null === $uuid) {
|
if (null === $uuid) {
|
||||||
$uuid = uuid_create(\UUID_TYPE_TIME);
|
$this->uid = static::generate();
|
||||||
$this->uid = substr($uuid, 15, 3).substr($uuid, 9, 4).$uuid[0].'-'.substr($uuid, 1, 4).'-6'.substr($uuid, 5, 3).substr($uuid, 18, 6);
|
} else {
|
||||||
|
parent::__construct($uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateTime(): \DateTimeImmutable
|
||||||
|
{
|
||||||
|
return BinaryUtil::hexToDateTime('0'.substr($this->uid, 0, 8).substr($this->uid, 9, 4).substr($this->uid, 15, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNode(): string
|
||||||
|
{
|
||||||
|
return substr($this->uid, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if ($node) {
|
||||||
|
return $uuid.substr($uuidV1, 24);
|
||||||
|
}
|
||||||
|
|
||||||
// uuid_create() returns a stable "node" that can leak the MAC of the host, but
|
// uuid_create() returns a stable "node" that can leak the MAC of the host, but
|
||||||
// UUIDv6 prefers a truly random number here, let's XOR both to preserve the entropy
|
// UUIDv6 prefers a truly random number here, let's XOR both to preserve the entropy
|
||||||
@ -37,24 +59,11 @@ class UuidV6 extends Uuid
|
|||||||
self::$seed = [random_int(0, 0xffffff), random_int(0, 0xffffff)];
|
self::$seed = [random_int(0, 0xffffff), random_int(0, 0xffffff)];
|
||||||
}
|
}
|
||||||
|
|
||||||
$node = unpack('N2', hex2bin('00'.substr($uuid, 24, 6)).hex2bin('00'.substr($uuid, 30)));
|
$node = unpack('N2', hex2bin('00'.substr($uuidV1, 24, 6)).hex2bin('00'.substr($uuidV1, 30)));
|
||||||
|
|
||||||
$this->uid .= sprintf('%06x%06x',
|
return $uuid.sprintf('%06x%06x',
|
||||||
(self::$seed[0] ^ $node[1]) | 0x010000,
|
(self::$seed[0] ^ $node[1]) | 0x010000,
|
||||||
self::$seed[1] ^ $node[2]
|
self::$seed[1] ^ $node[2]
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
parent::__construct($uuid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDateTime(): \DateTimeImmutable
|
|
||||||
{
|
|
||||||
return BinaryUtil::timeToDateTime('0'.substr($this->uid, 0, 8).substr($this->uid, 9, 4).substr($this->uid, 15, 3));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNode(): string
|
|
||||||
{
|
|
||||||
return substr($this->uid, 24);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user