Skip to content

Commit ccd555c

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

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.123');
26+
$this->assertSame('999999.123000', $ulid1->getDateTime()->format('U.u'));
27+
$ulid2 = $ulidFactory->create('@999999.123');
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy