web-dev-qa-db-ja.com

MVCでは、モデルは検証を処理する必要がありますか?

MVCパターンを使用するために開発したWebアプリケーションを再構築しようとしていますが、検証をモデルで処理する必要があるかどうかわかりません。たとえば、次のようなモデルの1つを設定しています。

class AM_Products extends AM_Object 
{
    public function save( $new_data = array() ) 
    {
        // Save code
    }
}

最初の質問:では、保存メソッドが$ new_dataの検証関数を呼び出す必要があるのか​​、またはデータが既に検証済みであると想定するのかと思いますか?

また、検証を提供する場合、データ型を定義するモデルコードの一部は次のようになると思います。

class AM_Products extends AM_Object
{
    protected function init() // Called by __construct in AM_Object
    {
        // This would match up to the database column `age`
        register_property( 'age', 'Age', array( 'type' => 'int', 'min' => 10, 'max' => 30 ) ); 
    }
}

2番目の質問: AM_Objectのすべての子クラスは、その特定のオブジェクトのデータベース内の各列に対してregister_propertyを実行します。これがそれを行う良い方法であるかどうかはわかりません。

番目の質問:検証をモデルで処理する必要がある場合、エラーメッセージまたはエラーコードを返し、ビューでコードを使用して適切なメッセージを表示する必要がありますか?

28

最初の回答:モデルの重要な役割は整合性を維持することです。ただし、ユーザー入力の処理はコントローラーの責任です。

つまり、コントローラーはユーザーデータ(ほとんどの場合は文字列のみ)を意味のあるものに変換する必要があります。これには解析が必要です(たとえば、10進演算子が異なるなどの場合、ロケールなどに依存する場合があります)。
そのため、「データは適切に形成されていますか?」のような実際の検証は、コントローラーによって実行される必要があります。ただし、「データに意味はあるのか」などの検証モデル内で実行する必要があります。

これを例で明確にするには:
アプリケーションで、日付(たとえば、期限の問題)を使用して、いくつかのエンティティを追加できると仮定します。日付が単なるUnixタイムスタンプとして表されるAPIがあるかもしれませんが、HTMLページから来るときは、MM/DD/YYYYの形式の異なる値のセットまたは文字列になります。モデルにこの情報は必要ありません。各コントローラが個別に日付を計算するようにしてください。ただし、日付がモデルに渡されると、モデルは整合性を維持する必要があります。たとえば、過去の日付、または休日/日曜日の日付を許可しないことは理にかなっています。

コントローラーには入力(処理)ルールが含まれています。モデルにビジネスルールが含まれています。何が起こっても、常にビジネスルールを適用する必要があります。コントローラーにビジネスルールがあるとすると、別のコントローラーを作成する場合は、それらを複製する必要があります。

2番目の回答:このアプローチは理にかなっていますが、メソッドをより強力にすることができます。最後のパラメータが配列ではなく、次のように定義されるIContstraintのインスタンスである必要があります。

_interface IConstraint {
     function test($value);//returns bool
}
_

そして、数値については、

_class NumConstraint {
    var $grain;
    var $min;
    var $max;
    function __construct($grain = 1, $min = NULL, $max = NULL) {
         if ($min === NULL) $min = INT_MIN;
         if ($max === NULL) $max = INT_MAX;
         $this->min = $min;
         $this->max = $max;
         $this->grain = $grain;
    }
    function test($value) {
         return ($value % $this->grain == 0 && $value >= $min && $value <= $max);
    }
}
_

また、_'Age'_が何を表すのか、正直に言うとわかりません。実際のプロパティ名ですか?デフォルトで規約があると仮定すると、パラメーターは関数の最後に移動して、オプションにすることができます。設定されていない場合、デフォルトでDB列名の to_camel_case になります。

したがって、呼び出し例は次のようになります。

_register_property('age', new NumConstraint(1, 10, 30));
_

インターフェイスを使用することのポイントは、必要に応じて制約を追加していくことができ、必要に応じて複雑にできることです。文字列が正規表現に一致するようにします。日付が7日以上先であること。等々。

3番目の回答:すべてのモデルエンティティにはResult checkValue(string property, mixed value)のようなメソッドが必要です。コントローラーは、設定データの前にを呼び出す必要があります。 Resultには、チェックが失敗したかどうかに関するすべての情報が含まれている必要があります。失敗した場合は、理由を示して、コントローラーがそれらをビューに反映できるようにします。
誤った値がモデルに渡された場合、モデルは単に例外を発生させることによって応答する必要があります。

