8000 [Uid] Add UidFactory to create Ulid and Uuid from timestamps and rand… · symfony/symfony@86861d3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 86861d3

Browse files
fancywebnicolas-grekas
authored andcommitted
[Uid] Add UidFactory to create Ulid and Uuid from timestamps and randomness/nodes
1 parent 1a78e05 commit 86861d3

File tree

14 files changed

+538
-22
lines changed

14 files changed

+538
-22
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter;
3535
use Symfony\Component\Serializer\Serializer;
3636
use Symfony\Component\Translation\Translator;
37+
use Symfony\Component\Uid\AbstractUid;
3738
use Symfony\Component\Validator\Validation;
3839
use Symfony\Component\WebLink\HttpHeaderSerializer;
3940
use Symfony\Component\Workflow\WorkflowEvents;
@@ -136,6 +137,7 @@ public function getConfigTreeBuilder()
136137
$this->addSecretsSection($rootNode);
137138
$this->addNotifierSection($rootNode);
138139
$this->addRateLimiterSection($rootNode);
140+
$this->addUidSection($rootNode);
139141

140142
return $treeBuilder;
141143
}
@@ -1891,4 +1893,37 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode)
18911893
->end()
18921894
;
18931895
}
1896+
1897+
private function addUidSection(ArrayNodeDefinition $rootNode)
1898+
{
1899+
$rootNode
1900+
->children()
1901+
->arrayNode('uid')
1902+
->info('Uid configuration')
1903+
->{class_exists(AbstractUid::class) ? 'canBeDisabled' : 'canBeEnabled'}()
1904+
->children()
1905+
->arrayNode('uuid_factory')
1906+
->addDefaultsIfNotSet()
1907+
->children()
1908+
->enumNode('default_named_version')
1909+
->defaultValue(5)
1910+
->values([5, 3])
1911+
->end()
1912+
->enumNode('default_timed_version')
1913+
->defaultValue(6)
1914+
->values([6, 1])
1915+
->end()
1916+
->scalarNode('default_namespace')
1917+
->cannotBeEmpty()
1918+
->end()
1919+
->scalarNode('default_node')
1920+
->cannotBeEmpty()
1921+
->end()
1922+
->end()
1923+
->end()
1924+
->end()
1925+
->end()
1926+
->end()
1927+
;
1928+
}
18941929
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand;
159159
use Symfony\Component\Translation\PseudoLocalizationTranslator;
160160
use Symfony\Component\Translation\Translator;
161+
use Symfony\Component\Uid\UuidFactory;
161162
use Symfony\Component\Validator\ConstraintValidatorInterface;
162163
use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader;
163164
use Symfony\Component\Validator\ObjectInitializerInterface;
@@ -447,6 +448,14 @@ public function load(array $configs, ContainerBuilder $container)
447448
$loader->load('web_link.php');
448449
}
449450

451+
if ($this->isConfigEnabled($container, $config['uid'])) {
452+
if (!class_exists(UuidFactory::class)) {
453+
throw new LogicException('Uid support cannot be enabled as the Uid component is not installed. Try running "composer require symfony/uid".');
454+
}
455+
456+
$this->registerUidConfiguration($config['uid'], $container, $loader);
457+
}
458+
450459
$this->addAnnotatedClassesToCompile([
451460
'**\\Controller\\',
452461
'**\\Entity\\',
@@ -2313,6 +2322,25 @@ public static function registerRateLimiter(ContainerBuilder $container, string $
23132322
$container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter');
23142323
}
23152324

2325+
private function registerUidConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
2326+
{
2327+
$loader->load('uid.php');
2328+
2329+
$uidFactory = $container->getDefinition('uuid.factory');
2330+
$uidFactory->setArguments([
2331+
$config['uuid_factory']['default_named_version'],
2332+
$config['uuid_factory']['default_timed_version'],
2333+
]);
2334+
2335+
if (isset($config['uuid_factory']['default_namespace'])) {
2336+
$uidFactory->addMethodCall('withDefaultNamespace', [$config['uuid_factory']['default_namespace']], true);
2337+
}
2338+
2339+
if (isset($config['uuid_factory']['default_node'])) {
2340+
$uidFactory->addMethodCall('withDefaultNode', [$config['uuid_factory']['default_node']], true);
2341+
}
2342+
}
2343+
23162344
private function resolveTrustedHeaders(array $headers): int
23172345
{
23182346
$trustedHeaders = 0;

src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<xsd:element name="mailer" type="mailer" minOccurs="0" maxOccurs="1" />
3636
<xsd:element name="http-cache" type="http_cache" minOccurs="0" maxOccurs="1" />
3737
<xsd:element name="rate-limiter" type="rate_limiter" minOccurs="0" maxOccurs="1" />
38+
<xsd:element name="uid" type="uid" minOccurs="0" maxOccurs="1" />
3839
</xsd:choice>
3940

4041
<xsd:attribute name="http-method-override" type="xsd:boolean" />
@@ -692,4 +693,32 @@
692693
<xsd:attribute name="interval" type="xsd:string" />
693694
<xsd:attribute name="amount" type="xsd:int" />
694695
</xsd:complexType>
696+
697+
<xsd:complexType name="uid">
698+
<xsd:sequence>
699+
<xsd:element name="uuid_factory" type="uuid_factory" minOccurs="0" maxOccurs="1" />
700+
</xsd:sequence>
701+
<xsd:attribute name="enabled" type="xsd:boolean" />
702+
</xsd:complexType>
703+
704+
<xsd:complexType name="uuid_factory">
705+
<xsd:attribute name="default_named_version" type="uuid_named_version" />
706+
<xsd:attribute name="default_timed_version" type="uuid_timed_version" />
707+
<xsd:attribute name="default_namespace" type="xsd:string" />
708+
<xsd:attribute name="default_node" type="xsd:string" />
709+
</xsd:complexType>
710+
711+
<xsd:simpleType name="uuid_named_version">
712+
<xsd:restriction base="xsd:int">
713+
<xsd:enumeration value="5" />
714+
<xsd:enumeration value="3" />
715+
</xsd:restriction>
716+
</xsd:simpleType>
717+
718+
<xsd:simpleType name="uuid_timed_version">
719+
<xsd:restriction base="xsd:int">
720+
<xsd:enumeration value="6" />
721+
<xsd:enumeration value="1" />
722+
</xsd:restriction>
723+
</xsd:simpleType>
695724
</xsd:schema>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\Component\Uid\UlidFactory;
15+
use Symfony\Component\Uid\UuidFactory;
16+
17+
return static function (ContainerConfigurator $container) {
18+
$container->services()
19+
->set('uuid.factory', UuidFactory::class)
20+
->alias(UuidFactory::class, 'uuid.factory')
21+
22+
->set('ulid.factory', UlidFactory::class)
23+
->alias(UlidFactory::class, 'ulid.factory')
24+
;
25+
};

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\Mailer\Mailer;
2323
use Symfony\Component\Messenger\MessageBusInterface;
2424
use Symfony\Component\Notifier\Notifier;
25+
use Symfony\Component\Uid\UuidFactory;
2526

2627
class ConfigurationTest extends TestCase
2728
{
@@ -563,6 +564,13 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
563564
'enabled' => false,
564565
'limiters' => [],
565566
],
567+
'uid' => [
568+
'enabled' => class_exists(UuidFactory::class),
569+
'uuid_factory' => [
570+
'default_named_version' => 5,
571+
'default_timed_version' => 6,
572+
],
573+
],
566574
];
567575
}
568576
}

