Skip to content

Commit 05d2f6c

Browse files
committed
feature #1785 [make:entity] Autocompletion for enum classes (tcoch, GromNaN)
This PR was merged into the 1.x branch. Discussion ---------- [make:entity] Autocompletion for enum classes This PR aims to provide autocompletion for `enum` classes, just like it exists for doctrine relations. Two tests on generating entity with enum types already exist (https://github.com/symfony/maker-bundle/blob/1.x/tests/Maker/MakeEntityTest.php#L697-L741) to cover that this new feature won't break anything. Commits ------- 370a910 Fix CS 56c662c Inject the directory path and namespace in the EnumHelper instead of the FileManager 80264ad Replace functional test for EnumHelper with a unit test 87d4d93 First try to create unit tests d04d1d5 Fix: handle if no src folder is found f0f1231 Use CPP for $enumHelper 2c357eb Re-organise constructor to ensure it doesn't break BC promise 2033b9f Fix Fabbot PHP code style aaa64b3 Add test failing if enum does not exists bf9ab35 Use enum_exists() function instead of class_exists() 34b11a5 Add licensing informations 24bf99c Fix for CS / Fabbot / Checks ca1c53d Fix for CI Static Analysis / PHP-CS-Fixer a976a34 feat: autocompletion for enum classes
2 parents 182f0c2 + 370a910 commit 05d2f6c

File tree

7 files changed

+224
-2
lines changed

7 files changed

+224
-2
lines changed

src/Maker/MakeEntity.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassProperty;
3232
use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator;
3333
use Symfony\Bundle\MakerBundle\Util\CliOutputHelper;
34+
use Symfony\Bundle\MakerBundle\Util\EnumHelper;
3435
use Symfony\Bundle\MakerBundle\Validator;
3536
use Symfony\Bundle\MercureBundle\DependencyInjection\MercureExtension;
3637
use Symfony\Component\Console\Command\Command;
@@ -292,7 +293,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
292293

293294
break;
294295
default:
295-
throw new \Exception('Invalid relation type');
296+
throw new \Exception('Invalid relation type.');
296297
}
297298

298299
// save the inverse side if it's being mapped
@@ -431,7 +432,7 @@ private function askForNextField(ConsoleStyle $io, array $fields, string $entity
431432
$classProperty->scale = $io->ask('Scale (number of decimals to store: 100.00 would be 2)', '0', Validator::validateScale(...));
432433
} elseif ('enum' === $type) {
433434
// ask for valid backed enum class
434-
$classProperty->enumType = $io->ask('Enum class', null, Validator::classIsBackedEnum(...));
435+
$classProperty->enumType = $this->askEnumDetails($io);
435436

436437
// set type according to user decision
437438
$classProperty->type = $io->confirm('Can this field store multiple enum values', false) ? 'simple_array' : 'string';
@@ -556,6 +557,35 @@ private function createEntityClassQuestion(string $questionText): Question
556557
return $question;
557558
}
558559

560+
private function askEnumDetails(ConsoleStyle $io): string
561+
{
562+
$targetEnumClass = null;
563+
while (null === $targetEnumClass) {
564+
$question = $this->createEnumQuestion('Enum class (e.g. <fg=yellow>App\Enum\Foo</>)');
565+
566+
$answeredEnumClass = $io->askQuestion($question);
567+
568+
if (enum_exists($answeredEnumClass)) {
569+
$targetEnumClass = $answeredEnumClass;
570+
} else {
571+
$io->error(\sprintf('Unknown enum "%s"', $answeredEnumClass));
572+
}
573+
}
574+
575+
return $targetEnumClass;
576+
}
577+
578+
private function createEnumQuestion(string $questionText): Question
579+
{
580+
$question = new Question($questionText);
581+
$question->setValidator(Validator::classIsBackedEnum(...));
582+
583+
$enumHelper = new EnumHelper($this->fileManager->getRootDirectory().'/src', 'App');
584+
$question->setAutocompleterValues($enumHelper->getAllEnums());
585+
586+
return $question;
587+
}
588+
559589
private function askRelationDetails(ConsoleStyle $io, string $generatedEntityClass, string $type, string $newFieldName): EntityRelation
560590
{
561591
// ask the targetEntity

src/Util/EnumHelper.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\MakerBundle\Util;
13+
14+
use Symfony\Component\Finder\Exception\DirectoryNotFoundException;
15+
use Symfony\Component\Finder\Finder;
16+
17+
/**
18+
* @internal
19+
*/
20+
class EnumHelper
21+
{
22+
public function __construct(
23+
private string $directory,
24+
private string $namespace = 'App',
25+
) {
26+
}
27+
28+
public function getAllEnums(): array
29+
{
30+
$enums = [];
31+
$finder = new Finder();
32+
33+
// Check all PHP files in the directory
34+
try {
35+
$finder->files()
36+
->in($this->directory)
37+
->name('*.php');
38+
} catch (DirectoryNotFoundException $e) {
39+
return [];
40+
}
41+
42+
foreach ($finder as $file) {
43+
$relativePath = str_replace([$this->directory.'/', '.php'], ['', ''], $file->getRealPath());
44+
$className = $this->namespace.'\\'.str_replace('/', '\\', $relativePath);
45+
46+
if (enum_exists($className)) {
47+
$enums[] = $className;
48+
}
49+
}
50+
51+
return $enums;
52+
}
53+
}

tests/Maker/MakeEntityTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,26 @@ public static function getTestDetails(): \Generator
716716
}),
717717
];
718718

