AMatch-PHPで入力パラメーターを確認する

仲間! この記事は、高高高負荷システム向けではありません。 提示されたソリューションの速度は、最も単純なチェックよりも明らかに遅いです。 数千または非常に深い構造では、提案されたアプローチは推奨されません。 このトピックでは、高速コードではなく高速コーディングが優先されます。



長くない



長い紹介なしでそれをやってみましょうが、それでも背景があります。 かつて、Webサービスの別の非常に重要なコンポーネントを作成する一環として、多くの非常に異なる入力パラメーター(この場合は$ _REQUESTを使用)をチェックする必要がありました。 コンポーネントは非常に複雑で、内部および外部のロジックがすべての参加者の間で毎日のループを引き起こし、書き、書き直し、のこぎりで挽き回した「選択された」プログラマーを吹き飛ばす必要がありました。 配列を含む数十の異なる変数がシステムのフロントエンドログインに陥ると、プログラマーはクロスタスク(ロジックの変更)を行い、互いに干渉します。コードは非常に急速に成長し、ifチェーンの数は複数のページを占有し始めます。 そのようなコードに戻ることは、脆弱な魂にとってますます異質です。 論理の各変更は、覚えて、理解し、許す必要のある同じテストの変更につながるため、テストはこれ以上役に立ちません。 そして、入力ストリーム全体を何らかの方法でチェックして、常にどこでも同じ形式でエラーに関するフィードバックを受け取る便利な方法を作成するという疑問が生じました。 ここでの重点は開発者にとっての利便性にありましたが、将来はそれを持っていることを厳しくお願いします。



ポイントに行きましょう



完全な例はgithubにあります。



特定のコントローラーがあり、まだ完全に消滅するまで、リファッキングと再考の段階をまだ通過していないとします。 まず第一に、彼はフロントエンド($ params = $ _REQUEST;)から来たすべてのものを取ります。 実験の汚さのために、事前フィルタリングがないことを想像してみましょう-送信するほど幸せになります。 たとえば、$ params配列の内容は次のようになります。



$params = array( 'doc_id' => 133, 'subject_id' => '64', 'parent_id' => 32, 'title' => 'New document', 'data' => array( 'flag' => 'experiment', 'from_topic' => false, ), );
      
      







ここでタスクを設定します。一部のキーが必ず存在すること、一部が厳密に入力されていること、何かが間隔(ゼロより大きいなど)に該当することを確認する必要があります。 さらに、配列に指定されたキー以外のものが含まれることは不可能です。 それまでの間、私たちは啓発を探しています-十分なデバッグ情報を取得できたらうれしいです。 最初に、例外がスローされる条件チェーンの形でこれを想像してみましょう。



 //   if (!isset($params['doc_id'])) { throw new Exception('Expected document id'); } if (!isset($params['subject_id'])) { throw new Exception('Expected subject id'); } if ($params['doc_id'] <= 0) { throw new Exception('Incorrect document id'); } if ($params['subject_id'] == 0) { //   throw new Exception('Incorrect document id'); } if (isset($params['parent_id']) && $params['parent_id'] <= 0) { //   —   throw new Exception('Incorrect parent id'); } if (isset($params['data']) && (!is_array($params['data']) || empty($params['data']))) { throw new Exception('Incorrect document data'); } //...if //...if-else //...if-or-if-else-if //...the brain is burning
      
      







それはおなじみですか? そして、非常に多く、一日に何度も。 ビジネスプロセスの「if」条件ごとに、さまざまな境界値を使用して単体テストを作成する必要があります。 このコードだけに必要なテストの行数を想像してください。 明日は? -「それを切り取り、ここで一度に3つのドキュメントが必要なので、受け入れることができ、parent_idは配列になります。」 そして、各状況が別々に書かれている雑多なエラーをどうするか? コード形式のエラーを必要とする新しいファッショナブルな支払いシステムと統合する方法は? このようなビネグレットのマッピングを記述すること自体がポポールです。



