EasyAdminBundle
EasyAdminBundle copied to clipboard
Add support for php 8.1 enums for choice field
Proposed change allows to use php 8.1 enum in choice field:
if we have enum in entity:
#[ORM\Column(type: Types::STRING, length: 8, nullable: true, enumType: Status::class)]
private Status $status;
then we can in configureFields:
$status = ChoiceField::new('status')->setChoices(Status::cases());
//or
$status = ChoiceField::new('status')->setChoices(
array_reduce(Status::cases(), function (array $elements, Status $status) {
return $elements + ['enum.status.'.$status->value => $status];
}, [])
);
//or
$status = ChoiceField::new('status');
without that change in the case of enums we got the error "Warning: array_flip(): Can only flip string and integer values, entry skipped" there:
$flippedChoices = array_flip($choices);
issue: https://github.com/EasyCorp/EasyAdminBundle/issues/4989
Because of doctrine limitations work only with this kind of enums:
enum Status:string
{
case WAITING = 'waiting';
case ERROR = 'error';
}
and NOT with
enum Status
{
case WAITING;
case ERROR;
}
Since Symfony has EnumType we can do it like this (workaround):
// for index because of array_flip
ChoiceField::new('packingType')
->onlyOnIndex()
->setChoices(function () {
$choices = array_map(static fn (?PackingUnit $unit) => [$unit->value => $unit->name], PackingUnit::cases());
return array_merge(...$choices);
})
->setFormType(EnumType::class)
->setFormTypeOption('class', PackingUnit::class)
->setFormTypeOption('choice_label', function (PackingUnit $enum) {
return $enum->value;
}),
// for form
ChoiceField::new('packingType')
->onlyOnForms()
->setChoices(function () {
$choices = array_map(static fn (?PackingUnit $unit) => [$unit->value => $unit], PackingUnit::cases());
return array_merge(...$choices);
})
->setFormType(EnumType::class)
->setFormTypeOption('class', PackingUnit::class)
->setFormTypeOption('choice_label', function (PackingUnit $enum) {
return $enum->value;
}),
@oleg-andreyev didn't work for me... waiting for this PR
@ERuban updated my workaround.
For me this fixes it for all pages (with the current implementation)
$choiceField = ChoiceField::new('status')
->setChoices(Status::cases())
->setFormType(EnumType::class)
->setFormTypeOption('class', Status::class)
;
if (in_array($pageName, [Crud::PAGE_INDEX, Crud::PAGE_DETAIL], true)) {
$choiceField->setChoices(array_reduce(
Status::cases(),
static fn (array $choices, Status $status) => $choices + [$status->name => $status->value],
[],
));
}
yield $choiceField;
A couple of the workarounds here work, but cause duplication if the key matches the value. e.g.
case OTHER = 'OTHER';
Results in:
OTHER, OTHER
@javiereguiluz please, take a look on this
For me this fixes it for all pages (with the current implementation)
$choiceField = ChoiceField::new('status') ->setChoices(Status::cases()) ->setFormType(EnumType::class) ->setFormTypeOption('class', Status::class) ; if (in_array($pageName, [Crud::PAGE_INDEX, Crud::PAGE_DETAIL], true)) { $choiceField->setChoices(array_reduce( Status::cases(), static fn (array $choices, Status $status) => $choices + [$status->name => $status->value], [], )); } yield $choiceField;
it looks nice for me, but it is not possible to use the ->setTranslatableChoices() in this case.
Or maybe use a different label displayed instead of the Enum name.
Here is an example:
My Enum:
enum PageContentType: string
{
case Banner = 'banner';
case Iframe = 'iframe';
case Image = 'image';
case Quote = 'quote';
public function label(): string
{
return match($this)
{
self::Banner => 'Bannière',
self::Iframe => 'Iframe (vidéo / map)',
self::Image => 'Image et texte',
self::Quote => 'Citation',
};
}
}
And there the Field:
ChoiceField::new('type', 'Type')->setColumns(4)
->setChoices(PageContentType::cases())
->setFormType(EnumType::class)
->setFormTypeOption('class', PageContentType::class)
In the first case, The label displayed on the EA select is "Banner" with the value "banner" but I would like to display a specific label (I tried with the function label()) or be able to translate the "Banner" ?
Maybe it is an issue related to EnumType itself...
EDIT:
If I create a "messages.fr.yaml" with Banner: "Bannière FR"

