GenerateUuidCommand.php 7.3 KB

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