diff --git a/src/Maker/MakeEntity.php b/src/Maker/MakeEntity.php index f8825610f..a093f290b 100644 --- a/src/Maker/MakeEntity.php +++ b/src/Maker/MakeEntity.php @@ -393,7 +393,7 @@ private function askForNextField(ConsoleStyle $io, array $fields, string $entity $allValidTypes = array_merge( array_keys($types), EntityRelation::getValidRelationTypes(), - ['relation'] + ['relation', 'enum'] ); while (null === $type) { $question = new Question('Field type (enter ? to see all types)', $defaultType); @@ -430,6 +430,12 @@ private function askForNextField(ConsoleStyle $io, array $fields, string $entity // 0 is the default value given in \Doctrine\DBAL\Schema\Column::$_scale $classProperty->scale = $io->ask('Scale (number of decimals to store: 100.00 would be 2)', '0', Validator::validateScale(...)); + } elseif ('enum' === $type) { + // ask for valid backed enum class + $classProperty->enumType = $io->ask('Enum class', null, Validator::classIsBackedEnum(...)); + + // set type according to user decision + $classProperty->type = $io->confirm('Can this field store multiple enum values', false) ? 'simple_array' : 'string'; } if ($io->confirm('Can this field be null in the database (nullable)', false)) { @@ -465,6 +471,9 @@ private function printAvailableTypes(ConsoleStyle $io): void 'time' => ['time_immutable'], 'dateinterval' => [], ], + 'other' => [ + 'enum' => [], + ], ]; $printSection = static function (array $sectionTypes) use ($io, &$allTypes) { @@ -535,6 +544,7 @@ private function printAvailableTypes(ConsoleStyle $io): void $io->writeln('Other Types'); // empty the values $allTypes = array_map(static fn () => [], $allTypes); + $allTypes = [...$typesTable['other'], ...$allTypes]; $printSection($allTypes); } diff --git a/src/Util/ClassSource/Model/ClassProperty.php b/src/Util/ClassSource/Model/ClassProperty.php index 220c42029..a57406d63 100644 --- a/src/Util/ClassSource/Model/ClassProperty.php +++ b/src/Util/ClassSource/Model/ClassProperty.php @@ -33,6 +33,7 @@ public function __construct( public ?int $scale = null, public bool $needsTypeHint = true, public bool $unique = false, + public ?string $enumType = null, ) { } @@ -52,6 +53,10 @@ public function getAttributes(): array $attributes['unique'] = true; } + if ($this->enumType) { + $attributes['enumType'] = $this->enumType; + } + foreach (['length', 'id', 'nullable', 'precision', 'scale'] as $property) { if (null !== $this->$property) { $attributes[$property] = $this->$property; @@ -74,6 +79,7 @@ public static function createFromObject(FieldMapping|array $data): self precision: $data->precision, scale: $data->scale, unique: $data->unique ?? false, + enumType: $data->enumType, ); } @@ -93,6 +99,7 @@ public static function createFromObject(FieldMapping|array $data): self precision: $data['precision'] ?? null, scale: $data['scale'] ?? null, unique: $data['unique'] ?? false, + enumType: $data['enumType'] ?? null, ); } } diff --git a/src/Util/ClassSourceManipulator.php b/src/Util/ClassSourceManipulator.php index e93aae2ca..a19d1626f 100644 --- a/src/Util/ClassSourceManipulator.php +++ b/src/Util/ClassSourceManipulator.php @@ -120,8 +120,14 @@ public function addEntityField(ClassProperty $mapping): void $attributes[] = $this->buildAttributeNode(Column::class, $mapping->getAttributes(), 'ORM'); $defaultValue = null; + $commentLines = []; if ('array' === $typeHint && !$nullable) { $defaultValue = new Node\Expr\Array_([], ['kind' => Node\Expr\Array_::KIND_SHORT]); + if (null !== $mapping->enumType) { + $commentLines = [sprintf('@return %s[]', Str::getShortClassName($mapping->enumType))]; + } + } elseif (null !== $mapping->enumType) { + $typeHint = $this->addUseStatementIfNecessary($mapping->enumType); } elseif ($typeHint && '\\' === $typeHint[0] && false !== strpos($typeHint, '\\', 1)) { $typeHint = $this->addUseStatementIfNecessary(substr($typeHint, 1)); } @@ -146,7 +152,8 @@ public function addEntityField(ClassProperty $mapping): void // getter methods always have nullable return values // because even though these are required in the db, they may not be set yet // unless there is a default value - null === $defaultValue + null === $defaultValue, + $commentLines ); // don't generate setters for id fields @@ -894,6 +901,16 @@ public function buildAttributeNode(string $attributeClass, array $options, ?stri ); } + if ('enumType' === $option) { + return new Node\Arg( + new Node\Expr\ConstFetch(new Node\Name(Str::getShortClassName($value).'::class')), + false, + false, + [], + new Node\Identifier($option) + ); + } + return new Node\Arg($context->buildNodeExprByValue($value), false, false, [], new Node\Identifier($option)); }, array_keys($options), array_values($options)); diff --git a/src/Validator.php b/src/Validator.php index 616f9b342..f26034944 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -244,4 +244,15 @@ public static function classIsUserInterface($userClassName): string return $userClassName; } + + public static function classIsBackedEnum($backedEnum): string + { + self::classExists($backedEnum); + + if (!isset(class_implements($backedEnum)[\BackedEnum::class])) { + throw new RuntimeCommandException(sprintf('The class "%s" is not a valid BackedEnum.', $backedEnum)); + } + + return $backedEnum; + } } diff --git a/tests/Maker/MakeEntityTest.php b/tests/Maker/MakeEntityTest.php index 87fbb1c9e..c9b4646ab 100644 --- a/tests/Maker/MakeEntityTest.php +++ b/tests/Maker/MakeEntityTest.php @@ -620,6 +620,7 @@ public function getTestDetails(): \Generator // field name 'firstName', 'string', + '', '', // length (default 255) // nullable '', @@ -710,6 +711,28 @@ public function getTestDetails(): \Generator $this->assertFileExists($runner->getPath('src/Entity/User.php')); }), ]; + + yield 'it_creates_a_new_class_with_enum_field' => [$this->createMakeEntityTest() + ->run(function (MakerTestRunner $runner) { + $this->copyEntity($runner, 'Enum/Role-basic.php'); + + $runner->runMaker([ + // entity class name + 'User', + // add additional field + 'role', + 'enum', + 'App\\Entity\\Enum\\Role', + '', + // nullable + 'y', + // finish adding fields + '', + ]); + + $this->runEntityTest($runner); + }), + ]; } /** @param array $data */ diff --git a/tests/fixtures/make-entity/entities/attributes/Enum/Role-basic.php b/tests/fixtures/make-entity/entities/attributes/Enum/Role-basic.php new file mode 100644 index 000000000..d7a0e7ba5 --- /dev/null +++ b/tests/fixtures/make-entity/entities/attributes/Enum/Role-basic.php @@ -0,0 +1,9 @@ +