Repository: symfony/symfony
File: src/Symfony/Component/Form/Extension/Core/Type/EnumType.php (PHP)

Code before:

namespace Symfony\Component\Form\Extension\Core\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatableInterface;

/**
 * A choice type for native PHP enums.
 *
 * @author Alexander M. Turek <me@derrabus.de>
 */
final class EnumType extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver
            ->setRequired(['class'])
            ->setAllowedTypes('class', 'string')
            ->setAllowedValues('class', enum_exists(...))
            ->setDefault('choices', static fn (Options $options): array => $options['class']::cases())
            ->setDefault('choice_label', static function (Options $options) {
                $choices = $options['choices'];

                if (\is_array($choices) && !array_is_list($choices)) {
                    // Check for grouped choices
                    $isGrouped = false;
                    foreach ($choices as $value) {
                        if (\is_array($value) && array_is_list($value)) {
                            $isGrouped = true;
                            break;
                        }
                    }
                    if (!$isGrouped) {
                        // Associative array = custom labels
                        return null;
                    }
                }

                return static fn (\UnitEnum $choice) => $choice instanceof TranslatableInterface ? $choice : $choice->name;
            })
            ->setDefault('choice_value', static function (Options $options): ?\Closure {
                if (!is_a($options['class'], \BackedEnum::class, true)) {
                    return null;
                }

                return static function (?\BackedEnum $choice): ?string {
                    if (null === $choice) {
                        return null;
                    }

Review comment (suggestion):
What happens if you have a mix of grouped and non-grouped choices, for example:

```php
'choices' => [
    'Group 1' => [
        'Custom Yes' => Answer::Yes,
        'Custom No' => Answer::No,
    ],
    'Custom 42' => Answer::FortyTwo,
],
```

Also, what if you have a mix of labeled and unlabeled choices:

```php
'choices' => [
    Answer::Yes,
    Answer::No,
    'Custom 42' => Answer::FortyTwo,
],
```

Code after:

namespace Symfony\Component\Form\Extension\Core\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatableInterface;

/**
 * A choice type for native PHP enums.
 *
 * @author Alexander M. Turek <me@derrabus.de>
 */
final class EnumType extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver
            ->setRequired(['class'])
            ->setAllowedTypes('class', 'string')
            ->setAllowedValues('class', enum_exists(...))
            ->setDefault('choices', static fn (Options $options): array => $options['class']::cases())
            ->setDefault('choice_label', static function (Options $options) {
                return static function (\UnitEnum $choice, int|string $key): string|TranslatableInterface {
                    if (\is_int($key)) {
                        // Key is an integer, use the enum's name (or translatable)
                        return $choice instanceof TranslatableInterface ? $choice : $choice->name;
                    }

                    // Key is a string, use it as the label
                    return $key;
                };
            })
            ->setDefault('choice_value', static function (Options $options): ?\Closure {
                if (!is_a($options['class'], \BackedEnum::class, true)) {
                    return null;
                }

                return static function (?\BackedEnum $choice): ?string {
                    if (null === $choice) {
                        return null;
                    }

                    return (string) $choice->value;
                };
            })
        ;
    }

    public function getParent(): string
    {