GenerateUuidCommand.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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\Command;
  11. use Symfony\Component\Console\Attribute\AsCommand;
  12. use Symfony\Component\Console\Command\Command;
  13. use Symfony\Component\Console\Completion\CompletionInput;
  14. use Symfony\Component\Console\Completion\CompletionSuggestions;
  15. use Symfony\Component\Console\Input\InputInterface;
  16. use Symfony\Component\Console\Input\InputOption;
  17. use Symfony\Component\Console\Output\ConsoleOutputInterface;
  18. use Symfony\Component\Console\Output\OutputInterface;
  19. use Symfony\Component\Console\Style\SymfonyStyle;
  20. use Symfony\Component\Uid\Factory\UuidFactory;
  21. use Symfony\Component\Uid\Uuid;
  22. #[AsCommand(name: 'uuid:generate', description: 'Generate a UUID')]
  23. class GenerateUuidCommand extends Command
  24. {
  25. public function __construct(
  26. private UuidFactory $factory = new UuidFactory(),
  27. ) {
  28. parent::__construct();
  29. }
  30. protected function configure(): void
  31. {
  32. $this
  33. ->setDefinition([
  34. new InputOption('time-based', null, InputOption::VALUE_REQUIRED, 'The timestamp, to generate a time-based UUID: a parsable date/time string'),
  35. new InputOption('node', null, InputOption::VALUE_REQUIRED, 'The UUID whose node part should be used as the node of the generated UUID'),
  36. new InputOption('name-based', null, InputOption::VALUE_REQUIRED, 'The name, to generate a name-based UUID'),
  37. new InputOption('namespace', null, InputOption::VALUE_REQUIRED, 'The UUID to use at the namespace for named-based UUIDs, predefined namespaces keywords "dns", "url", "oid" and "x500" are accepted'),
  38. new InputOption('random-based', null, InputOption::VALUE_NONE, 'To generate a random-based UUID'),
  39. new InputOption('count', 'c', InputOption::VALUE_REQUIRED, 'The number of UUID to generate', 1),
  40. new InputOption('format', 'f', InputOption::VALUE_REQUIRED, \sprintf('The UUID output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'rfc4122'),
  41. ])
  42. ->setHelp(<<<'EOF'
  43. The <info>%command.name%</info> generates a UUID.
  44. <info>php %command.full_name%</info>
  45. To generate a time-based UUID:
  46. <info>php %command.full_name% --time-based=now</info>
  47. To specify a time-based UUID's node:
  48. <info>php %command.full_name% --time-based=@1613480254 --node=fb3502dc-137e-4849-8886-ac90d07f64a7</info>
  49. To generate a name-based UUID:
  50. <info>php %command.full_name% --name-based=foo</info>
  51. To specify a name-based UUID's namespace:
  52. <info>php %command.full_name% --name-based=bar --namespace=fb3502dc-137e-4849-8886-ac90d07f64a7</info>
  53. To generate a random-based UUID:
  54. <info>php %command.full_name% --random-based</info>
  55. To generate several UUIDs:
  56. <info>php %command.full_name% --count=10</info>
  57. To output a specific format:
  58. <info>php %command.full_name% --format=base58</info>
  59. EOF
  60. )
  61. ;
  62. }
  63. protected function execute(InputInterface $input, OutputInterface $output): int
  64. {
  65. $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
  66. $time = $input->getOption('time-based');
  67. $node = $input->getOption('node');
  68. $name = $input->getOption('name-based');
  69. $namespace = $input->getOption('namespace');
  70. $random = $input->getOption('random-based');
  71. if (false !== ($time ?? $name ?? $random) && 1 < ((null !== $time) + (null !== $name) + $random)) {
  72. $io->error('Only one of "--time-based", "--name-based" or "--random-based" can be provided at a time.');
  73. return 1;
  74. }
  75. if (null === $time && null !== $node) {
  76. $io->error('Option "--node" can only be used with "--time-based".');
  77. return 1;
  78. }
  79. if (null === $name && null !== $namespace) {
  80. $io->error('Option "--namespace" can only be used with "--name-based".');
  81. return 1;
  82. }
  83. switch (true) {
  84. case null !== $time:
  85. if (null !== $node) {
  86. try {
  87. $node = Uuid::fromString($node);
  88. } catch (\InvalidArgumentException $e) {
  89. $io->error(\sprintf('Invalid node "%s": %s', $node, $e->getMessage()));
  90. return 1;
  91. }
  92. }
  93. try {
  94. new \DateTimeImmutable($time);
  95. } catch (\Exception $e) {
  96. $io->error(\sprintf('Invalid timestamp "%s": %s', $time, str_replace('DateTimeImmutable::__construct(): ', '', $e->getMessage())));
  97. return 1;
  98. }
  99. $create = fn (): Uuid => $this->factory->timeBased($node)->create(new \DateTimeImmutable($time));
  100. break;
  101. case null !== $name:
  102. if ($namespace && !\in_array($namespace, ['dns', 'url', 'oid', 'x500'], true)) {
  103. try {
  104. $namespace = Uuid::fromString($namespace);
  105. } catch (\InvalidArgumentException $e) {
  106. $io->error(\sprintf('Invalid namespace "%s": %s', $namespace, $e->getMessage()));
  107. return 1;
  108. }
  109. }
  110. $create = function () use ($namespace, $name): Uuid {
  111. try {
  112. $factory = $this->factory->nameBased($namespace);
  113. } catch (\LogicException) {
  114. throw new \InvalidArgumentException('Missing namespace: use the "--namespace" option or configure a default namespace in the underlying factory.');
  115. }
  116. return $factory->create($name);
  117. };
  118. break;
  119. case $random:
  120. $create = $this->factory->randomBased()->create(...);
  121. break;
  122. default:
  123. $create = $this->factory->create(...);
  124. break;
  125. }
  126. $formatOption = $input->getOption('format');
  127. if (\in_array($formatOption, $this->getAvailableFormatOptions(), true)) {
  128. $format = 'to'.ucfirst($formatOption);
  129. } else {
  130. $io->error(\sprintf('Invalid format "%s", supported formats are "%s".', $formatOption, implode('", "', $this->getAvailableFormatOptions())));
  131. return 1;
  132. }
  133. $count = (int) $input->getOption('count');
  134. try {
  135. for ($i = 0; $i < $count; ++$i) {
  136. $output->writeln($create()->$format());
  137. }
  138. } catch (\Exception $e) {
  139. $io->error($e->getMessage());
  140. return 1;
  141. }
  142. return 0;
  143. }
  144. public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
  145. {
  146. if ($input->mustSuggestOptionValuesFor('format')) {
  147. $suggestions->suggestValues($this->getAvailableFormatOptions());
  148. }
  149. }
  150. /** @return string[] */
  151. private function getAvailableFormatOptions(): array
  152. {
  153. return [
  154. 'base32',
  155. 'base58',
  156. 'rfc4122',
  157. ];
  158. }
  159. }