Skip to content

Commit

Permalink
feature #1488 allow the option to use ulid's for entity id's
Browse files Browse the repository at this point in the history
  • Loading branch information
jrushlow authored Mar 22, 2024
1 parent 970f4c0 commit f6c8719
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 23 deletions.
16 changes: 13 additions & 3 deletions src/Doctrine/EntityClassGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@
use ApiPlatform\Metadata\ApiResource;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bridge\Doctrine\Types\UlidType;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\Maker\Common\EntityIdTypeEnum;
use Symfony\Bundle\MakerBundle\Str;
use Symfony\Bundle\MakerBundle\Util\ClassNameDetails;
use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Uid\Ulid;
use Symfony\Component\Uid\Uuid;
use Symfony\UX\Turbo\Attribute\Broadcast;

Expand All @@ -37,7 +40,7 @@ public function __construct(
) {
}

public function generateEntityClass(ClassNameDetails $entityClassDetails, bool $apiResource, bool $withPasswordUpgrade = false, bool $generateRepositoryClass = true, bool $broadcast = false, bool $useUuidIdentifier = false): string
public function generateEntityClass(ClassNameDetails $entityClassDetails, bool $apiResource, bool $withPasswordUpgrade = false, bool $generateRepositoryClass = true, bool $broadcast = false, EntityIdTypeEnum $useUuidIdentifier = EntityIdTypeEnum::INT): string
{
$repoClassDetails = $this->generator->createClassNameDetails(
$entityClassDetails->getRelativeName(),
Expand All @@ -60,13 +63,20 @@ public function generateEntityClass(ClassNameDetails $entityClassDetails, bool $
$useStatements->addUseStatement(ApiResource::class);
}

if ($useUuidIdentifier) {
if (EntityIdTypeEnum::UUID === $useUuidIdentifier) {
$useStatements->addUseStatement([
Uuid::class,
UuidType::class,
]);
}

if (EntityIdTypeEnum::ULID === $useUuidIdentifier) {
$useStatements->addUseStatement([
Ulid::class,
UlidType::class,
]);
}

$entityPath = $this->generator->generateClass(
$entityClassDetails->getFullName(),
'doctrine/Entity.tpl.php',
Expand All @@ -77,7 +87,7 @@ public function generateEntityClass(ClassNameDetails $entityClassDetails, bool $
'broadcast' => $broadcast,
'should_escape_table_name' => $this->doctrineHelper->isKeyword($tableName),
'table_name' => $tableName,
'uses_uuid' => $useUuidIdentifier,
'id_type' => $useUuidIdentifier,
]
);

Expand Down
24 changes: 24 additions & 0 deletions src/Maker/Common/EntityIdTypeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

/*
* This file is part of the Symfony MakerBundle package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\MakerBundle\Maker\Common;

/**
* @author Jesse Rushlow <jr@rushlow.dev>
*
* @internal
*/
enum EntityIdTypeEnum
{
case INT;
case UUID;
case ULID;
}
44 changes: 36 additions & 8 deletions src/Maker/Common/UidTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@

namespace Symfony\Bundle\MakerBundle\Maker\Common;

use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Uid\Ulid;
use Symfony\Component\Uid\Uuid;

/**
Expand All @@ -23,18 +25,23 @@
*/
trait UidTrait
{
/**
* Set by calling checkIsUsingUuid().
* Use in a maker's generate() to determine if entity wants to use uuid's.
*/
protected bool $usesUid = false;
private bool $usesUuid = false;
private bool $usesUlid = false;

/**
* Call this in a maker's configure() to consistently allow entity's with UUID's.
* This should be called after you calling "setHelp()" in the maker.
*/
protected function addWithUuidOption(Command $command): Command
{
$command->addOption('with-uuid', 'u', InputOption::VALUE_NONE, 'Use UUID for entity "id"');
$uidHelp = file_get_contents(\dirname(__DIR__, 2).'/Resources/help/_WithUid.txt');
$help = $command->getHelp()."\n".$uidHelp;

$command
->addOption(name: 'with-uuid', mode: InputOption::VALUE_NONE, description: 'Use UUID for entity "id"')
->addOption('with-ulid', mode: InputOption::VALUE_NONE, description: 'Use ULID for entity "id"')
->setHelp($help)
;

return $command;
}
Expand All @@ -44,8 +51,29 @@ protected function addWithUuidOption(Command $command): Command
*/
protected function checkIsUsingUid(InputInterface $input): void
{
if (($this->usesUid = $input->getOption('with-uuid')) && !class_exists(Uuid::class)) {
throw new \RuntimeException('You must install symfony/uid to use Uuid\'s as "id" (composer require symfony/uid)');
if (($this->usesUuid = $input->getOption('with-uuid')) && !class_exists(Uuid::class)) {
throw new RuntimeCommandException('You must install symfony/uid to use Uuid\'s as "id" (composer require symfony/uid)');
}

if (($this->usesUlid = $input->getOption('with-ulid')) && !class_exists(Ulid::class)) {
throw new RuntimeCommandException('You must install symfony/uid to use Ulid\'s as "id" (composer require symfony/uid)');
}

if ($this->usesUuid && $this->usesUlid) {
throw new RuntimeCommandException('Setting --with-uuid & --with-ulid at the same time is not allowed. Please choose only one.');
}
}

protected function getIdType(): EntityIdTypeEnum
{
if ($this->usesUuid) {
return EntityIdTypeEnum::UUID;
}

if ($this->usesUlid) {
return EntityIdTypeEnum::ULID;
}

return EntityIdTypeEnum::INT;
}
}
10 changes: 4 additions & 6 deletions src/Maker/MakeEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,10 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
if (!$classExists) {
$broadcast = $input->getOption('broadcast');
$entityPath = $this->entityClassGenerator->generateEntityClass(
$entityClassDetails,
$input->getOption('api-resource'),
false,
true,
$broadcast,
$this->usesUid
entityClassDetails: $entityClassDetails,
apiResource: $input->getOption('api-resource'),
broadcast: $broadcast,
useUuidIdentifier: $this->getIdType(),
);

if ($broadcast) {
Expand Down
7 changes: 6 additions & 1 deletion src/Maker/MakeResetPassword.php
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,12 @@ private function successMessage(ConsoleStyle $io, string $requestClassName): voi

private function generateRequestEntity(Generator $generator, ClassNameDetails $requestClassNameDetails, ClassNameDetails $repositoryClassNameDetails): void
{
$requestEntityPath = $this->entityClassGenerator->generateEntityClass($requestClassNameDetails, false, generateRepositoryClass: false, useUuidIdentifier: $this->usesUid);
$requestEntityPath = $this->entityClassGenerator->generateEntityClass(
entityClassDetails: $requestClassNameDetails,
apiResource: false,
generateRepositoryClass: false,
useUuidIdentifier: $this->getIdType()
);

$generator->writeChanges();

Expand Down
8 changes: 4 additions & 4 deletions src/Maker/MakeUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,10 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
// A) Generate the User class
if ($userClassConfiguration->isEntity()) {
$classPath = $this->entityClassGenerator->generateEntityClass(
$userClassNameDetails,
false, // api resource
$userClassConfiguration->hasPassword(), // security user
useUuidIdentifier: $this->usesUid
entityClassDetails: $userClassNameDetails,
apiResource: false, // api resource
withPasswordUpgrade: $userClassConfiguration->hasPassword(), // security user
useUuidIdentifier: $this->getIdType()
);
} else {
$classPath = $generator->generateClass($userClassNameDetails->getFullName(), 'Class.tpl.php');
Expand Down
10 changes: 10 additions & 0 deletions src/Resources/help/_WithUid.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Instead of using the default "int" type for the entity's "id", you can use the
UUID type from Symfony's Uid component.
<href=https://symfony.com/doc/current/components/uid.html#storing-uuids-in-databases>https://symfony.com/doc/current/components/uid.html#storing-uuids-in-databases</>

<info>php %command.full_name% --with-uuid</info>

Or you can use the ULID type from Symfony's Uid component.
<href=https://symfony.com/doc/current/components/uid.html#storing-ulids-in-databases>https://symfony.com/doc/current/components/uid.html#storing-ulids-in-databases</>

<info>php %command.full_name% --with-ulid</info>
18 changes: 17 additions & 1 deletion src/Resources/skeleton/doctrine/Entity.tpl.php
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
<?php

use Symfony\Bundle\MakerBundle\Maker\Common\EntityIdTypeEnum;

?>
<?= "<?php\n" ?>

namespace <?= $namespace ?>;
Expand All @@ -15,7 +20,7 @@
<?php endif ?>
class <?= $class_name."\n" ?>
{
<?php if ($uses_uuid): ?>
<?php if (EntityIdTypeEnum::UUID === $id_type): ?>
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
Expand All @@ -26,6 +31,17 @@ public function getId(): ?Uuid
{
return $this->id;
}
<?php elseif (EntityIdTypeEnum::ULID === $id_type): ?>
#[ORM\Id]
#[ORM\Column(type: UlidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: 'doctrine.ulid_generator')]
private ?Ulid $id = null;

public function getId(): ?Ulid
{
return $this->id;
}
<?php else: ?>
#[ORM\Id]
#[ORM\GeneratedValue]
Expand Down
20 changes: 20 additions & 0 deletions tests/Maker/MakeEntityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,26 @@ public function getTestDetails(): \Generator
}),
];

yield 'it_creates_a_new_class_with_ulid' => [$this->createMakeEntityTest()
->addExtraDependencies('symfony/uid')
->run(function (MakerTestRunner $runner) {
$runner->runMaker([
// entity class name
'User',
// add not additional fields
'',
], '--with-ulid');

$this->assertFileExists($runner->getPath('src/Entity/User.php'));

$content = file_get_contents($runner->getPath('src/Entity/User.php'));
$this->assertStringContainsString('use Symfony\Component\Uid\Ulid;', $content);
$this->assertStringContainsString('[ORM\CustomIdGenerator(class: \'doctrine.ulid_generator\')]', $content);

$this->runEntityTest($runner);
}),
];

yield 'it_creates_a_new_class_with_fields' => [$this->createMakeEntityTest()
->run(function (MakerTestRunner $runner) {
$runner->runMaker([
Expand Down
60 changes: 60 additions & 0 deletions tests/Maker/MakeResetPasswordTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,66 @@ public function getTestDetails(): \Generator
}),
];

yield 'it_generates_with_ulid' => [$this->createMakerTest()
->setSkippedPhpVersions(80100, 80109)
->addExtraDependencies('symfony/uid')
->run(function (MakerTestRunner $runner) {
$this->makeUser($runner);

$output = $runner->runMaker([
'App\Entity\User',
'app_home',
'jr@rushlow.dev',
'SymfonyCasts',
], '--with-ulid');

$this->assertStringContainsString('Success', $output);

$generatedFiles = [
'src/Controller/ResetPasswordController.php',
'src/Entity/ResetPasswordRequest.php',
'src/Form/ChangePasswordFormType.php',
'src/Form/ResetPasswordRequestFormType.php',
'src/Repository/ResetPasswordRequestRepository.php',
'templates/reset_password/check_email.html.twig',
'templates/reset_password/email.html.twig',
'templates/reset_password/request.html.twig',
'templates/reset_password/reset.html.twig',
];

foreach ($generatedFiles as $file) {
$this->assertFileExists($runner->getPath($file));
}

$resetPasswordRequestEntityContents = file_get_contents($runner->getPath('src/Entity/ResetPasswordRequest.php'));
$this->assertStringContainsString('use Symfony\Component\Uid\Ulid;', $resetPasswordRequestEntityContents);
$this->assertStringContainsString('[ORM\CustomIdGenerator(class: \'doctrine.ulid_generator\')]', $resetPasswordRequestEntityContents);

$configFileContents = file_get_contents($runner->getPath('config/packages/reset_password.yaml'));

// Flex recipe adds comments in reset_password.yaml, check file was replaced by maker
$this->assertStringNotContainsString('#', $configFileContents);

$resetPasswordConfig = $runner->readYaml('config/packages/reset_password.yaml');

$this->assertSame('App\Repository\ResetPasswordRequestRepository', $resetPasswordConfig['symfonycasts_reset_password']['request_password_repository']);

$runner->writeFile(
'config/packages/mailer.yaml',
Yaml::dump(['framework' => [
'mailer' => ['dsn' => 'null://null'],
]])
);

$runner->copy(
'make-reset-password/tests/it_generates_with_normal_setup.php',
'tests/ResetPasswordFunctionalTest.php'
);

$runner->runTests();
}),
];

yield 'it_generates_with_translator_installed' => [$this->createMakerTest()
// @legacy - drop skipped versions when PHP 8.1 is no longer supported.
->setSkippedPhpVersions(80100, 80109)
Expand Down
21 changes: 21 additions & 0 deletions tests/Maker/MakeUserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,27 @@ public function getTestDetails(): \Generator
}),
];

yield 'it_generates_entity_with_password_and_ulid' => [$this->createMakerTest()
->addExtraDependencies('doctrine')
->addExtraDependencies('symfony/uid')
->run(function (MakerTestRunner $runner) {
$runner->copy(
'make-user/standard_setup',
''
);

$runner->runMaker([
// user class name
'User',
'y', // entity
'email', // identity property
'y', // with password
], '--with-ulid');

$this->runUserTest($runner, 'it_generates_entity_with_password_and_ulid.php');
}),
];

yield 'it_generates_non_entity_no_password' => [$this->createMakerTest()
->addExtraDependencies('doctrine')
->run(function (MakerTestRunner $runner) {
Expand Down
Loading

0 comments on commit f6c8719

Please sign in to comment.