がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

validationについて考えてみる

「について考えてみる」三部作(って今決めたw)、最後の項目。
内容は「validation、をどこに実装するか?」てなお話です。

一旦、Webアプリケーションのお話で展開します。。
前提として、Modelのお話が出てくるんで https://gallu.hatenadiary.jp/entry/2019/05/13/213612 のあたりを一読しておいていただければ幸い。
以下では「Model ≒ ORM」なModelを想定します。
違う場合は「データの塊を扱う箇所 ≒ Model」って読み替えていただければ*1

前提として。
「validationはやるよねぇ」ってのは、一端そこは合意前提で。
いやまぁ「どれくらいがっつりやるのか」ってのはありますし、そもそもとして「valid(有効)ってなに?」ってのもありまして。
具体的には「日付のvalidation」は、その日付がグレゴリウス暦であれば比較的簡単かと思うのですが、それが「誕生日」だとすると「どこまでがvalidなんだろう?」というあたりでちょっとした議論くらいは巻き起こせる感じではあるのですが*2
とはいえまぁ「ある程度の落としどころ」を加減した上での「明らかにinvalidなデータくらいはブロックしたいよねぇ」くらいの感じ、を、一端想定していきたいと思います。

さて。
昔々であれば、こんな事を考える必要は(多分)無くて。
つまり

// 入力を受け取って
// validationして
// DBにinsertしたりupdateしたりして
// HTMLを出力する

的に、立て板に水の如く「書き流せば」よかったのですが。
昨今、これらを「1ファイルで一気に書く」事は比較的レアケースとなりつつあり。多くの場合は、いくつかのクラスを「有機的につないで」1つの機能にしているので。
そうすると「どこに書くの?」というのは、とても重要なんじゃないかなぁ、と、個人的には思うところでございます*3

いやまぁおいちゃんの中では(今のところ)大体決まっていて。
「データ保存の直前で、テーブル*4のレコード単位」って考えてます。
一般的なPHPフレームワークだと「Model」ですかねぇ。MagicWeaponだとdata_clump派生クラス。
理由は簡単で「validateは、一塊の"データ"*5に紐付く処理」だと考えているから。

なんだけど、そこで終わるようならわざわざこんな文章は書かない訳で、そのあたりから詳しく。

