8000 feature #39507 [Uid] Add UidFactory to create Ulid and Uuid from time… · symfony/symfony@12b9d92 · GitHub
[go: up one dir, main page]

Skip to content

Commit 12b9d92

Browse files
feature #39507 [Uid] Add UidFactory to create Ulid and Uuid from timestamps and randomness/nodes (fancyweb)
This PR was merged into the 5.3-dev branch. Discussion ---------- [Uid] Add UidFactory to create Ulid and Uuid from timestamps and randomness/nodes | Q | A | ------------- | --- | Branch? | 5.x | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - Ref #36097 When you migrate an existing resource identifier to an uid, you might want to choose the timestamp so that it is coherent with the creation date of the existing resource. (eg: I have a row in a table with id=1, created_at=2018-12-11 19:00:00, I would like to use that timestamp to create the resource Ulid). I guess it can also be useful to choose the randomness of the Ulid or the node of the Uuid. From what I understood, v3 and v5 don't need those features, this is why there are not in the factory. See #39507 (review) for more details. Commits ------- 88a99dd [Uid] Add UuidFactory to create Ulid and Uuid from timestamps, namespaces and nodes
2 parents 37e1823 + 88a99dd commit 12b9d92

File tree

23 files changed

+833
-27
lines changed

23 files changed

+833
-27
lines changed

src/Symfony/Bridge/Doctrine/CHANGELOG.md

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

77
* Deprecate `DoctrineTestHelper` and `TestRepositoryFactory`
88
* [BC BREAK] Remove `UuidV*Generator` classes
9+
* Add `UuidGenerator`
910

1011
5.2.0
1112
-----

src/Symfony/Bridge/Doctrine/IdGenerator/UlidGenerator.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,24 @@
1313

1414
use Doctrine\ORM\EntityManager;
1515
use Doctrine\ORM\Id\AbstractIdGenerator;
16+
use Symfony\Component\Uid\Factory\UlidFactory;
1617
use Symfony\Component\Uid\Ulid;
1718

