はじめに
WebアプリケーションフレームワークとしてのSymfonyには、フォームやORM、バリデーションetc…といった機能があります。今回は少し視点をずらして、これらの機能の中から、アプリケーション内の何かと何かの「あいだにあるもの」に着目してみます。実は、この「あいだにあるもの」たちの機能をうまく使うことで、アプリケーションコードが想定するデータの形を整えることができます。
例えば、YAMLから設定データを読み込んで、PHPの連想配列で扱うプログラムを記述しているとしましょう。設定データを扱うあらゆるメソッドで、その都度キーの存在有無をチェックしているようなコードを想像してみてください。とてもイヤな感じですね。このようなコードがあったら、単純に無駄なコードが多いという表面的な問題だけでなく、正しいデータの有り様が定まっていないという問題が潜んでいる可能性が大いにあります。そしてこの種の潜在的な問題は、ボディーブローのようにバグの温床になっていきます。
この問題を解決する1つの手段として、ある機能への特に入力の前処理段階でデータを正規化(Normalize)するアプローチが一般的です。Symfonyアプリケーションにおいて、機能の一部として正規化の役割を担う次の5つのコンポーネントについて紹介します。
- Form
- JMSSerializer
- Doctrine
- Config
- OptionsResolver
全体図としては以下のようになります。
それぞれの使われ方を簡単に説明します。
Form
アプリケーションのEntityに対して、Webフォームとの入出力の間に位置するのがFormです。名前から「Webフォームとして出力するタグを定義する」というイメージを持ってしまいがちです。しかし、それはどちらかというとオマケと捉えておき、Entityの構造とWebフォームとして入出力する情報の構造とのマッピングや変換を記述していると見た方が素直に扱えます。
Formの中で、データの正規化機能として重要なのは、DataTransformerやフォームのイベントです。また、フォームのデータ変換機能をまとめて処理するDataMapperを独立して作ることもできます(※1)。以下は参考となるドキュメントのリンクです。
- How to Use Data Transformers
- How to Dynamically Modify Forms Using Form Events
- Value Objects in Symfony Forms
※1 フォームのDataMapperの機能は別の記事で解説予定です。
なお、次に紹介するJMSSerializerを使ってJSONのリクエスト/レスポンスを扱っている場合でも、受け取ったデータの基本的な正規化をFormで行うようにしておくと役割がスッキリします。
JMSSerializer
アプリケーションのEntityに対して、JSONのリクエスト/レスポンスとの入出力の間に位置するのがJMSSerializerです。Entityクラスのプロパティやメソッドにアノテーションを記述するだけで、JSON上のプロパティ名などにマッピングできます(@SerializedName)。@TypeでJSON向けのデータ型を指定したり、@VirtualPropertyでEntity側には存在しないプロパティをJSON向けに作るといった機能もあります。これらの機能を使って簡易なデータ正規化は可能ですが、Symfonyで普通に行うEntityクラスに記述したバリデーションの実行なども含めて、通常はFormも合わせて使います。
Doctrine
アプリケーションのEntityに対して、RDBとの入出力の間に位置するのがDoctrine ORMです。これは普通ですね。
Symfony/Doctrineでちょっと複雑なValueObjectを扱う例のような変換をDoctrineのレイヤーに仕込むことで、アプリケーションのEntityをより表現豊かにすることなどもできます。
Config
YAMLやXML形式の設定ファイルを読み込んで扱う場合に、SymfonyではバンドルのExtensionという機能のレイヤで処理することになります。読み込んだYAMLやXMLはPHPコード内では連想配列になっており、これを正規化するのがConfigです。
多くの場合、Extensionの段階で何らかのサービス定義に作用させる等を行うため、ここで読み込んで正規化した連想配列をアプリケーションコード側で直接利用することはありません。しかし、YAMLで編集する際の構造と、プログラム内で扱う構造の違いを吸収し、かつ、イレギュラーな設定を弾いたり正規化したりする場所があることは有用です。
Config の利用シーンは次に紹介する OptionsResolver と似ていますが、Config はツリー全体の定義が可能で、変形操作を定義できるなど高機能です。
OptionsResolver
他の機能と少しレイヤが異なりますが、連想配列の正規化というミクロな目的に絞れば、SymfonyのOptionsResolverが有用です。もしも自分のPHPプロジェクトで、コンポーネント間や複数のメソッドに渡って連想配列でデータを受け渡しており、各所に連想配列内のキーのチェックが散らばっているようであれば、どこか1箇所でOptionsResolverを通してデータを正規化する処理を入れるとスッキリします。使い方は簡単で、以下のようにして必ず定義されているキーや、値が必須のキー、キーごとのデフォルト値などを記述しておきます。最後に $options = $resolver->resolve($options);
のようにして連想配列データを OptionsResolver に通します。
$resolver = new OptionsResolver();
$resolver->setRequired(array('host', 'username', 'password'));
$resolver->setDefaults(array(
'host' => 'smtp.example.org',
'username' => 'user',
'password' => 'pa$$word',
'port' => 25,
));
$options = $resolver->resolve($options);
resolve()
メソッドの実行時点で形式のチェックが行われ、エラーがある場合は例外(UndefinedOptionsException など)がスローされます。
TIPS 階層を持った連想配列に対して構造を定義する場合は、階層ごとに OptionsResolver の定義インスタンスを作り、値を取り出しながら適用していきます。
おわりに
Symfonyのいくつかの機能を、正規化という役割に着目して紹介しました。アプリケーションの各所で受け渡されるデータを、適切な場所で正規化することで、処理するメソッド内では、考慮しなければならない範囲(スコープ)を狭めることができます。同時に、データの有り様を事前に明らかにする活動にも焦点を当てることができます。
まだまだ型の強制力が弱いPHPでは、このような道具が役立つ場面が多くあるかと思います。ご参考になれば幸いです。