31
back2dos

「back2dos」に完全に同意しません。モデルに送信される前にコントローラーが入力データの検証に使用できる、別のフォーム/検証レイヤーを常に使用することをお勧めします。

理論的な観点から見ると、モデルの検証は信頼できるデータ(内部システム状態)で動作し、理想的にはいつでも再現可能である必要がありますが、入力の検証は、信頼できないソースからのデータに対して1回明示的に動作します(ユースケースとユーザー特権によって異なります)。

この分離により、依存性注入を通じて疎結合できる再利用可能なモデル、コントローラー、フォームを構築することが可能になります。入力検証をホワイトリスト検証(「既知の良いものを受け入れる」)として、モデル検証をブラックリスト検証(「既知の悪いものを拒否」)と考えてください。ホワイトリストの検証はより安全ですが、ブラックリストの検証は、モデルレイヤーが非常に特定のユースケースに過度に制約されるのを防ぎます。

無効なモデルデータは常に例外をスローするはずです(そうしないと、アプリケーションは間違いに気づかずに実行を継続できます)一方で、外部ソースからの無効な入力値は予期せぬものではなく、かなり一般的です(間違いを犯さないユーザーがいない限り)。

参照: https://lastzero.net/2015/11/why-im-using-a-separate-layer-for-input-data-validation/

7
lastzero

はい、モデルは検証を実行する必要があります。 UIも入力を検証する必要があります。

有効な値と状態を決定するのは明らかにモデルの責任です。時々そのようなルールは頻繁に変更されます。その場合、メタデータからモデルにフィードしたり、モデルを装飾したりします。

3
Falcon

すばらしい質問です。

World Wide Web開発の観点から、次のことも尋ねられたらどうでしょうか。

「ユーザーインターフェースから不正なユーザー入力がコントローラーに提供された場合、コントローラーは一種の循環ループでビューを更新し、コマンドと入力データを処理前に正確にする必要があります?方法?ビューはどのように通常の状態で更新されますか?ビューはモデルに密結合されていますか?ユーザー入力検証モデルのコアビジネスロジックですか、それともそれが予備的であり、コントローラーの内部で発生する必要があります(ユーザー入力データがリクエストの一部であるため)?

(実際には、適切な入力が取得されるまで、モデルのインスタンス化を1遅延できますか?

モデルは、モデルのインスタンス化の前に(そしてモデルが取得される前に必ず発生する)基本的な HTTPリクエスト入力検証によって妨げられない、純粋で純粋な環境を(可能な限り)管理する必要がある入力データ)。状態データ(永続的またはその他)とAPI関係の管理はモデルの世界なので、基本的な HTTPリクエスト入力検証をコントローラーで発生させます。

まとめ。

1)他に進む前にコントローラーとメソッドが存在している必要があるため、(URLから解析された)ルートを検証します。これは、真のコントローラーに到達する前に、フロントコントローラーレルム(ルータークラス)で必ず発生します。ああ。 :-)

2)モデルには、HTTPリクエスト、データベース、ファイル、API、そしてネットワークなど、入力データの多くのソースが含まれる場合があります。すべての入力検証をモデルに配置する場合は、 HTTP要求入力検証プログラムのビジネス要件の一部を検討します。ケースは閉じられました。

3)それでも、 HTTPリクエスト入力が適切でない場合、多数のオブジェクトをインスタンス化する費用を経験することは近視眼です。モデルとそのすべての複雑さをインスタンス化する前に検証することで、** HTTPリクエスト入力**(リクエストに含まれる)が適切かどうかを確認できます(そうすれば、APIとDBの入出力のバリデーターがさらに増える可能性があります)データ)。

以下をテストします。

a)HTTPリクエストメソッド(GET、POST、PUT、PATCH、DELETE ...)

b)最小限のHTMLコントロール(十分ですか?)。

c)HTMLコントロールの最大数(多すぎませんか?)。

d)正しいHTMLコントロール(正しいコントロールがありますか?)。

e)入力エンコーディング(通常、エンコーディングはUTF-8ですか?).

f)最大入力サイズ(入力のいずれかが極端に範囲外ですか?).