最後に彼はAMatchを示しています



さて、デザートはAMatchを使用して多くの要因について同じ配列をチェックしています



 require_once('class.AMatch.php'); $match = AMatch::runMatch($params) ->doc_id(0, '<') //    ->subject_id(0, '!=') //    ->subject_id('', '!float') //  float ->author_name(AMatch::OPTIONAL, 'string') //    ->author_name('Guest') //   ->parent_id(AMatch::OPTIONAL, 'int') //   int ->parent_id(0, '<') //    ->parent_id(array(32, 33), 'in_left_array') //       ->data('', 'array') //  ->data('', '!empty') //   ->data('old_property', '!key_exists') //     ->data('experiment', 'in_array') //     'experiment' ->title() //     ; $result = $match->stopMatch(); if (!$result) { die(var_export($match->matchComments(), true)); //    } echo 'Victory!';
      
      







与えられたコードを分析しましょう。 検証の開始(条件の一致)は、チェック対象の配列をAMatch :: runMatch()メソッドに転送することから始まります。 次に、逆参照により、スキームに従ってチェックが呼び出されます。

 ->_([___], [])->…->stopMatch()
      
      





stopMatch()メソッドは、一般的な結果-trueまたはfalseを返します。 もちろん、「機能しなかった」という事実だけでは必ずしも十分ではありません。 したがって、stopMatch()を$ match変数に呼び出す前にオブジェクトへのリンクを保存すると、stopMatch()の後に結果に関する詳細なコメントを取得できます。 stopMatch()にもチェックが含まれ、エラーが生成されるため、このメソッドの前にコメントを呼び出すと、情報が不完全になる可能性があることに注意してください。

最初の起動では「勝利!」



シェフ、それはすべてなくなった



入ってくる配列を台無しにして結果を見てみましょう:



 $params_bad = array( 'doc_id' => -4, 'subject_id' => null, 'parent_id' => 30, 'data' => array( 'flag' => 'booom', 'from_topic' => array(), 'old_property' => true, ), 'wtf_param' => 'exploit', ); $params = $params_bad;
      
      







出力は次のとおりです。



 array ( 'doc_id' => 'Condition is not valid', )
      
      







なぜ1つだけのアイテムですか? 彼はチェーンの最初だったからです。 いつもとは異なり、パラメーターのセット全体をソートする必要があります。 デフォルトでは、AMatchは、満たされない最初の条件が受信されるとすぐにチェックを中断します。 完全スキャンを有効にする前に、どの条件が失敗したかを見てみましょう。 これを行うには、エラー出力を次のように置き換えます。



 die( var_export($match->matchComments(), true) . PHP_EOL . var_export($match->matchCommentsConditions(), true) );
      
      