719+
yield 'it_cannot_create_a_new_class_with_fake_enum_field' => [self::createMakeEntityTest()
720+
->run(static function (MakerTestRunner $runner) {
721+
$output = $runner->runMaker([
722+
// entity class name
723+
'User',
724+
// add additional field
725+
'fakeEnum',
726+
'enum',
727+
'App\\Enum\\Fake',
728+
'',
729+
// nullable
730+
'y',
731+
// finish adding fields
732+
'',
733+
], '', true);
734+
735+
self::assertStringContainsString('Class "App\Enum\Fake" doesn\'t exist', $output);
736+
}),
737+
];
738+
719739
yield 'it_creates_a_new_class_with_enum_field_multiple_and_nullable' => [self::createMakeEntityTest()
720740
->run(static function (MakerTestRunner $runner) {
721741
self::copyEntity($runner, 'Enum/Role-basic.php');

tests/Util/EnumHelperTest.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\MakerBundle\Tests\Util;
13+
14+
use Composer\Autoload\ClassLoader;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Bundle\MakerBundle\Util\EnumHelper;
17+
18+
class EnumHelperTest extends TestCase
19+
{
20+
private string $fixturesDir = __DIR__.'/fixtures/enum_helper';
21+
private ClassLoader $classLoader;
22+
23+
protected function setUp(): void
24+
{
25+
$this->classLoader = new ClassLoader();
26+
$this->classLoader->addPsr4('App\\', $this->fixturesDir.'/src');
27+
$this->classLoader->register(true);
28+
}
29+
30+
protected function tearDown(): void
31+
{
32+
$this->classLoader?->unregister();
33+
}
34+
35+
public function testGetAllEnumsReturnsEnumClasses()
36+
{
37+
$enumHelper = new EnumHelper($this->fixturesDir.'/src', 'App');
38+
$enums = $enumHelper->getAllEnums();
39+
40+
$this->assertCount(2, $enums);
41+
$this->assertContains('App\\Enum\\Status', $enums);
42+
$this->assertContains('App\\Enum\\Priority', $enums);
43+
}
44+
45+
public function testGetAllEnumsExcludesNonEnumClasses()
46+
{
47+
$enumHelper = new EnumHelper($this->fixturesDir.'/src', 'App');
48+
$enums = $enumHelper->getAllEnums();
49+
50+
$this->assertNotContains('App\\Entity\\User', $enums);
51+
}
52+
53+
public function testGetAllEnumsReturnsEmptyArrayWhenSrcDirDoesNotExist()
54+
{
55+
$enumHelper = new EnumHelper(__DIR__.'/nonexistent', 'App');
56+
$enums = $enumHelper->getAllEnums();
57+
58+
$this->assertIsArray($enums);
59+
$this->assertEmpty($enums);
60+
}
61+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace App\Entity;
13+
14+
class User
15+
{
16+
private string $name;
17+
}
18+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace App\Enum;
13+
14+
enum Priority: int
15+
{
16+
case LOW = 1;
17+
case MEDIUM = 2;
18+
case HIGH = 3;
19+
}
20+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace App\Enum;
13+
14+
enum Status: string
15+
{
16+
case DRAFT = 'draft';
17+
case PUBLISHED = 'published';
18+
case ARCHIVED = 'archived';
19+
}
20+

0 commit comments

Comments
 (0)