We can see the translation. Is it a good way of translation the Enum Key in translation file without write a speciale var like "label.banner"?
Hello What's the status of this PR ? :) it would be very useful, I had to abandon enum for the moment and go for a class with const instead
One another area, which this PR doesn't address, is the ChoiceFilter. Maybe it would be good to include this as well?
Thanks Tomek! This has been finally merged!
I added some tests/docs (https://github.com/EasyCorp/EasyAdminBundle/commit/934d5e64234919c4f2c0d933c3086d5d10fe4c5b) but it'd be great if more people could test this in real apps before tagging a new release. Thanks!
Thanks, it works, add PR https://github.com/EasyCorp/EasyAdminBundle/pull/5610 with some php-cs-fixer and PHP < 8.1 issues
Hi, I have errors when use Enum with ChoiceField.
Entity implementation:

Controller: yield ChoiceField::new('weight.unit', new TranslatableMessage('weight.unit.label')) ->setColumns('form-field--small');
Before this last release I've used it like that: yield ChoiceField::new('weight.unit', new TranslatableMessage('weight.unit.label')) ->setChoices($isInfoPage ? WeightUnit::getAsArray() : WeightUnit::cases()) ->setFormType(EnumType::class) ->setFormTypeOption('class', WeightUnit::class) ->setFormTypeOption('choice_label', function (WeightUnit $enum) { return new TranslatableMessage('weight.unit.'.$enum->value); }) ->setCustomOption(ChoiceField::OPTION_USE_TRANSLATABLE_CHOICES, $isInfoPage) ->setColumns('form-field--small');
The problem is that wen I use ChoiceType symfony can't convert Enum value because I don't use EnumType.
@abozhinov
Same problem. It can be solve in your entity by changing the getter method ex
#[ORM\Column(type: 'string', length: 10, enumType: UserType::class, name: "user_type")]
private ?UserType $type = null;
public function getTypeEnum(): ?UserType
{
return $this->type;
}
public function getType(): ?string
{
return $this->type?->value;
}
public function setType($type): self
{
if(!$type instanceof UserType ) {
$type= UserType::from($type);
}
$this->type = $type;
return $this;
}
There is no point to use Enum and return string. I think the best will be to adapt ChoiceField to use EnumType.
I don't say it's the solution. I just put here my quickest solution i used (it can help someone). It's not a minor release (my app was broken today when putting it in prod).
@abozhinov thanks for reporting this. I'd need your help to solve it. I've created #5620 but I'm not sure if it's the right fix. Thanks.
@javiereguiluz the issue is that ChoiceType can't convert Enum to String. I think we should create new EnumField to move the logic from ChoiceField to the new field type.
I have the same problem. +1 for a new dedicated EnumField.
Well, I decided this as a workaround for the current 4.x branch. Javier @javiereguiluz how to implement this better in the bundle itself?
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">
<entity name="Ksn135\CompanyBundle\Entity\DocSetting">
...
<field name="type" type="string" length="20" nullable="false" enum-type="Ksn135\CompanyBundle\Enums\DocType" />
...
</entity>
</doctrine-mapping>
enum DocType: string
{
case INDIVIDUAL = 'individual';
case TYPICAL = 'typical';
public static function choices(): array
{
return [
'admin_label.enum.docType.' . self::INDIVIDUAL->value => self::INDIVIDUAL->name,
'admin_label.enum.docType.' . self::TYPICAL->value => self::TYPICAL->name,
];
}
}
class DocSettingCrudController extends AbstractCrudController
{
...
public function createEditFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface
{
$builder = parent::createEditFormBuilder($entityDto, $formOptions, $context);
$builder->get('type')->addModelTransformer(
new CallbackTransformer(
static fn(mixed $value): mixed => $value, // no convertion
static fn(?string $value): ?DocType => DocType::tryFrom($value)
)
);
return $builder;
}
public function configureFields(string $pageName): iterable
{
return [
...
Fields\ChoiceField::new ('type', 'admin_label.doc.field.type')->setRequired(true)
->setFormType(EnumType::class)
->setFormTypeOption('class', DocType::class)
->setFormTypeOption('choice_label', static fn($choice): ?string => 'admin_label.enum.docType.' . strtolower($choice))
->setFormTypeOption('choice_value', static fn($choice): ?string => $choice instanceof \BackedEnum ? $choice->value : $choice)
->setChoices(\in_array($pageName, [Crud::PAGE_INDEX, Crud::PAGE_DETAIL], true) ? DocType::choices() : DocType::cases())
...
];
}
}
# messages.en.yaml
admin_label:
enum:
docType:
individual: The Individual
typical: Very typical
I found a solution. Check my PR -> https://github.com/EasyCorp/EasyAdminBundle/pull/5640