PHP enum як ENUM

PHP enum як ENUM

Ufo\DoctrineHelper\DBAL\AbstractEnumType — базовий тип Doctrine DBAL, який зберігає значення PHP backed enum у колонці MySQL ENUM(...). Перелік допустимих значень колонки береться з кейсів enum, тож схема і код не розходяться.

Підключення

1. Enum

namespace App;

enum MyEnum: string
{
    case CASE_1 = 'My case 1';
    case CASE_2 = 'My case 2';
}

2. Тип DBAL

Створіть клас, що наслідує AbstractEnumType, і вкажіть у властивості $enum клас вашого enum:

namespace App\DBAL;

use App\MyEnum;
use BackedEnum;
use Ufo\DoctrineHelper\DBAL\AbstractEnumType;

class MyEnumType extends AbstractEnumType
{
    protected string|BackedEnum $enum = MyEnum::class;
}

3. Реєстрація типу

Ім'я типу — повне ім'я класу enum (його повертає getName()):

# config/packages/doctrine.yaml
doctrine:
    dbal:
        types:
            App\MyEnum: 'App\DBAL\MyEnumType'
        mapping_types:
            enum: string

mapping_types: enum: string потрібен, щоб Doctrine могла прочитати наявні колонки ENUM під час порівняння схеми (міграції, doctrine:schema:update).

4. Колонка сутності

namespace App\Entity;

use App\MyEnum;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'demo')]
class Demo
{
    #[ORM\Column(type: MyEnum::class, nullable: false)]
    protected string $case;

    public function __construct(MyEnum $case)
    {
        $this->case = $case->value;
    }

    public function changeCase(MyEnum $case): void
    {
        $this->case = $case->value;
    }

    public function getCase(): MyEnum
    {
        return MyEnum::from($this->case);
    }
}

Тип колонки в схемі — ENUM('My case 1', 'My case 2').

Як працює тип

Метод Поведінка
getSQLDeclaration() ENUM('<value>', ...) з $enum::cases()
getName() клас enum, напр. App\MyEnum
convertToDatabaseValue() перевіряє значення через $enum::from($value) і повертає ->value; недопустиме значення — InvalidArgumentException з текстом Invalid 'App\MyEnum' value.
convertToPHPValue() повертає значення з бази без змін (string/int), а не екземпляр enum
requiresSQLCommentHint() true — у DBAL 3 тип позначається коментарем колонки, щоб інструменти схеми розпізнавали його

Звідси правила використання:

  • у властивості сутності зберігайте скалярне значення ($case->value), а не екземпляр enum: from() не приймає enum, і запис завершиться InvalidArgumentException;
  • перетворення в enum робіть у геттері (MyEnum::from($this->case));
  • підходять лише backed enum (enum X: string або : int);
  • оголошення ENUM(...) не залежить від платформи — тип розрахований на MySQL / MariaDB;
  • null у nullable-колонці не підтримується — див. Відомі помилки.

Приклад: EAV Bundle

doctrine-eav-bundle використовує AbstractEnumType для типу значення атрибута:

namespace Ufo\EAV\DBAL;

use BackedEnum;
use Ufo\DoctrineHelper\DBAL\AbstractEnumType;
use Ufo\EAV\Utils\DiscriminatorType;

class DiscriminatorTypeEnumType extends AbstractEnumType
{
    protected string|BackedEnum $enum = DiscriminatorType::class;
}

Бандл реєструє тип не в YAML, а з розширення контейнера (PrependExtensionInterface), — так зручно, коли тип потрібен бібліотеці, а не застосунку (фрагмент UfoEAVExtension::prepend(), скорочено):

public function prepend(ContainerBuilder $container)
{
    $container->loadFromExtension('doctrine', [
        'dbal' => [
            'types' => [
                DiscriminatorType::class => DiscriminatorTypeEnumType::class,
            ],
            'mapping_types' => [
                'enum' => 'string',
            ],
        ],
    ]);
}

Колонка в сутності:

#[ORM\Column(type: DiscriminatorType::class, length: 255, nullable: true)]