既存 Rails アプリケーションの Enums 定義を、別の PHP アプリケーションに取り込むひとつの方法を考えてみました。

背景

背景として、以下のような状況を想定しています。

  • PHP アプリケーションが複数存在している。
  • データの永続化には RDB を使用しており、複数アプリケーション間で共用されている。
  • 複数アプリケーションには、同じような実装が重複して存在している。
  • 重複排除その他を目的として、Rails で REST API を用意し、共通したビジネスロジックはすべてそちらに実装を移そうとしている。
  • 現在、その過渡期である。

動機

上記のような状況においては、複数のアプリケーション間で共通しないビジネスロジックについては、REST API への移行優先度は当然低くなると思われます。
そうなると、PHP アプリケーションと REST API の両方から共用 RDB にアクセスするような状況が生まれます。

さて、Rails には Enums という便利な機能があります。
Enum (列挙型)については、Java といった他のプログラミング言語でも同様の概念が存在します。
ここでは、Enum 自体についての説明は省きます。

一方、PHP では Enum が標準でバンドルされていません。
SPL Types という PECL 拡張が用意されており、これをインストールすることによって、PHP ネイティブな 列挙型(SplEnum)が使用可能にはなります。
ただし、得たいのが Enum だけであるのに対して、SPL Types ではその他の型もいろいろと含まれていたりと、導入するのが大げさな印象です。
そのため、Enum を実現する簡易クラスを各自で用意したり、ライブラリを使用するケースが多いようです。

簡易的な Enum クラスの実装は、以下の記事がとても参考になります。

また、ライブラリでは以下のようなものが公開されています。

詳しく確認していませんが、Enum 関連のライブラリは、おそらく無数に存在していると思われます。

これらの内容を見ると分かるのですが、だいたい Enum という名前のクラスを用意してそれを継承し、定数宣言を列挙したサブクラスを作成すれば便利機能が使えるというアプローチが基本のようです。

前置きが長くなりましたが、そもそもの動機に戻ります。

PHP アプリケーション側では、永続化対象データの属性(たとえば〇〇ステータスなど)について Enum の導入をしておらず、定数による属性値定義もあいまいでマジックナンバーだらけといった状況があるとします。(レガシーシステムには散見されると思います。)
そのような状況で、Enum を導入したいとなった時に、せっかくなら Rails 側で定義済みである Enums の情報を元に、PHP の Enum クラスを継承したサブクラスを生成出来ると手っ取り早そうと考えたのが、今回の動機です。

アプローチと取り込み

アプローチは単純に以下のように考えました。

  1. Rails アプリケーションから定義済み Enums 情報を YAML や JSON 形式でエクスポートする。
  2. 上記の YAML や JSON をパースして PHP のソースコード(Enum クラスのサブクラス)を生成する。
  3. 生成したソースコードをクラスごとにファイルとして出力する。

まず、エクスポートについては、以下の Rails Plugin を作成しました。Rake タスクになっています。

gem は以下で公開しています。

プロジェクトの Gemfile に追加した後、次のようにして、Enums 定義をエクスポートします。
標準出力に出力するので、適宜リダイレクトしてください。

$ bundle install
$ bin/rake enum_exporter:yaml > enums.yml
# JSON の場合は以下
$ bin/rake enum_exporter:json > enums.json

これでエクスポート側は完了です。

たとえば以下のような定義が出力されます。(YAML の場合)

---
- User:
    state:
      active: 0
      inactive: 1
- Shop:
    state:
      active: 0
      inactive: 1

次に、PHP クラスの生成については、以下の PHP ライブラリ(CLIツール)を作成しました。

インストールは以下のようにします。

$ composer require genkiroid/enum-generator

エクスポートした定義ファイルと、出力先を指定して以下のコマンドを実行します。

$ enum-generator --in enums.yaml --out /tmp/enums/

出力先が存在してエラーになる場合は、forceオプションを指定すると強制的に上書きします。

$ enum-generator --in enums.yaml --out /tmp/enums/ --force

前述の YAML ファイルを入力とした場合、出力されるファイルの内容は、以下のようになります。

  • UserState.php
<?php

class UserState extends Enum
{
    const ACTIVE = 0;
    const INACTIVE = 1;
}
  • ShopState.php
<?php

class ShopState extends Enum
{
    const ACTIVE = 0;
    const INACTIVE = 1;
}

後は、自作でもパッケージでも良いので Enum クラスを導入すれば、PHP アプリケーションで Enum の機能を使用出来るようになります。

まとめと所感

なかなかニッチな状況と問題領域になりますが、抱えている状況が近い場合は、それなりに便利かも知れません。
ただ、PHP アプリケーションが REST API の完全なクライアントになる場合は出る幕がなく、PHP アプリケーションが共用 RDB に直接アクセスする場合のみ機能します。
(REST API とのやり取りは、Enum の値ではなくラベルのほうで行う必要がある。そうしないと Rails に怒られる。)

今回の取り組みでの所感は以下な感じでした。

  • Rails Plugin の dummy app がテストを用意する上でとても便利。
  • PHP の getopt に慣れそうな気がしない。
  • ライブラリ名のアンダースコアとハイフンに、Ruby と PHP の文化の違いを感じたが、個人の Github リポジトリに並んでるのを見ると些か気持ち悪い。
  • nikic/PHP-Parser はやっぱり便利。