初めに
PHPフレームワークFlowは、DoctrineというORMを搭載しています。DoctrineはデフォルトではEnum型を扱うことができず、そのためには一工夫する必要があります。
今回はPHPフレームワークFlowにおける、DoctrineでEnum型を扱う方法を解説します。
Enum型とは
別名「列挙型」。PHP8.1から追加された機能です。
こんな感じで、取りうる値を列挙することができます。
列挙した値以外は入れることができないため、予期せぬ値が入る余地をなくすことができます。
enum Status: string
{
case STATUS_VISIBLE = 'visible';
case STATUS_INVISIBLE = 'invisible';
}
DoctrineでEnum型を扱いたい
そんなEnumですが、Doctrineでは一工夫しないと使うことができません。
なぜ扱えないのか
Docrineは、「ドメインモデルを元に自動でテーブルを作成することができる」という素晴らしい機能を有しています。ドメインモデルで定義されたstring型やint型の変数は、接続するDBの種類に合わせて自動で型変換されます。
しかし、ユーザが定義したEnum型の場合そうはいきません。TBL作成の際、Doctrineがどの型にマッピングすればいいのかが分からず、エラーが起きてしまうのです。
解決策
これを解決するために、Doctrineの公式ドキュメントには2つの方法が記載されています。
- 案1:string型やint型などで代用する
- 案2:enum型とDBの型の対応をDoctrineに読み込ませる
それぞれ解説していきます。
案1:string型やint型などで代用する
enumの値をstring型やint型などで代用する方法です。
columnDefinitionでENUM型を定義することで、DB上はENUM型で扱うことができます。
<?php
#[Entity]
class Article
{
#[Column(type: "string", columnDefinition: "ENUM('visible', 'invisible')")]
private $status;
}
しかし、この実装には問題があります。
それは、DBに合わせてドメインモデルの型を変更しているという点です。
DBを意識せずにドメインモデルを作成できるのがORMのいいところですが、その良さが完全に失われてしまいます。
案2:enum型とDBの型の対応をDoctrineに読み込ませる(推奨!)
こちらは、PHPのenumを対応を型に読み込ませるというものです。
具体的な方法は後述しますが、案1と違いドメインモデルの変数をEnum型にすることができるため、DBを気にしないで実装することができます。
<?php
#[Entity]
class Article
{
// 一工夫入れることで、Enum型のまま扱うことができる!
#[Column(type: "enumvisibility")]
private $status;
}
試してみる
ということで、実際に案2を試してみましょう。
今回はFlowにおけるEnum型を扱う方法のため、純粋なDoctrineの作法とは少し異なる箇所もございますが、ご了承ください。
プロジェクト構成は以下です。
Quickstart
└ Packages/
├ Application/
| └ Neos.Welcome/
| ├ Classes/
| | ├ Domain/
| | | └ Model
| | | └ EnumTest.php(★)
| | ├ Enum
| | | └ StatusEnum.php(★)
| | └ Type
| | └ StatusEnumType.php(★)
| |
| └ Configuration
| └ Settings.yaml(★)
├ Framework/
└ Libraries/
Enum
まずはPHPで扱うEnum型を定義しました。
<?php
namespace Neos\Welcome\Enum;
enum StatusEnum: string
{
case STATUS_VISIBLE = 'visible';
case STATUS_INVISIBLE = 'invisible';
}
Type
続いて、PHPのEnum型とDBの型のマッピングを行うための処理を作成します。
Doctrineでは、Typeというクラスを継承することでこの仕組みを実現しています。
Doctrineは内部で型を持っており、型は全てこのTypeクラスを継承して作成されています。この型は、PHPの型とDBの型の両方を有しており、変換の際は間に入ってくれるというようなイメージです。
今回は以下のようなstatus_enumという型を作成しました。
<?php
namespace Neos\Welcome\Type;
use Composer\XdebugHandler\Status;
use Neos\Flow\Annotations as Flow;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Neos\Welcome\Enum\StatusEnum;
class StatusEnumType extends Type
{
const STATUS_ENUM = 'status_enum';
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return "ENUM('visible', 'invisible')";
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return StatusEnum::tryFrom($value);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if ($value instanceof StatusEnum) {
return $value->value;
}
throw new \InvalidArgumentException('Invalid StatusEnum');
}
public function getName()
{
return self::STATUS_ENUM;
}
}
各メソッドの役割は以下です。
メソッド | 役割 |
---|---|
getSQLDeclaration | カラム作成時の型の指定方法を文字列で返す |
convertToPHPValue | DB⇒PHPの型に変換するときの処理 |
convertToDatabaseValue | PHP⇒DBの型に変換する時の処理 |
getName | Doctrineの型の名前 |
Settings.yaml
Settings.yamlには先ほど作成したstatus_enum
がPHPとDBそれぞれどの型と対応するのかを定義します。
Neos:
Flow:
persistence:
doctrine:
dbal:
mappingTypes:
status_enum:
className: Neos\Welcome\Type\StatusEnumType
dbType: enum
設定キー | 詳細 |
---|---|
className | PHPの型のクラス名 |
dbType | DBの型 |
Entity
最後に、enumの型を用いたドメインモデルを作成して、準備完了です。
<?php
namespace Neos\Welcome\Domain\Model;
use Neos\Flow\Annotations as Flow;
use Doctrine\ORM\Mapping as ORM;
use Neos\Welcome\Enum\StatusEnum;
/**
* @Flow\Entity
* @ORM\Table(name="enum_test")
*/
class EnumTest {
/**
* @ORM\Column(type="status_enum")
* @var StatusEnum
*/
protected $status;
}
実行
ということで実際にTBLを作成してみましょう。
$ ./flow doctrine:update
Executed a database schema update.
問題なく実行されました!
実際に作成されたTBLがこちらです。
statusがEnum型になっており、visible
とinvisible
が定義されているのが分かります。
mysql> show create table enum_test\G
*************************** 1. row ***************************
Table: enum_test
Create Table: CREATE TABLE `enum_test` (
`persistence_object_identifier` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL,
`status` enum('visible','invisible') COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`persistence_object_identifier`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1 row in set (0.00 sec)
mysql>
終わりに
今回はFlowのDoctrineでEnum型を扱う方法について書きました。
うまくやればORMの良さを活かしながらEnumを扱うことができそうですね。
実際にデータが入ることも次回確認していこうと思います。
ここまで読んでいただきありがとうございました!