PHP enum as ENUM
Ufo\DoctrineHelper\DBAL\AbstractEnumType is a base Doctrine DBAL type that stores PHP backed enum values in a MySQL ENUM(...) column. The list of allowed column values is taken from the enum cases, so the schema and the code do not drift apart.
Setup
1. Enum
namespace App;
enum MyEnum: string
{
case CASE_1 = 'My case 1';
case CASE_2 = 'My case 2';
}
2. DBAL type
Create a class extending AbstractEnumType and set your enum class in the $enum property:
namespace App\DBAL;
use App\MyEnum;
use BackedEnum;
use Ufo\DoctrineHelper\DBAL\AbstractEnumType;
class MyEnumType extends AbstractEnumType
{
protected string|BackedEnum $enum = MyEnum::class;
}
3. Type registration
The type name is the fully qualified enum class name (returned by getName()):
# config/packages/doctrine.yaml
doctrine:
dbal:
types:
App\MyEnum: 'App\DBAL\MyEnumType'
mapping_types:
enum: string
mapping_types: enum: string is needed so that Doctrine can read existing ENUM columns when comparing the schema (migrations, doctrine:schema:update).
4. Entity column
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);
}
}
The column type in the schema is ENUM('My case 1', 'My case 2').
How the type works
| Method | Behaviour |
|---|---|
getSQLDeclaration() |
ENUM('<value>', ...) from $enum::cases() |
getName() |
the enum class, e.g. App\MyEnum |
convertToDatabaseValue() |
checks the value with $enum::from($value) and returns ->value; an invalid value throws InvalidArgumentException with the message Invalid 'App\MyEnum' value. |
convertToPHPValue() |
returns the database value unchanged (string/int), not an enum instance |
requiresSQLCommentHint() |
true — in DBAL 3 the type is marked with a column comment so that schema tools recognize it |
Hence the usage rules:
- store the scalar value (
$case->value) in the entity property, not the enum instance:from()does not accept an enum, and writing fails withInvalidArgumentException; - convert to the enum in the getter (
MyEnum::from($this->case)); - only backed enums work (
enum X: stringor: int); - the
ENUM(...)declaration does not depend on the platform — the type is meant for MySQL / MariaDB; nullin a nullable column is not supported — see Known issues.
Example: EAV Bundle
doctrine-eav-bundle uses AbstractEnumType for the attribute value type:
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;
}
The bundle registers the type not in YAML but from its container extension (PrependExtensionInterface), which is convenient when the type is needed by a library rather than by the application (an excerpt of UfoEAVExtension::prepend(), shortened):
public function prepend(ContainerBuilder $container)
{
$container->loadFromExtension('doctrine', [
'dbal' => [
'types' => [
DiscriminatorType::class => DiscriminatorTypeEnumType::class,
],
'mapping_types' => [
'enum' => 'string',
],
],
]);
}
The entity column:
#[ORM\Column(type: DiscriminatorType::class, length: 255, nullable: true)]