1819
final class UlidGenerator extends AbstractIdGenerator
1920
{
21+
private $factory;
22+
23+
public function __construct(UlidFactory $factory = null)
24+
{
25+
$this->factory = $factory;
26+
}
27+
2028
public function generate(EntityManager $em, $entity): Ulid
2129
{
30+
if ($this->factory) {
31+
return $this->factory->create();
32+
}
33+
2234
return new Ulid();
2335
}
2436
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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\Bridge\Doctrine\IdGenerator;
13+
14+
use Doctrine\ORM\EntityManager;
15+
use Doctrine\ORM\Id\AbstractIdGenerator;
16+
use Symfony\Component\Uid\Factory\UuidFactory;
17+
use Symfony\Component\Uid\Uuid;
18+
19+
final class UuidGenerator extends AbstractIdGenerator
20+
{
21+
private $protoFactory;
22+
private $factory;
23+
private $entityGetter;
24+
25+
public function __construct(UuidFactory $factory = null)
26+
{
27+
$this->protoFactory = $this->factory = $factory ?? new UuidFactory();
28+
}
29+
30+
public function generate(EntityManager $em, $entity): Uuid
31+
{
32+
if (null !== $this->entityGetter) {
33+
if (\is_callable([$entity, $this->entityGetter])) {
34+
return $this->factory->create($entity->{$this->entityGetter}());
35+
}
36+
37+
return $this->factory->create($entity->{$this->entityGetter});
38+
}
39+
40+
return $this->factory->create();
41+
}
42+
43+
/**
44+
* @param Uuid|string|null $namespace
45+
*
46+
* @return static
47+
*/
48+
public function nameBased(string $entityGetter, $namespace = null): self
49+
{
50+
$clone = clone $this;
51+
$clone->factory = $clone->protoFactory->nameBased($namespace);
52+
$clone->entityGetter = $entityGetter;
53+
54+
return $clone;
55+
}
56+
57+
/**
58+
* @return static
59+
*/
60+
public function randomBased(): self
61+
{
62+
$clone = clone $this;
63+
$clone->factory = $clone->protoFactory->randomBased();
64+
$clone->entityGetter = null;
65+
66+
return $clone;
67+
}
68+
69+
/**
70+
* @param Uuid|string|null $node
71+
*
72+
* @return static
73+
*/
74+
public function timeBased($node = null): self
75+
{
76+
$clone = clone $this;
77+
$clone->factory = $clone->protoFactory->timeBased($node);
78+
$clone->entityGetter = null;
79+
80+
return $clone;
81+
}
82+
}

src/Symfony/Bridge/Doctrine/Tests/IdGenerator/UlidGeneratorTest.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
use Doctrine\ORM\Mapping\Entity;
1515
use PHPUnit\Framework\TestCase;
1616
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
17-
use Symfony\Component\Uid\AbstractUid;
17+
use Symfony\Component\Uid\Factory\UlidFactory;
1818
use Symfony\Component\Uid\Ulid;
1919

2020
class UlidGeneratorTest extends TestCase
@@ -25,8 +25,23 @@ public function testUlidCanBeGenerated()
2525
$generator = new UlidGenerator();
2626
$ulid = $generator->generate($em, new Entity());
2727

28-
$this->assertInstanceOf(AbstractUid::class, $ulid);
2928
$this->assertInstanceOf(Ulid::class, $ulid);
3029
$this->assertTrue(Ulid::isValid($ulid));
3130
}
31+
32+
/**
33+
* @requires function \Symfony\Component\Uid\Factory\UlidFactory::create
34+
*/
35+
public function testUlidFactory()
36+
{
37+
$ulid = new Ulid('00000000000000000000000000');
38+
$em = new EntityManager();
39+
$factory = $this->createMock(UlidFactory::class);
40+
$factory->expects($this->any())
41+
->method('create')
42+
->willReturn($ulid);
43+
$generator = new UlidGenerator($factory);
44+
45+
$this->assertSame($ulid, $generator->generate($em, new Entity()));
46+
}
3247
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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\Bridge\Doctrine\Tests\IdGenerator;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
16+
use Symfony\Component\Uid\Factory\UuidFactory;
17+
use Symfony\Component\Uid\NilUuid;
18+
use Symfony\Component\Uid\Uuid;
19+
use Symfony\Component\Uid\UuidV4;
20+
use Symfony\Component\Uid\UuidV6;
21+
22+
/**
23+
* @requires function \Symfony\Component\Uid\Factory\UuidFactory::create
24+
*/
25+
class UuidGeneratorTest extends TestCase
26+
{
27+
public function testUuidCanBeGenerated()
28+
{
29+
$em = new EntityManager();
30+
$generator = new UuidGenerator();
31+
$uuid = $generator->generate($em, new Entity());
32+
33+
$this->assertInstanceOf(Uuid::class, $uuid);
34+
}
35+
36+
public function testCustomUuidfactory()
37+
{
38+
$uuid = new NilUuid();
39+
$em = new EntityManager();
40+
$factory = $this->createMock(UuidFactory::class);
41+
$factory->expects($this->any())
42+
->method('create')
43+
->willReturn($uuid);
44+
$generator = new UuidGenerator($factory);
45+
46+
$this->assertSame($uuid, $generator->generate($em, new Entity()));
47+
}
48+
49+
public function testUuidfactory()
50+
{
51+
$em = new EntityManager();
52+
$generator = new UuidGenerator();
53+
$this->assertInstanceOf(UuidV6::class, $generator->generate($em, new Entity()));
54+
55+
$generator = $generator->randomBased();
56+
$this->assertInstanceOf(UuidV4::class, $generator->generate($em, new Entity()));
57+
58+
$generator = $generator->timeBased();
59+
$this->assertInstanceOf(UuidV6::class, $generator->generate($em, new Entity()));
60+
61+
$generator = $generator->nameBased('prop1', Uuid::NAMESPACE_OID);
62+
$this->assertEquals(Uuid::v5(new Uuid(Uuid::NAMESPACE_OID), '3'), $generator->generate($em, new Entity()));
63+
64+
$generator = $generator->nameBased('prop2', Uuid::NAMESPACE_OID);
65+
$this->assertEquals(Uuid::v5(new Uuid(Uuid::NAMESPACE_OID), '2'), $generator->generate($em, new Entity()));
66+
67+
$generator = $generator->nameBased('getProp4', Uuid::NAMESPACE_OID);
68+
$this->assertEquals(Uuid::v5(new Uuid(Uuid::NAMESPACE_OID), '4'), $generator->generate($em, new Entity()));
69+
70+
$factory = new UuidFactory(6, 6, 5, 5, null, Uuid::NAMESPACE_OID);
71+
$generator = new UuidGenerator($factory);
72+
$generator = $generator->nameBased('prop1');
73+
$this->assertEquals(Uuid::v5(new Uuid(Uuid::NAMESPACE_OID), '3'), $generator->generate($em, new Entity()));
74+
}
75+
}
76+
77+
class Entity
78+
{
79+
public $prop1 = 1;
80+
public $prop2 = 2;
81+
82+
public function prop1()
83+
{
84+
return 3;
85+
}
86+
87+
public function getProp4()
88+
{
89+
return 4;
90+
}
91+
}

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ CHANGELOG
1010
* Added the `dispatcher` option to `debug:event-dispatcher`
1111
* Added the `event_dispatcher.dispatcher` tag
1212
* Added `assertResponseFormatSame()` in `BrowserKitAssertionsTrait`
13+
* Add support for configuring UUID factory services
1314