src/Symfony/Component/Uid/BinaryUtil.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,34 @@ public static function timeToDateTime(string $time): \DateTimeImmutable
142142

143143
return \DateTimeImmutable::createFromFormat('U.u?', substr_replace($time, '.', -7, 0));
144144
}
145+
146+
/**
147+
* @return string Count of 100-nanosecond intervals since the UUID epoch 1582-10-15 00:00:00 in hexadecimal
148+
*/
149+
public static function dateTimeToTime(\DateTimeInterface $time): string
150+
{
151+
if (\PHP_INT_SIZE >= 8) {
152+
if (-self::TIME_OFFSET_INT > $time = (int) $time->format('Uu0')) {
153+
throw new \InvalidArgumentException('The provided UUID timestamp must be higher than 1582-10-15.');
154+
}
155+
156+
return str_pad(dechex(self::TIME_OFFSET_INT + $time), 16, '0', \STR_PAD_LEFT);
157+
}
158+
159+
$time = $time->format('Uu0');
160+
$negative = '-' === $time[0];
161+
if ($negative && self::TIME_OFFSET_INT < $time = substr($time, 1)) {
162+
throw new \InvalidArgumentException('The provided UUID timestamp must be higher than 1582-10-15.');
163+
}
164+
$time = self::fromBase($time, self::BASE10);
165+
$time = str_pad($time, 8, "\0", \STR_PAD_LEFT);
166+
167+
if ($negative) {
168+
$time = self::add($time, self::TIME_OFFSET_COM1) ^ "\xff\xff\xff\xff\xff\xff\xff\xff";
169+
} else {
170+
$time = self::add($time, self::TIME_OFFSET_BIN);
171+
}
172+
173+
return bin2hex($time);
174+
}
145175
}

src/Symfony/Component/Uid/CHANGELOG.md

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

77
* Add `AbstractUid::fromBinary()`, `AbstractUid::fromBase58()`, `AbstractUid::fromBase32()` and `AbstractUid::fromRfc4122()`
88
* [BC BREAK] Replace `UuidV1::getTime()`, `UuidV6::getTime()` and `Ulid::getTime()` by `UuidV1::getDateTime()`, `UuidV6::getDateTime()` and `Ulid::getDateTime()`
9+
* Add `UuidFactory` and `UlidFactory`
910

1011
5.2.0
1112
-----
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Uid\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Uid\UlidFactory;
16+
17+
final class UlidFactoryTest extends TestCase
18+
{
19+
public function testCreate()
20+
{
21+
$ulidFactory = new UlidFactory();
22+
23+
$ulidFactory->create();
24+
25+
$ulid1 = $ulidFactory->create('@999999.123000');
26+
$this->assertSame('999999.123000', $ulid1->getDateTime()->format('U.u'));
27+
$ulid2 = $ulidFactory->create('@999999.123000');
28+
$this->assertSame('999999.123000', $ulid2->getDateTime()->format('U.u'));
29+
30+
$this->assertFalse($ulid1->equals($ulid2));
31+
$this->assertSame(-1, ($ulid1->compare($ulid2)));
32+
33+
$ulid3 = $ulidFactory->create('@1234.162524');
34+
$this->assertSame('1234.162000', $ulid3->getDateTime()->format('U.u'));
35+
}
36+
37+
public function testCreateWithInvalidTimestamp()
38+
{
39+
$this->expectException(\InvalidArgumentException::class);
40+
$this->expectExceptionMessage('The timestamp must be positive.');
41+
42+
(new UlidFactory())->create('@-1000');
43+
}
44+
}

0 commit comments

Comments
 (0)
0