1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【PHPフレームワークFlow】Enum型をdoctrineで扱う方法

Last updated at Posted at 2024-04-12

初めに

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型を定義しました。

StatusEnum.php
<?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という型を作成しました。

StatusEnumType.php
<?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それぞれどの型と対応するのかを定義します。

Settings.yaml
Neos:
  Flow:
    persistence:
      doctrine:
        dbal:
          mappingTypes:
            status_enum:
              className: Neos\Welcome\Type\StatusEnumType
              dbType: enum
設定キー 詳細
className PHPの型のクラス名
dbType DBの型

Entity

最後に、enumの型を用いたドメインモデルを作成して、準備完了です。

EnumTest.php
<?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型になっており、visibleinvisibleが定義されているのが分かります。

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を扱うことができそうですね。
実際にデータが入ることも次回確認していこうと思います。

ここまで読んでいただきありがとうございました!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?