1415
5.2.0
1516
-----

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\Factory\UuidFactory;
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(UuidFactory::class) ? 'canBeDisabled' : 'canBeEnabled'}()
1904+
->addDefaultsIfNotSet()
1905+
->children()
1906+
->enumNode('default_uuid_version')
1907+
->defaultValue(6)
1908+
->values([6, 4, 1])
1909+
->end()
1910+
->enumNode('name_based_uuid_version')
1911+
->defaultValue(5)
1912+
->values([5, 3])
1913+
->end()
1914+
->scalarNode('name_based_uuid_namespace')
1915+
->cannotBeEmpty()
1916+
->end()
1917+
->enumNode('time_based_uuid_version')
1918+
->defaultValue(6)
1919+
->values([6, 1])
1920+
->end()
1921+
->scalarNode('time_based_uuid_node')
1922+
->cannotBeEmpty()
1923+
->end()
1924+
->end()
1925+
->end()
1926+
->end()
1927+
;
1928+
}
18941929
}

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@
160160
use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand;
161161
use Symfony\Component\Translation\PseudoLocalizationTranslator;
162162
use Symfony\Component\Translation\Translator;
163+
use Symfony\Component\Uid\Factory\UuidFactory;
164+
use Symfony\Component\Uid\UuidV4;
163165
use Symfony\Component\Validator\ConstraintValidatorInterface;
164166
use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader;
165167
use Symfony\Component\Validator\ObjectInitializerInterface;
@@ -449,6 +451,14 @@ public function load(array $configs, ContainerBuilder $container)
449451
$loader->load('web_link.php');
450452
}
451453

454+
if ($this->isConfigEnabled($container, $config['uid'])) {
455+
if (!class_exists(UuidFactory::class)) {
456+
throw new LogicException('Uid support cannot be enabled as the Uid component is not installed. Try running "composer require symfony/uid".');
457+
}
458+
459+
$this->registerUidConfiguration($config['uid'], $container, $loader);
460+
}
461+
452462
$this->addAnnotatedClassesToCompile([
453463
'**\\Controller\\',
454464
'**\\Entity\\',
@@ -2322,6 +2332,27 @@ public static function registerRateLimiter(ContainerBuilder $container, string $
23222332
$container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter');
23232333
}
23242334

2335+
private function registerUidConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
2336+
{
2337+
$loader->load('uid.php');
2338+
2339+
$container->getDefinition('uuid.factory')
2340+
->setArguments([
2341+
$config['default_uuid_version'],
2342+
$config['time_based_uuid_version'],
2343+
$config['name_based_uuid_version'],
2344+
UuidV4::class,
2345+
$config['time_based_uuid_node'] ?? null,
2346+
$config['name_based_uuid_namespace'] ?? null,
2347+
])
2348+
;
2349+
2350+
if (isset($config['name_based_uuid_namespace'])) {
2351+
$container->getDefinition('name_based_uuid.factory')
2352+
->setArguments([$config['name_based_uuid_namespace']]);
2353+
}
2354+
}
2355+
23252356
private function resolveTrustedHeaders(array $headers): int
23262357
{
23272358
$trustedHeaders = 0;

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

Lines changed: 32 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,35 @@
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:attribute name="enabled" type="xsd:boolean" />
699+
<xsd:attribute name="default_uuid_version" type="default_uuid_version" />
700+
<xsd:attribute name="name_based_uuid_version" type="name_based_uuid_version" />
701+
<xsd:attribute name="time_based_uuid_version" type="time_based_uuid_version" />
702+
<xsd:attribute name="name_based_uuid_namespace" type="xsd:string" />
703+
<xsd:attribute name="time_based_uuid_node" type="xsd:string" />
704+
</xsd:complexType>
705+
706+
<xsd:simpleType name="default_uuid_version">
707+
<xsd:restriction base="xsd:int">
708+
<xsd:enumeration value="6" />
709+
<xsd:enumeration value="4" />
710+
<xsd:enumeration value="1" />
711+
</xsd:restriction>
712+
</xsd:simpleType>
713+
714+
<xsd:simpleType name="name_based_uuid_version">
715+
<xsd:restriction base="xsd:int">
716+
<xsd:enumeration value="5" />
717+
<xsd:enumeration value="3" />
718+
</xsd:restriction>
719+
</xsd:simpleType>
720+
721+
<xsd:simpleType name="time_based_uuid_version">
722+
<xsd:restriction base="xsd:int">
723+
<xsd:enumeration value="6" />
724+
<xsd:enumeration value="1" />
725+
</xsd:restriction>
726+
</xsd:simpleType>
695727
</xsd:schema>

0 commit comments

Comments
 (0)
0