diff --git a/src/Symfony/Component/Serializer/Normalizer/UidNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/UidNormalizer.php index a7152613cb..009d334895 100644 --- a/src/Symfony/Component/Serializer/Normalizer/UidNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/UidNormalizer.php @@ -22,9 +22,9 @@ final class UidNormalizer implements NormalizerInterface, DenormalizerInterface, public const NORMALIZATION_FORMAT_KEY = 'uid_normalization_format'; public const NORMALIZATION_FORMAT_CANONICAL = 'canonical'; - public const NORMALIZATION_FORMAT_BASE_58 = 'base_58'; - public const NORMALIZATION_FORMAT_BASE_32 = 'base_32'; - public const NORMALIZATION_FORMAT_RFC_4122 = 'rfc_4122'; + public const NORMALIZATION_FORMAT_BASE58 = 'base58'; + public const NORMALIZATION_FORMAT_BASE32 = 'base32'; + public const NORMALIZATION_FORMAT_RFC4122 = 'rfc4122'; private $defaultContext = [ self::NORMALIZATION_FORMAT_KEY => self::NORMALIZATION_FORMAT_CANONICAL, @@ -45,11 +45,11 @@ final class UidNormalizer implements NormalizerInterface, DenormalizerInterface, switch ($context[self::NORMALIZATION_FORMAT_KEY] ?? $this->defaultContext[self::NORMALIZATION_FORMAT_KEY]) { case self::NORMALIZATION_FORMAT_CANONICAL: return (string) $object; - case self::NORMALIZATION_FORMAT_BASE_58: + case self::NORMALIZATION_FORMAT_BASE58: return $object->toBase58(); - case self::NORMALIZATION_FORMAT_BASE_32: + case self::NORMALIZATION_FORMAT_BASE32: return $object->toBase32(); - case self::NORMALIZATION_FORMAT_RFC_4122: + case self::NORMALIZATION_FORMAT_RFC4122: return $object->toRfc4122(); } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/UidNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/UidNormalizerTest.php index f1b766ea4c..e2e68832a9 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/UidNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/UidNormalizerTest.php @@ -39,7 +39,7 @@ class UidNormalizerTest extends TestCase public function normalizeProvider() { - $uidFormats = [null, 'canonical', 'base_58', 'base_32', 'rfc_4122']; + $uidFormats = [null, 'canonical', 'base58', 'base32', 'rfc4122']; $data = [ [ UuidV1::fromString('9b7541de-6f87-11ea-ab3c-9da9a81562fc'), @@ -149,7 +149,7 @@ class UidNormalizerTest extends TestCase public function testNormalizeWithNormalizationFormatPassedInConstructor() { $uidNormalizer = new UidNormalizer([ - 'uid_normalization_format' => 'rfc_4122', + 'uid_normalization_format' => 'rfc4122', ]); $ulid = Ulid::fromString('01ETWV01C0GYQ5N92ZK7QRGB10'); diff --git a/src/Symfony/Component/Uid/CHANGELOG.md b/src/Symfony/Component/Uid/CHANGELOG.md index 1acafc0e32..03e3e9cbdf 100644 --- a/src/Symfony/Component/Uid/CHANGELOG.md +++ b/src/Symfony/Component/Uid/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * [BC BREAK] Replace `UuidV1::getTime()`, `UuidV6::getTime()` and `Ulid::getTime()` by `UuidV1::getDateTime()`, `UuidV6::getDateTime()` and `Ulid::getDateTime()` * Add `Uuid::NAMESPACE_*` constants from RFC4122 * Add `UlidFactory`, `UuidFactory`, `RandomBasedUuidFactory`, `TimeBasedUuidFactory` and `NameBasedUuidFactory` + * Add commands to generate and inspect UUIDs and ULIDs 5.2.0 ----- diff --git a/src/Symfony/Component/Uid/Command/GenerateUlidCommand.php b/src/Symfony/Component/Uid/Command/GenerateUlidCommand.php new file mode 100644 index 0000000000..7eb1b76abf --- /dev/null +++ b/src/Symfony/Component/Uid/Command/GenerateUlidCommand.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Uid\Factory\UlidFactory; + +class GenerateUlidCommand extends Command +{ + protected static $defaultName = 'ulid:generate'; + protected static $defaultDescription = 'Generates a ULID'; + + private $factory; + + public function __construct(UlidFactory $factory = null) + { + $this->factory = $factory ?? new UlidFactory(); + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this + ->setDefinition([ + new InputOption('time', null, InputOption::VALUE_REQUIRED, 'The ULID timestamp: a parsable date/time string'), + new InputOption('count', 'c', InputOption::VALUE_REQUIRED, 'The number of ULID to generate', 1), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'The ULID output format: base32, base58 or rfc4122', 'base32'), + ]) + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +The %command.name% command generates a ULID. + + php %command.full_name% + +To specify the timestamp: + + php %command.full_name% --time="2021-02-16 14:09:08" + +To generate several ULIDs: + + php %command.full_name% --count=10 + +To output a specific format: + + php %command.full_name% --format=rfc4122 +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + if (null !== $time = $input->getOption('time')) { + try { + $time = new \DateTimeImmutable($time); + } catch (\Exception $e) { + $io->error(sprintf('Invalid timestamp "%s": %s', $time, str_replace('DateTimeImmutable::__construct(): ', '', $e->getMessage()))); + + return 1; + } + } + + switch ($input->getOption('format')) { + case 'base32': $format = 'toBase32'; break; + case 'base58': $format = 'toBase58'; break; + case 'rfc4122': $format = 'toRfc4122'; break; + default: + $io->error(sprintf('Invalid format "%s", did you mean "base32", "base58" or "rfc4122"?', $input->getOption('format'))); + + return 1; + } + + $count = (int) $input->getOption('count'); + try { + for ($i = 0; $i < $count; ++$i) { + $output->writeln($this->factory->create($time)->$format()); + } + } catch (\Exception $e) { + $io->error($e->getMessage()); + + return 1; + } + + return 0; + } +} diff --git a/src/Symfony/Component/Uid/Command/GenerateUuidCommand.php b/src/Symfony/Component/Uid/Command/GenerateUuidCommand.php new file mode 100644 index 0000000000..0e602b08af --- /dev/null +++ b/src/Symfony/Component/Uid/Command/GenerateUuidCommand.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Uid\Factory\UuidFactory; +use Symfony\Component\Uid\Uuid; + +class GenerateUuidCommand extends Command +{ + protected static $defaultName = 'uuid:generate'; + protected static $defaultDescription = 'Generates a UUID'; + + private $factory; + + public function __construct(UuidFactory $factory = null) + { + $this->factory = $factory ?? new UuidFactory(); + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this + ->setDefinition([ + new InputOption('time-based', null, InputOption::VALUE_REQUIRED, 'The timestamp, to generate a time-based UUID: a parsable date/time string'), + new InputOption('node', null, InputOption::VALUE_REQUIRED, 'The UUID whose node part should be used as the node of the generated UUID'), + new InputOption('name-based', null, InputOption::VALUE_REQUIRED, 'The name, to generate a name-based UUID'), + new InputOption('namespace', null, InputOption::VALUE_REQUIRED, 'The UUID to use at the namespace for named-based UUIDs'), + new InputOption('random-based', null, InputOption::VALUE_NONE, 'To generate a random-based UUID'), + new InputOption('count', 'c', InputOption::VALUE_REQUIRED, 'The number of UUID to generate', 1), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'The UUID output format: rfc4122, base58 or base32', 'rfc4122'), + ]) + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +The %command.name% generates a UUID. + + php %command.full_name% + +To generate a time-based UUID: + + php %command.full_name% --time-based=now + +To specify a time-based UUID's node: + + php %command.full_name% --time-based=@1613480254 --node=fb3502dc-137e-4849-8886-ac90d07f64a7 + +To generate a name-based UUID: + + php %command.full_name% --name-based=foo + +To specify a name-based UUID's namespace: + + php %command.full_name% --name-based=bar --namespace=fb3502dc-137e-4849-8886-ac90d07f64a7 + +To generate a random-based UUID: + + php %command.full_name% --random-based + +To generate several UUIDs: + + php %command.full_name% --count=10 + +To output a specific format: + + php %command.full_name% --format=base58 +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + $time = $input->getOption('time-based'); + $node = $input->getOption('node'); + $name = $input->getOption('name-based'); + $namespace = $input->getOption('namespace'); + $random = $input->getOption('random-based'); + + if (false !== ($time ?? $name ?? $random) && 1 < ((null !== $time) + (null !== $name) + $random)) { + $io->error('Only one of "--time-based", "--name-based" or "--random-based" can be provided at a time.'); + + return 1; + } + + if (null === $time && null !== $node) { + $io->error('Option "--node" can only be used with "--time-based".'); + + return 1; + } + + if (null === $name && null !== $namespace) { + $io->error('Option "--namespace" can only be used with "--name-based".'); + + return 1; + } + + switch (true) { + case null !== $time: + if (null !== $node) { + try { + $node = Uuid::fromString($node); + } catch (\InvalidArgumentException $e) { + $io->error(sprintf('Invalid node "%s": %s', $node, $e->getMessage())); + + return 1; + } + } + + try { + $time = new \DateTimeImmutable($time); + } catch (\Exception $e) { + $io->error(sprintf('Invalid timestamp "%s": %s', $time, str_replace('DateTimeImmutable::__construct(): ', '', $e->getMessage()))); + + return 1; + } + + $create = function () use ($node, $time): Uuid { + return $this->factory->timeBased($node)->create($time); + }; + break; + + case null !== $name: + if ($namespace) { + try { + $namespace = Uuid::fromString($namespace); + } catch (\InvalidArgumentException $e) { + $io->error(sprintf('Invalid namespace "%s": %s', $namespace, $e->getMessage())); + + return 1; + } + } + + $create = function () use ($namespace, $name): Uuid { + try { + $factory = $this->factory->nameBased($namespace); + } catch (\LogicException $e) { + throw new \InvalidArgumentException('Missing namespace: use the "--namespace" option or configure a default namespace in the underlying factory.'); + } + + return $factory->create($name); + }; + break; + + case $random: + $create = [$this->factory->randomBased(), 'create']; + break; + + default: + $create = [$this->factory, 'create']; + break; + } + + switch ($input->getOption('format')) { + case 'base32': $format = 'toBase32'; break; + case 'base58': $format = 'toBase58'; break; + case 'rfc4122': $format = 'toRfc4122'; break; + default: + $io->error(sprintf('Invalid format "%s", did you mean "base32", "base58" or "rfc4122"?', $input->getOption('format'))); + + return 1; + } + + $count = (int) $input->getOption('count'); + try { + for ($i = 0; $i < $count; ++$i) { + $output->writeln($create()->$format()); + } + } catch (\Exception $e) { + $io->error($e->getMessage()); + + return 1; + } + + return 0; + } +} diff --git a/src/Symfony/Component/Uid/Command/InspectUlidCommand.php b/src/Symfony/Component/Uid/Command/InspectUlidCommand.php new file mode 100644 index 0000000000..ba6c45c9b8 --- /dev/null +++ b/src/Symfony/Component/Uid/Command/InspectUlidCommand.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Uid\Ulid; + +class InspectUlidCommand extends Command +{ + protected static $defaultName = 'ulid:inspect'; + protected static $defaultDescription = 'Inspects a ULID'; + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('ulid', InputArgument::REQUIRED, 'The ULID to inspect'), + ]) + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +The %command.name% displays information about a ULID. + + php %command.full_name% 01EWAKBCMWQ2C94EXNN60ZBS0Q + php %command.full_name% 1BVdfLn3ERmbjYBLCdaaLW + php %command.full_name% 01771535-b29c-b898-923b-b5a981f5e417 +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + try { + $ulid = Ulid::fromString($input->getArgument('ulid')); + } catch (\InvalidArgumentException $e) { + $io->error($e->getMessage()); + + return 1; + } + + $io->table(['Label', 'Value'], [ + ['Canonical (Base 32)', (string) $ulid], + ['Base 58', $ulid->toBase58()], + ['RFC 4122', $ulid->toRfc4122()], + new TableSeparator(), + ['Timestamp', ($ulid->getDateTime())->format('Y-m-d H:i:s.v')], + ]); + + return 0; + } +} diff --git a/src/Symfony/Component/Uid/Command/InspectUuidCommand.php b/src/Symfony/Component/Uid/Command/InspectUuidCommand.php new file mode 100644 index 0000000000..6b6bbf3ed3 --- /dev/null +++ b/src/Symfony/Component/Uid/Command/InspectUuidCommand.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Uid\Uuid; +use Symfony\Component\Uid\UuidV1; +use Symfony\Component\Uid\UuidV6; + +class InspectUuidCommand extends Command +{ + protected static $defaultName = 'uuid:inspect'; + protected static $defaultDescription = 'Inspects a UUID'; + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('uuid', InputArgument::REQUIRED, 'The UUID to inspect'), + ]) + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +The %command.name% displays information about a UUID. + + php %command.full_name% a7613e0a-5986-11eb-a861-2bf05af69e52 + php %command.full_name% MfnmaUvvQ1h8B14vTwt6dX + php %command.full_name% 57C4Z0MPC627NTGR9BY1DFD7JJ +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + try { + /** @var Uuid $uuid */ + $uuid = Uuid::fromString($input->getArgument('uuid')); + } catch (\InvalidArgumentException $e) { + $io->error($e->getMessage()); + + return 1; + } + + if (-1 === $version = uuid_type($uuid)) { + $version = 'nil'; + } elseif (0 === $version || 2 === $version || 6 < $version) { + $version = 'unknown'; + } + + $rows = [ + ['Version', $version], + ['Canonical (RFC 4122)', (string) $uuid], + ['Base 58', $uuid->toBase58()], + ['Base 32', $uuid->toBase32()], + ]; + + if ($uuid instanceof UuidV1 || $uuid instanceof UuidV6) { + $rows[] = new TableSeparator(); + $rows[] = ['Timestamp', $uuid->getDateTime()->format('Y-m-d H:i:s.u')]; + } + + $io->table(['Label', 'Value'], $rows); + + return 0; + } +} diff --git a/src/Symfony/Component/Uid/Tests/Command/GenerateUlidCommandTest.php b/src/Symfony/Component/Uid/Tests/Command/GenerateUlidCommandTest.php new file mode 100644 index 0000000000..e41e6fc15c --- /dev/null +++ b/src/Symfony/Component/Uid/Tests/Command/GenerateUlidCommandTest.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Uid\Command\GenerateUlidCommand; +use Symfony\Component\Uid\Ulid; + +final class GenerateUlidCommandTest extends TestCase +{ + /** + * @group time-sensitive + */ + public function testDefaults() + { + $time = microtime(false); + $time = substr($time, 11).substr($time, 1, 4); + + $commandTester = new CommandTester(new GenerateUlidCommand()); + + $this->assertSame(0, $commandTester->execute([])); + + $ulid = Ulid::fromBase32(trim($commandTester->getDisplay())); + $this->assertEquals(\DateTimeImmutable::createFromFormat('U.u', $time), $ulid->getDateTime()); + } + + public function testUnparsableTimestamp() + { + $commandTester = new CommandTester(new GenerateUlidCommand()); + + $this->assertSame(1, $commandTester->execute(['--time' => 'foo'])); + $this->assertStringContainsString('Invalid timestamp "foo"', $commandTester->getDisplay()); + } + + public function testTimestampBeforeUnixEpoch() + { + $commandTester = new CommandTester(new GenerateUlidCommand()); + + $this->assertSame(1, $commandTester->execute(['--time' => '@-42'])); + $this->assertStringContainsString('The timestamp must be positive', $commandTester->getDisplay()); + } + + public function testTimestamp() + { + $commandTester = new CommandTester(new GenerateUlidCommand()); + + $this->assertSame(0, $commandTester->execute(['--time' => '2021-02-16 18:09:42.999'])); + + $ulid = Ulid::fromBase32(trim($commandTester->getDisplay())); + $this->assertEquals(new \DateTimeImmutable('2021-02-16 18:09:42.999'), $ulid->getDateTime()); + } + + public function testCount() + { + $commandTester = new CommandTester(new GenerateUlidCommand()); + + $this->assertSame(0, $commandTester->execute(['--count' => '10'])); + + $ulids = explode("\n", trim($commandTester->getDisplay(true))); + $this->assertCount(10, $ulids); + foreach ($ulids as $ulid) { + $this->assertTrue(Ulid::isValid($ulid)); + } + } + + public function testInvalidFormat() + { + $commandTester = new CommandTester(new GenerateUlidCommand()); + + $this->assertSame(1, $commandTester->execute(['--format' => 'foo'])); + $this->assertStringContainsString('Invalid format "foo"', $commandTester->getDisplay()); + } + + public function testFormat() + { + $commandTester = new CommandTester(new GenerateUlidCommand()); + + $this->assertSame(0, $commandTester->execute(['--format' => 'rfc4122'])); + + Ulid::fromRfc4122(trim($commandTester->getDisplay())); + } +} diff --git a/src/Symfony/Component/Uid/Tests/Command/GenerateUuidCommandTest.php b/src/Symfony/Component/Uid/Tests/Command/GenerateUuidCommandTest.php new file mode 100644 index 0000000000..27e829fc2b --- /dev/null +++ b/src/Symfony/Component/Uid/Tests/Command/GenerateUuidCommandTest.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Uid\Command\GenerateUuidCommand; +use Symfony\Component\Uid\Factory\UuidFactory; +use Symfony\Component\Uid\Uuid; +use Symfony\Component\Uid\UuidV1; +use Symfony\Component\Uid\UuidV3; +use Symfony\Component\Uid\UuidV4; +use Symfony\Component\Uid\UuidV5; +use Symfony\Component\Uid\UuidV6; + +final class GenerateUuidCommandTest extends TestCase +{ + public function testDefaults() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + $this->assertSame(0, $commandTester->execute([])); + $this->assertInstanceOf(UuidV6::class, Uuid::fromRfc4122(trim($commandTester->getDisplay()))); + + $commandTester = new CommandTester(new GenerateUuidCommand(new UuidFactory(UuidV4::class))); + $this->assertSame(0, $commandTester->execute([])); + $this->assertInstanceOf(UuidV4::class, Uuid::fromRfc4122(trim($commandTester->getDisplay()))); + } + + public function testTimeBasedWithInvalidNode() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute(['--time-based' => 'now', '--node' => 'foo'])); + $this->assertStringContainsString('Invalid node "foo"', $commandTester->getDisplay()); + } + + public function testTimeBasedWithUnparsableTimestamp() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute(['--time-based' => 'foo'])); + $this->assertStringContainsString('Invalid timestamp "foo"', $commandTester->getDisplay()); + } + + public function testTimeBasedWithTimestampBeforeUUIDEpoch() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute(['--time-based' => '@-16807797990'])); + $this->assertStringContainsString('The given UUID date cannot be earlier than 1582-10-15.', $commandTester->getDisplay()); + } + + public function testTimeBased() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + $this->assertSame(0, $commandTester->execute(['--time-based' => 'now'])); + $this->assertInstanceOf(UuidV6::class, Uuid::fromRfc4122(trim($commandTester->getDisplay()))); + + $commandTester = new CommandTester(new GenerateUuidCommand(new UuidFactory( + UuidV6::class, + UuidV1::class, + UuidV5::class, + UuidV4::class, + 'b2ba9fa1-d84a-4d49-bb0a-691421b27a00' + ))); + $this->assertSame(0, $commandTester->execute(['--time-based' => '2000-01-02 19:09:17.871524 +00:00'])); + $uuid = Uuid::fromRfc4122(trim($commandTester->getDisplay())); + $this->assertInstanceOf(UuidV1::class, $uuid); + $this->assertStringMatchesFormat('1c31e868-c148-11d3-%s-691421b27a00', (string) $uuid); + } + + public function testNameBasedWithInvalidNamespace() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute(['--name-based' => 'foo', '--namespace' => 'bar'])); + $this->assertStringContainsString('Invalid namespace "bar"', $commandTester->getDisplay()); + } + + public function testNameBasedWithoutNamespace() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute(['--name-based' => 'foo'])); + $this->assertStringContainsString('Missing namespace', $commandTester->getDisplay()); + } + + public function testNameBased() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + $this->assertSame(0, $commandTester->execute(['--name-based' => 'foo', '--namespace' => 'bcdf2a0e-e287-4d20-a92f-103eda39b100'])); + $this->assertInstanceOf(UuidV5::class, Uuid::fromRfc4122(trim($commandTester->getDisplay()))); + + $commandTester = new CommandTester(new GenerateUuidCommand(new UuidFactory( + UuidV6::class, + UuidV1::class, + UuidV3::class, + UuidV4::class, + null, + '6fc5292a-5f9f-4ada-94a4-c4063494d657' + ))); + $this->assertSame(0, $commandTester->execute(['--name-based' => 'bar'])); + $this->assertEquals(new UuidV3('54950ff1-375c-33e8-a992-2109e384091f'), Uuid::fromRfc4122(trim($commandTester->getDisplay()))); + } + + public function testRandomBased() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + $this->assertSame(0, $commandTester->execute(['--random-based' => null])); + $this->assertInstanceOf(UuidV4::class, Uuid::fromRfc4122(trim($commandTester->getDisplay()))); + } + + /** + * @dataProvider provideInvalidCombinationOfBasedOptions + */ + public function testInvalidCombinationOfBasedOptions(array $input) + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute($input)); + $this->assertStringContainsString('Only one of "--time-based", "--name-based" or "--random-based"', $commandTester->getDisplay()); + } + + public function provideInvalidCombinationOfBasedOptions() + { + return [ + [['--time-based' => 'now', '--name-based' => 'foo']], + [['--time-based' => 'now', '--random-based' => null]], + [['--name-based' => 'now', '--random-based' => null]], + [['--time-based' => 'now', '--name-based' => 'now', '--random-based' => null]], + ]; + } + + /** + * @dataProvider provideExtraNodeOption + */ + public function testExtraNodeOption(array $input) + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute($input)); + $this->assertStringContainsString('Option "--node" can only be used with "--time-based"', $commandTester->getDisplay()); + } + + public function provideExtraNodeOption() + { + return [ + [['--node' => 'foo']], + [['--name-based' => 'now', '--node' => 'foo']], + [['--random-based' => null, '--node' => 'foo']], + ]; + } + + /** + * @dataProvider provideExtraNamespaceOption + */ + public function testExtraNamespaceOption(array $input) + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute($input)); + $this->assertStringContainsString('Option "--namespace" can only be used with "--name-based"', $commandTester->getDisplay()); + } + + public function provideExtraNamespaceOption() + { + return [ + [['--namespace' => 'foo']], + [['--time-based' => 'now', '--namespace' => 'foo']], + [['--random-based' => null, '--namespace' => 'foo']], + ]; + } + + public function testCount() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['--count' => '10'])); + + $uuids = explode("\n", trim($commandTester->getDisplay(true))); + $this->assertCount(10, $uuids); + foreach ($uuids as $uuid) { + $this->assertTrue(Uuid::isValid($uuid)); + } + } + + public function testInvalidFormat() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(1, $commandTester->execute(['--format' => 'foo'])); + $this->assertStringContainsString('Invalid format "foo"', $commandTester->getDisplay()); + } + + public function testFormat() + { + $commandTester = new CommandTester(new GenerateUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['--format' => 'base32'])); + + Uuid::fromBase32(trim($commandTester->getDisplay())); + } +} diff --git a/src/Symfony/Component/Uid/Tests/Command/InspectUlidCommandTest.php b/src/Symfony/Component/Uid/Tests/Command/InspectUlidCommandTest.php new file mode 100644 index 0000000000..7bd48bc9ab --- /dev/null +++ b/src/Symfony/Component/Uid/Tests/Command/InspectUlidCommandTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Uid\Command\InspectUlidCommand; + +final class InspectUlidCommandTest extends TestCase +{ + public function test() + { + $commandTester = new CommandTester(new InspectUlidCommand()); + + $this->assertSame(1, $commandTester->execute(['ulid' => 'foobar'])); + $this->assertStringContainsString('Invalid ULID: "foobar"', $commandTester->getDisplay()); + + foreach ([ + '01E439TP9XJZ9RPFH3T1PYBCR8', + '1BKocMc5BnrVcuq2ti4Eqm', + '0171069d-593d-97d3-8b3e-23d06de5b308', + ] as $ulid) { + $this->assertSame(0, $commandTester->execute(['ulid' => $ulid])); + $this->assertSame(<<getDisplay(true)); + } + } +} diff --git a/src/Symfony/Component/Uid/Tests/Command/InspectUuidCommandTest.php b/src/Symfony/Component/Uid/Tests/Command/InspectUuidCommandTest.php new file mode 100644 index 0000000000..9505b3bcd4 --- /dev/null +++ b/src/Symfony/Component/Uid/Tests/Command/InspectUuidCommandTest.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Uid\Command\InspectUuidCommand; + +final class InspectUuidCommandTest extends TestCase +{ + public function testInvalid() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(1, $commandTester->execute(['uuid' => 'foobar'])); + $this->assertStringContainsString('Invalid UUID: "foobar"', $commandTester->getDisplay()); + } + + public function testNil() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['uuid' => '00000000-0000-0000-0000-000000000000'])); + $this->assertSame(<<getDisplay(true)); + } + + public function testUnknown() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['uuid' => '461cc9b9-2397-0dba-91e9-33af4c63f7ec'])); + $this->assertSame(<<getDisplay(true)); + + $this->assertSame(0, $commandTester->execute(['uuid' => '461cc9b9-2397-2dba-91e9-33af4c63f7ec'])); + $this->assertSame(<<getDisplay(true)); + + $this->assertSame(0, $commandTester->execute(['uuid' => '461cc9b9-2397-7dba-91e9-33af4c63f7ec'])); + $this->assertSame(<<getDisplay(true)); + + $this->assertSame(0, $commandTester->execute(['uuid' => '461cc9b9-2397-cdba-91e9-33af4c63f7ec'])); + $this->assertSame(<<getDisplay(true)); + } + + public function testV1() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['uuid' => '4c8e3a2a-5993-11eb-a861-2bf05af69e52'])); + $this->assertSame(<<getDisplay(true)); + } + + public function testV3() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['uuid' => 'd108a1a0-957e-3c77-b110-d3f912374439'])); + $this->assertSame(<<getDisplay(true)); + } + + public function testV4() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['uuid' => '705c6eab-a535-4f49-bd51-436d0e81206a'])); + $this->assertSame(<<getDisplay(true)); + } + + public function testV5() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['uuid' => '4ec6c3ad-de94-5f75-b5f0-ad56661a30c4'])); + $this->assertSame(<<getDisplay(true)); + } + + public function testV6() + { + $commandTester = new CommandTester(new InspectUuidCommand()); + + $this->assertSame(0, $commandTester->execute(['uuid' => '1eb59937-b0a7-6288-a861-db3dc2d8d4db'])); + $this->assertSame(<<getDisplay(true)); + } +} diff --git a/src/Symfony/Component/Uid/composer.json b/src/Symfony/Component/Uid/composer.json index 369a3081f4..0eae40ea68 100644 --- a/src/Symfony/Component/Uid/composer.json +++ b/src/Symfony/Component/Uid/composer.json @@ -23,6 +23,9 @@ "php": ">=7.2.5", "symfony/polyfill-uuid": "^1.15" }, + "require-dev": { + "symfony/console": "^4.4|^5.0" + }, "autoload": { "psr-4": { "Symfony\\Component\\Uid\\": "" }, "exclude-from-classmap": [