【PHP】Enumを活用して元号データを管理する方法

## はじめに
PHP8.1のリリースでEnum(列挙型)が搭載されましたね。

型安全性の向上や、コードの可読性と保守性の向上に繋がるので積極的に使っていきたいなと感じています。

元号データを様々な形式で表示するようなEnumを実装してみました。

今回はそちらをご紹介させていただきます。

## Enumとは？
> 列挙型(Enumerations) または Enum を使うと、 開発者は取りうる値を限定した独自の型を定義できます。 これによって、"不正な状態を表現できなくなる" ので、 ドメインモデルを定義する時に特に役立ちます。

https://www.php.net/manual/ja/language.enumerations.overview.php

本記事では、座学的な説明は省かせていただきます。
こちらの記事理解しやすかったです。（特に血液型のところがお気に入り）

https://qiita.com/KeijiYONEDA/items/b300048b1b45e7d68b27#enum%E5%88%97%E6%8C%99%E5%9E%8B%E3%81%A8%E3%81%AF

## Enumで元号データの管理
それでは本題です。

### 従来だと...。
従来だとこんな感じでしょうか？
Class実装してみました。
```php:JapaneseEra.php
class JapaneseEra {
    protected static $eras = [
        '1868-01-25' => '明治',
        '1912-07-30' => '大正',
        '1926-12-25' => '昭和',
        '1989-01-08' => '平成',
        '2019-05-01' => '令和',
    ];

    /**
     * DateTimeオブジェクトから時代を取得.
     * 
     * @param \DateTime $datetime
     */
    public static function getEraName(\DateTime $datetime): ?string {
        foreach (array_reverse(self::$eras, true) as $startDate => $name) {
            if ($datetime >= new \DateTime($startDate)) {
                return $name;
            }
        }
        return null;
    }
}
```
見ての通り、入ってきたDatetimeオブジェクトを元号変換することに特化したClassですね。
もちろん、特別な実装を加えない限り、Extends(拡張)先で`$eras`は書き換えられますね。
（欠点ではなく、柔軟性があると捉える人も多いと思いますが...。）

### Enumで...。
```php:Era.php
enum Era: string {
  case Meiji = 'M';
  case Taisho = 'T';
  case Showa = 'S';
  case Heisei = 'H';
  case Reiwa = 'R';

  /**
   * 元号の開始日を取得(定義).
   */
  public function getStartDate(): string {
    return match($this) {
      self::Meiji => '1873-01-01',
      self::Taisho => '1912-07-30',
      self::Showa => '1926-12-25',
      self::Heisei => '1989-01-08',
      self::Reiwa => '2019-05-01',
    };
  }

  /**
   * 和暦名を取得.
   */
  public function getJapaneseName(): string {
    return match($this) {
      self::Meiji => '明治',
      self::Taisho => '大正',
      self::Showa => '昭和',
      self::Heisei => '平成',
      self::Reiwa => '令和',
    };
  }

  /**
   * Datetimeオブジェクトから元号を取得.
   */
  public static function fromDateTime(\DateTime $date): ?self {
    foreach (array_reverse(self::cases()) as $case) {
      $startDate = new \DateTime($case->getStartDate());
      if ($date >= $startDate) {
        return $case;
      }
    }
    // 日付がどの元号にも属していない場合.
    return NULL;
  }

  /**
   * 元号が開始して何年かを取得.
   */
  public function yearSinceStartEra(\DateTime $datetime): int {
    $startDate = new \DateTime($this->getStartDate());
    return $startDate->diff($datetime)->y + 1;
  }

}

```
こんな感じでしょうか？
イメージまだつきませんかね？使用例を見てみましょう。

#### 使用例 - 基本
```php
// 使用例.
$era = Era::Reiwa;
echo $era->value;
// string: R
echo $era->getStartDate();
// date: 2019-05-01 00:00:00.0 Asia/Tokyo (+09:00)
echo $era->getJapaneseName();
// string: 令和
echo $era->yearSinceStartEra(new \DateTime('now'));
// int: 5
```

どうでしょうか？略称や正式名称等にも対応できますよね。

#### 使用例 - こんな感じもありですよね
```php
$birthday = '2000-11-15';

$era = Era::fromDateTime($birthday);

echo $this->value . '.' . $era->yearSinceStartEra($date);
// H.12

echo $this->getJapaneseName() . $era->yearSinceStartEra($date) . '年';
// 平成12年
```
「H.12」や「平成12年」というように、柔軟にFormatを変更できますよね。

## まとめ
**「じゃあ、Enumを利用する最大の強みは？」**
言及できるほどの深い理解を持ち合わせていませんが、筆者は **「コードの自己文書化機能」** だと感じています。

感覚的にはなりますが、Enumはドキュメント(仕様書)がそのままコードになっているような感じがしませんか？

「Classでいいじゃねーか。」「Traitでいいじゃねーか。」って感じる方の意見...。

わかります。もちろんわかります。

でもタイプセーフティですし、限定された固定の値のみを保持するので堅牢で保守しやすいですよね。
元号のように固定された範囲の値を扱う場合には非常に適しているのかなと私は思います。

以上、あざした。

## 参考
https://www.php.net/manual/ja/language.enumerations.overview.php

https://qiita.com/KeijiYONEDA/items/b300048b1b45e7d68b27#enum%E5%88%97%E6%8C%99%E5%9E%8B%E3%81%A8%E3%81%AF

https://qiita.com/rana_kualu/items/bdfa6c844125c1d0f4d4