結果は、偽を生成した比較演算を示しています。



 array ( 'doc_id' => 'Condition is not valid', ) array ( 'doc_id' => // ,     array ( 0 => 0, //     1 => '<', //   «  » ), )
      
      







本質的に、これは元のエントリの繰り返しです。



 ->doc_id(0, '<')
      
      







すべてのコメントは定数に保存されます(AMatch :: KEY_CONDITION_NOT_VALID == 'Condition is not valid')。 明らかに、エラー定数とその発生条件に基づいて、ユーザーにエラーを発行する標準マッピングを構成することが可能になりました。



もっと木材が必要です!



他の条件に戻りましょう。 条件が終了するまでチェックおよびチェックするようにAMatchに指示するには、フラグ(ビットマスク)を使用する必要があります。 現在、3つのフラグがあります。





これらのフラグは、アプリケーションデバッグモードで特に役立ちます。 フラグ「FLAG_DONT_STOP_MATCHING」は、たとえば、フォームデータを確認し、すべての問題をヒープで返す必要がある場合に特に役立ちます。 コードを補足します。

 $flags = AMatch::FLAG_DONT_STOP_MATCHING; $match = AMatch::runMatch($params, $flags) …
      
      







結果はすべての問題を明らかにします:



 array ( 'doc_id' => 'Condition is not valid', 'subject_id' => 'Condition is not valid', 'parent_id' => 'Condition is not valid', 'data' => array ( 'flag' => 'Condition is not valid', ), 'title' => 'Expected parameter does not exist in the array of parameters', ) … (  )
      
      







タフなのが大好き!



特別なフラグFLAG_STRICT_STRUCTUREに注意してください。 たとえば、APIを作成してそのバージョンをサポートしている場合に特に役立ちます。 クライアントはAPIに接続し、さまざまなリクエストを送信します。 送信されたリクエストの形式が古く、無効な値が送信されるだけでなく、一般に余分なキーが送信されることに注意してください。



 $flags = AMatch::FLAG_DONT_STOP_MATCHING | AMatch::FLAG_STRICT_STRUCTURE;
      
      







このフラグを使用してコードを実行しましょう。 コメントに2つの新しい行が追加されます。



'stopMatch' => '入力データの不明なパラメーター'、

'不明なパラメーター:' => 'wtf_param'、

//マッピングには、エラー値AMatch :: UNKNOWN_PARAMETERS_LISTとキー名AMatch :: _ UNKNOWN_PARAMETERS_LISTを使用します



一般に、このことから、検証条件がないパラメーターが見つかっていることがわかります。



害虫駆除



最後のフラグは、無効なキーがあることを知るだけでなく、有効なキーが正確に検証されたことを知る必要がある場合のデバッグモードに役立ちます。 デバッグでフラグを補完し、元の正しい配列を返します。



 define('DEBUG_MODE', true); if (DEBUG_MODE) { $flags |= AMatch::FLAG_SHOW_GOOD_COMMENTS; } …   Victory: if (DEBUG_MODE) { $comments = $match->matchComments(); $comments_explanation = $match->matchCommentsConditions(); echo PHP_EOL; var_export($comments); echo PHP_EOL; var_export($comments_explanation); }
      
      







これで、コメントには多くのbukafが含まれます(それらはすべてAMatch定数にあります)。



 Victory! array ( 'doc_id' => 'OK. Condition is valid', 'subject_id' => 'OK. Condition is valid', 'author_name' => 'Optional parameter, skipped bad condition result', 'parent_id' => 'OK. Expected parameter type is valid', 'data' => 'OK. Expected parameter type is valid', 'title' => 'OK. Expected parameter exist in the array of parameters', 'stopMatch' => 'The array does not contains unknown parameters', )
      
      







それとは別に、実行結果とコメントの書き換えは、失敗した場合にのみ実行されることに注意してください。 つまり、以前に条件に「成功」​​があった場合、誤った条件がキーによるコメントを上書きします。 以前に失敗があった場合、成功条件はコメントに影響しません。



私は満足していません!



キャンディーを追加します。 $ match-> stopMatch()の前に、ネストされた配列構造を確認します。



 function checkDocumentData($data) { $result = AMatch::runMatch($data) ->flag('experiment') //   ->from_topic(specialValidation(), true) //  ,       true ->from_topic(false) //  false ->link_id(AMatch::OPTIONAL, 'int') //   int ; return array($result->stopMatch(), $result->matchComments(), $result->matchCommentsConditions()); } function specialValidation() { return 1 < 2; //    ,   -  (,    ) } $match->data('checkDocumentData', 'callback'); //     
      
      





ここですべてが明確であることを願っています。



手紙後



Andrei Tereshchenko、Andrei Lugovoy、Pravo.ru開発チームへのアイデアをありがとう。

他の例(unittestsを参照)を見つけて、ソースをダウンロードできます: https : //github.com/KIVagant/AMatch

あなたのアイデアが役に立つコミットメントに変わったら、私はとても幸せです。 私は批判が好きではありませんが、まあ、コメントで燃えてください。 残念ながら、私は英語が苦手なので、グラマーナチウェルカムです。



UPD:

次の記事: 新しいコールバックとエラー処理



UPD:

Composerに変換されます。



All Articles