初手に気づいたのはLaravelが「Controllerに入ってくる前にvalidateを片付けておく( https://readouble.com/laravel/5.5/ja/validation.html )」って思想を見て*6……「あぁそういえばこーゆー思考、以前にも見たなぁ」と思いまして。
その後、CodeIgniter 3でもやはり、validatinoは「form validation( https://codeigniter.jp/user_guide/3/libraries/form_validation.html )」という名前で、controllerに紐付いていて。
ちぃと興味がわいたんで、あちこち、調べてみました。

CakePHP3。
https://book.cakephp.org/3.0/ja/core-libraries/validation.html
Validatorクラスが完全に独立していて、Controllerに書いてもModelに書いても「どちらでもどんぞ」的に見えます。

FuelPHP
http://fuelphp.jp/docs/1.8/classes/validation/validation.html
CakePHP3と同様「Validationが独立」しているので以下略

Symfony
https://symfony.com/doc/current/validation.html
どうも2種類あるっぽいですが、「The Basics of Validation」がEntity(Model)に書くような書き方であることから、「どちらかというとModelに書いて欲しい」雰囲気である事を想起させます。

Zend Framework
https://docs.zendframework.com/zend-validator/
今ひとつつかめないのですが、Validatorクラスが独立しているので以下略、と思われます。

Phalcon
https://phalcon-docs-ja.readthedocs.io/ja/stable/reference/validation.html
Validationクラスが独立しているので以下略。
………ところで、今、Phalconってどれくらい使われてるんですかねぇ?

Slim
http://www.slimframework.com/docs/
そもそも色々と「シンプル」なフレームワークなので、Validationも「自分で実装しましょう」(笑
なので、URIも「ドキュメントのTop」ですvalidateを書いてあるPageじゃないです(笑

さて。
大雑把にまとめますと
・Controller:2
・どちらかというとModel:1
・どっちでも:3
こんな感じですかねぇ。
いやまぁ別に「多数決でナニカを決めたい」わけではないのですが、とりあえず実態くらいは把握しておきましょう、と。

んで。
たまたま別件で知って、いやまぁ色々と思うところも多々あるフレームワークではあるのですが。
他言語になりますが、RoR(Ruby on Rails)において、いわゆるModelに近しい位置にいるActive Recordが https://railsguides.jp/active_record_validations.html で、こんな記述がありました。

データをデータベースに保存する前にバリデーションを実行する方法は、他にもデータベースネイティブの制約機能、クライアント側でのバリデーション、コントローラレベルのバリデーションなど、さまざまです。それぞれのメリットとデメリットは以下のとおりです。

・「データベース制約」や「ストアドプロシージャ」を使うと、バリデーションのメカニズムがデータベースに依存してしまい、テストや保守がその分面倒になります。ただし、データベースが (Rails以外の) 他のアプリケーションからも使われるのであれば、データベースレベルである程度のバリデーションを行なっておくのはよい方法です。また、データベースレベルのバリデーションの中には、使用頻度がきわめて高いテーブルの一意性バリデーションなど、他の方法では実装が困難なものもあります。
・「クライアント側でのバリデーション」は扱いやすく便利ですが、一般に単独では信頼性が不足します。JavaScriptを使ってバリデーションを実装する場合、ユーザーがJavaScriptをオフにしてしまえばバイパスされてしまいます。ただし、他の方法と併用するのであれば、クライアント側でのバリデーションはユーザーに即座にフィードバックを返すための便利な方法となるでしょう。
・「コントローラレベルのバリデーション」は一度はやってみたくなるものですが、たいてい手に負えなくなり、テストも保守も困難になりがちです。アプリケーションの寿命を永らえ、保守作業を苦痛なものにしないようにするためには、コントローラのコード量は可能な限り減らすべきです。

上で紹介したその他のバリデーションについては、特定の状況に応じて適宜追加してください。Railsチームは、ほとんどの場合モデルレベルのバリデーションが最も適切であると考えています。

このように書かれているのが、大変に興味深いかなぁ、と。
端的には「非常に近い見解を持っている」あたりで、興味がわきました。

さて。
ちぃと切り口を変えてみますと。
概ね「Controllerにvalidationを置きたい」というお話は「validationをformに紐付けたい」と考えているように見受けられ、同様に「Modelにvalidationを置きたい」というお話は「validationをテーブル(レコード)に紐付けたい」と考えているように見受けられます。

んで。
form側のお話がわからんでもなくて、つまり
・同じテーブルでも、formの入り口によって項目が違ったりする(常に全項目とは限らない)
などの揺れがあるので「formに紐付けたい」んじゃなかろうかなぁ、と。
あとはまぁ「エラーメッセージが出しやすい」って発想はありそうに思われるんですがね。

ただ、一方で
・同じテーブル/カラムにいれる情報なのに、入る口によって「文字だったり数字だったり」とか「最大長が100だったり200だったり」って変わるんだろうか?
って思うんですよねぇ。
「変わる」のであれば、確かにそれは「formに紐付けたほうがよい」かもしれないと思うのですが、おいちゃんが今まで経験している限りで「それを必須要件として持っている」案件って、出会った事がないんですよ。
少なくとも「同じ項目」であれば、そこのvalidateは基本「変わらない」。
せいぜいが「ここの入り口からだとこのカラムは変更禁止」てな感じくらいなので、ただ「変更禁止」は、validateではなく、別の責務なんじゃないか、って思うんですよねぇ。

そうすると。
個人的には
・「データ」を管理しているところでvalidationする
のが、一番「素直」なんじゃないかなぁ、と思うわけです。
なんか、カラムの追加があっても変更があっても「対象になるModelクラスを修正」すればよいだけ、なので、DRYになるじゃないですか。

で、次点としてどうしても(フレームワークとかの都合で)Controllerでせざるを得ないのであれば、せめて「そこに渡すルールは、Model側で管理して一意に管理」にしてはどうかなぁ? と。これならまぁDRYになる……かもしれない。「カラムの追加」とかがあるとDRYにはならなさそうな気もいっぱいしますが、まぁ「書き方次第」でもありましょうから、「ちゃんとDRYになるように書く」前提、って事で。

とはいえ一方で、寺子屋のメンバーから「Active Record(model)にあんまり責務が集中するのも如何なものだろう?」という議題提起をもらいまして、それはそれで興味深いところかなぁ、と。
おいちゃん個人としては「素直に考えた結果、どこかのクラス(の責務)が集中するのは、ある程度仕方がないんじゃないかなぁ」という雑な思想を持っているので(そのあたりは、 https://gallu.hatenadiary.jp/entry/2019/05/13/213612 でファットコントローラーへの言及で似たようなお話をしてます)、もうちょっと丁寧な議論を聞いてみたい気も満々でございます。

この辺は、なかなかに興味深いところかなぁ、と思われるのですが。
どこかで「アンサーブログ」とか、出てこないですかねぇ?w

*1:なので、万が一MagicWeaponを想定する場合、以下のModelはdata_clumpに相当します

*2:この辺、面白いっちゃぁ面白い所なので、要望があったら別途

*3:もちろん「ちゃんと動けばどこに書いたっていいじゃない」という主張がある事も存じ上げてはおりますが、個人的にはそういった御仁とは「適切な距離をおいて、穏便に恙なく平和裏に節度をもって形式上のお付き合いをしていきたいものだなぁ」と感じたりする所でございます

*4:ファイルだったりドキュメントだったりしてもよいんだけど

*5:もうちょっと端的には「1テーブル の 1レコード」

*6:5.5なのは「最近、お仕事で使ってたバージョン」だから。LTSだからってのもあります