Home > Blockchain >  How to persist enum with Symfony Doctrine (entity field type:"enum")
How to persist enum with Symfony Doctrine (entity field type:"enum")

Time:10-21

I have an enum (thanks php 8.1) that I want to record in database. The field is of type Enum. With a SQL query via Adminer, there is no problem.

UPDATE `users` SET `grade` = 1, WHERE `id` = '51';

In this case, in Adminer, I can see 'A5' in the grade column (name of enum with value 1)

But with Doctrine and a symfony form, the data is not persisted (on this field only). I have no error.

if ($form->isSubmitted() && $form->isValid()){

        $entityManager = $this->getDoctrine()->getManager();
        $entityManager->persist($user);
        $entityManager->flush();

This the "add" in ProfileType

            ->add('grade', ChoiceType::class, [
            'data' => $user ? $user->getGrade() : GradeEnum::D1,
            'placeholder' => 'Choose an option',
            'required' => false,
            'choices' => [
                GradeEnum::D1->name => GradeEnum::D1,
                GradeEnum::D2->name => GradeEnum::D2,
                GradeEnum::C1->name => GradeEnum::C1,
                GradeEnum::C2->name => GradeEnum::C2,
                GradeEnum::B1->name => GradeEnum::B1,
                GradeEnum::B2->name => GradeEnum::B2,
                GradeEnum::A1->name => GradeEnum::A1,
                GradeEnum::A2->name => GradeEnum::A2,
                GradeEnum::A3->name => GradeEnum::A3,
                GradeEnum::A4->name => GradeEnum::A4,
                GradeEnum::A5->name => GradeEnum::A5,
                GradeEnum::A5plus->name => GradeEnum::A5plus,
            ],
            'attr' => [
                'class' => 'mb-3'
            ],
            'label' => 'Grade'
        ])

I made a dump of the form. I get an enum.

App\Enum\GradeEnum {#332
  name: "A5"
  value: 1
}

If I try to persist data in this field with another type (like integer), I have a mistake. This is normal. But why doesn't doctrine persist with an enum?

CodePudding user response:

Yes doctrine does not support php Enum from version 8.1 but you can create your own Doctrine type. Defining a custom Doctrine type

In your case it should be something link this:

<?php

namespace App\DBAL;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use App\Enum\GradeEnum;
use InvalidArgumentException;
use JetBrains\PhpStorm\Pure;

class GradeType extends Type
{
    protected string $name;
    protected array $values = [
        GradeEnum::A5plus,
        GradeEnum::A5,
        GradeEnum::A4,
        GradeEnum::A3,
        GradeEnum::A2,
        GradeEnum::A1,
        GradeEnum::B2,
        GradeEnum::B1,
        GradeEnum::C2,
        GradeEnum::C1,
        GradeEnum::D2,
        GradeEnum::D1,
    ];

    const GRADE = 'grade';

    public function getName(): string
    {
        return self::GRADE;
    }

    public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
    {
        $values = array_map(function ($val) {
            /** @var GradeEnum $val */
            return "'{$val->name}'";
        }, $this->values);

        return 'ENUM(' . implode(', ', $values) . ')';
    }

    #[Pure]
    public function convertToPHPValue($value, AbstractPlatform $platform): ?GradeEnum
    {
        if (null === $value) {
            return null;
        }
        return GradeEnum::getGradeFromString($value);
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        /** @var GradeEnum $value */
        if ($value !== null) {
            if (!in_array($value, $this->values)) {
                throw new InvalidArgumentException("Invalid '" . $this->name . "' value.");
            } else {
                return $value->name;
            }
        }

        return null;
    }

    public function canRequireSQLConversion(): bool
    {
        return true;
    }

    public function requiresSQLCommentHint(AbstractPlatform $platform): bool
    {
        return true;
    }
}
<?php

namespace App\Enum;

enum GradeEnum: int
{
    case A5plus = 0;
    case A5 = 1;
    case A4 = 2;
    case A3 = 3;
    case A2 = 4;
    case A1 = 5;
    case B2 = 6;
    case B1 = 7;
    case C2 = 8;
    case C1 = 9;
    case D2 = 10;
    case D1 = 11;

    public static function getGradeFromString(string $grade): GradeEnum {
        return match ($grade) {
            self::A5plus->name => self::A5plus,
            self::A5->name => self::A5,
            self::A4->name => self::A4,
            self::A3->name => self::A3,
            self::A2->name => self::A2,
            self::A1->name => self::A1,
            self::B1->name => self::B1,
            self::B2->name => self::B2,
            self::C1->name => self::C1,
            self::C2->name => self::C2,
            self::D1->name => self::D1,
            self::D2->name => self::D2,
        };
    }
}

Do not forget to add your type into your doctrine.yaml

doctrine:
    dbal:
        types:
            grade: App\DBAL\GradeType
        mapping_types:
            enum: string
            grade: grade

Have a good day !

  • Related