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)]