MockUuidFactory.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Uid\Factory;
  11. use Symfony\Component\Uid\Exception\InvalidArgumentException;
  12. use Symfony\Component\Uid\Exception\LogicException;
  13. use Symfony\Component\Uid\TimeBasedUidInterface;
  14. use Symfony\Component\Uid\Uuid;
  15. use Symfony\Component\Uid\UuidV1;
  16. use Symfony\Component\Uid\UuidV3;
  17. use Symfony\Component\Uid\UuidV4;
  18. use Symfony\Component\Uid\UuidV5;
  19. use Symfony\Component\Uid\UuidV6;
  20. class MockUuidFactory extends UuidFactory
  21. {
  22. private \Iterator $sequence;
  23. /**
  24. * @param iterable<string|Uuid> $uuids
  25. */
  26. public function __construct(
  27. iterable $uuids,
  28. private Uuid|string|null $timeBasedNode = null,
  29. private Uuid|string|null $nameBasedNamespace = null,
  30. ) {
  31. $this->sequence = match (true) {
  32. \is_array($uuids) => new \ArrayIterator($uuids),
  33. $uuids instanceof \Iterator => $uuids,
  34. $uuids instanceof \Traversable => new \IteratorIterator($uuids),
  35. };
  36. }
  37. public function create(): Uuid
  38. {
  39. if (!$this->sequence->valid()) {
  40. throw new LogicException('No more UUIDs in sequence.');
  41. }
  42. $uuid = $this->sequence->current();
  43. $this->sequence->next();
  44. return match (true) {
  45. $uuid instanceof Uuid => $uuid,
  46. \is_string($uuid) => Uuid::fromString($uuid),
  47. default => throw new InvalidArgumentException(\sprintf('Next UUID in sequence is not a valid UUID string or object: "%s" given.', get_debug_type($uuid))),
  48. };
  49. }
  50. public function randomBased(): RandomBasedUuidFactory
  51. {
  52. return new class($this->create(...)) extends RandomBasedUuidFactory {
  53. public function __construct(
  54. private \Closure $create,
  55. ) {
  56. }
  57. public function create(): UuidV4
  58. {
  59. if (!($uuid = ($this->create)()) instanceof UuidV4) {
  60. throw new InvalidArgumentException(\sprintf('Next UUID in sequence is not a UuidV4: "%s" given.', get_debug_type($uuid)));
  61. }
  62. return $uuid;
  63. }
  64. };
  65. }
  66. public function timeBased(Uuid|string|null $node = null): TimeBasedUuidFactory
  67. {
  68. if (\is_string($node ??= $this->timeBasedNode)) {
  69. $node = Uuid::fromString($node);
  70. }
  71. return new class($this->create(...), $node) extends TimeBasedUuidFactory {
  72. public function __construct(
  73. private \Closure $create,
  74. private ?Uuid $node = null,
  75. ) {
  76. }
  77. public function create(?\DateTimeInterface $time = null): Uuid&TimeBasedUidInterface
  78. {
  79. $uuid = ($this->create)();
  80. if (!($uuid instanceof Uuid && $uuid instanceof TimeBasedUidInterface)) {
  81. throw new InvalidArgumentException(\sprintf('Next UUID in sequence is not a Uuid and TimeBasedUidInterface: "%s" given.', get_debug_type($uuid)));
  82. }
  83. if (null !== $time && $uuid->getDateTime() !== $time) {
  84. throw new InvalidArgumentException(\sprintf('Next UUID in sequence does not match the expected time: "%s" != "%s".', $uuid->getDateTime()->format('@U.uT'), $time->format('@U.uT')));
  85. }
  86. if (null !== $this->node && ($uuid instanceof UuidV1 || $uuid instanceof UuidV6) && $uuid->getNode() !== substr($this->node->toRfc4122(), -12)) {
  87. throw new InvalidArgumentException(\sprintf('Next UUID in sequence does not match the expected node: "%s" != "%s".', $uuid->getNode(), substr($this->node->toRfc4122(), -12)));
  88. }
  89. return $uuid;
  90. }
  91. };
  92. }
  93. public function nameBased(Uuid|string|null $namespace = null): NameBasedUuidFactory
  94. {
  95. if (null === $namespace ??= $this->nameBasedNamespace) {
  96. throw new LogicException(\sprintf('A namespace should be defined when using "%s()".', __METHOD__));
  97. }
  98. return new class($this->create(...), $namespace) extends NameBasedUuidFactory {
  99. public function __construct(
  100. private \Closure $create,
  101. private Uuid|string $namespace,
  102. ) {
  103. }
  104. public function create(string $name): UuidV5|UuidV3
  105. {
  106. if (!($uuid = ($this->create)()) instanceof UuidV5 && !$uuid instanceof UuidV3) {
  107. throw new InvalidArgumentException(\sprintf('Next UUID in sequence is not a UuidV5 or UuidV3: "%s".', get_debug_type($uuid)));
  108. }
  109. $factory = new UuidFactory(nameBasedClass: $uuid::class, nameBasedNamespace: $this->namespace);
  110. if ($uuid->toRfc4122() !== $expectedUuid = $factory->nameBased()->create($name)->toRfc4122()) {
  111. throw new InvalidArgumentException(\sprintf('Next UUID in sequence does not match the expected named UUID: "%s" != "%s".', $uuid->toRfc4122(), $expectedUuid));
  112. }
  113. return $uuid;
  114. }
  115. };
  116. }
  117. }