文字列とファイルを取得する可能性があるため、モデルがインスタンス化されるのを待つと、リクエストがサーバーにヒットするため、非常にコストがかかる可能性があります。

ここで説明したことは、コントローラを介して受信されるリクエストのintentにヒットします。 intentの検証を省略すると、アプリケーションの脆弱性が高まります。 インテントは、良い(基本的なルールでプレイする)または悪い(基本的なルールの外に出る)のみにすることができます。

インテント HTTPリクエストの場合は、オールオアナッシングの命題です。すべてが合格、またはリクエストが無効です。モデルに何も送信する必要はありません。

この基本レベルのHTTPリクエストインテントには、通常のユーザー入力エラーと検証に関連するなしがあります。私のアプリケーションでは、HTTPリクエストが上記の5つの方法で有効である必要があります。 多層防御の言い方をすると、anyの5つが失敗した場合、サーバー側でユーザー入力の検証を行うことはできません。

はい、これは、ファイル入力であっても、フロントエンドが検証し、受け入れられる最大ファイルサイズをユーザーに知らせるためのフロントエンドの試行に準拠する必要があることを意味します。 HTMLのみ? JavaScriptなし?大丈夫ですが、大きすぎるファイルをアップロードした場合の結果をユーザーに通知する必要があります(主に、ファイルがすべてのフォームデータを失い、システムから追い出されます)。

4)これは、 HTTPリクエスト入力データがアプリケーションのビジネスロジックの一部ではないことを意味しますか?いいえ、それはコンピューターが有限のデバイスであり、リソースを賢く使用する必要があることを意味します。悪意のある活動を遅らせるのではなく、遅らせるのは理にかなっています。後で停止するのを待つために、コンピューティングリソースにさらに多くの料金を支払います。

5) HTTPリクエスト入力が悪い場合、リクエスト全体が悪いです。それが私の見方です。適切なHTTPリクエスト入力の定義は、モデルのビジネス要件から導き出されますが、リソース境界のいくつかのポイントが必要です。不正なリクエストを強制終了して「ああ、気にしないでください。不正なリクエスト。

ユーザーが妥当な入力ミスをしただけではなく、HTTPリクエストが範囲外であるため悪意があると宣言し、すぐに停止する必要があると判断された。

6)したがって、私のお金では、HTTPリクエスト(メソッド、URL /ルート、およびデータ)はすべて良好であるか、それ以外の場合は何も続行できません。堅牢なモデルには既に関係する検証タスクがありますが、優れたリソースシェパードは、「私の方法、または高い方法。正しい方向に進んでください。または、まったく来ない」と言っています。

しかし、それはあなたのプログラムです。 「それを行うには複数の方法があります。」一部の方法は、他の方法よりも時間と費用がかかります。後で(モデル内で)HTTP要求データを検証すると、アプリケーションの存続期間にわたって(特に、スケールアップまたはスケールアウトの場合)より多くのコストがかかるはずです。

バリデーターがモジュール式の場合、コントローラーでの基本的な* HTTPリクエスト入力**の検証は問題になりません。ストラテジ化されたValidatorクラスを使用するだけです。バリデータは、専用のバリデータ(電子メール、電話、フォームトークン、キャプチャなど)で構成されることもあります。

これは完全に間違っていると考える人もいますが、Gang of FourがDesign Patterns:Elements of Re-usable Object-Oriented Softwareを書いたとき、HTTPはまだ始まったばかりでした。

================================================== ========================

これは、通常のユーザー入力検証(HTTPリクエストが有効であると見なされた後)に関係するため、ユーザーが考えなければならないことを混乱させたときにビューを更新しています。この種のユーザー入力の検証は、モデルで行う必要があります。

フロントエンドでのJavaScriptの保証はありません。つまり、アプリケーションのUIのエラーステータスによる非同期更新を保証する方法はありません。真のプログレッシブ拡張は、同期のユースケースもカバーします。

同期ユースケースの説明は、すべてのUIトリック(コントロールの表示/非表示、コントロールの無効化/有効化)の状態を追跡する手間と時間を費やすことを望まないため、ますます失われている技術です。 、エラー表示、エラーメッセージ)(通常は配列の状態を追跡することにより)。

Update :図では、ViewModelを参照する必要があると言います。いいえ。疎結合を維持するには、ViewからModelにデータを渡す必要があります。 enter image description here

2