複数除外または興味深い建築トリックを共有したい

PHPフレームワークでのエラー処理が好きではありませんでした。 そして、言葉の使用さえ好きではなかった。 すぐに明確にするために-私はerror_reportingではなく、致命的なエラーについて話しているのではなく、検証エラーと呼ばれるものについて話しているのです。 モデルまたはフォームのいずれか-フレームワークに依存します。



ご覧ください。 モデル検証エラーを取得するYiiおよびYii2の例を次に示します。

$errors = $model->getErrors();
      
      





symfonyフォームエラー:

 $errors = $form->getErrors();
      
      





ピクシーを積極的に宣伝しました(昔、彼については何もありませんでした):

 $result = $validator->validate($data); $errors = $result->errors();
      
      







ここで何が間違っていますか?

はい、それだけです。 そうではありません。 このコードはすべて非常に悪臭がします。PHP4、スパゲッティアーキテクチャ、およびさまざまな概念が混ざっています。



どうする?





理解を開始します。 最初から。



重要な概念を定義します。





1. 有効性とは、「このコンテキストで有効な値、つまり有効な値」という質問に対する答えです 。 コンテキストは異なる場合があります。これはフォーム内のフィールドであり、オブジェクトのプロパティです。 興味深いことに、有効性の質問に対する「はい」という答えは追加情報を意味するものではありませんが、「いいえ」という答えには明確化が必要です。 例:長さが6文字未満であるため、パスワードは無効です。



2. 検証検証プロセスです。 私たちにはあなたとある意味があり、文脈があります。 バリデーター (検証を実行するプロセス)は、このコンテキストで値が有効かどうか、そしてそうでない場合はその理由を明確に答える必要があります。



3.前の段落の「理由」は、正確に「 検証エラー 」と呼ばれます検証エラー -データの有効性に関する質問に対する偽の回答の具体的な原因、つまり検証の失敗の理由に関する詳細情報。 実際、これらは「シェフ、すべてがなくなった!」という意味では間違いではありませんが、検証者の特定のレポートだけで、「間違い」という言葉はすでに開発者の間で定着しています。



4. 検証ルール -コンテキストと値を入力として受け取り、有効性応答を返す関数。 答えには、true / falseと検証レポート(エラーがある場合)の両方を含める必要があります。



サニタイズ(またはロシア語で「クリア」)値を混乱させるために、検証は非常に頻繁に行われます(特にPHP 5.2をまだサポートしている一部のフレームワークでは、それらを指さしません)。 「検証」と「クリーニング」(または正規化)の概念を混同しないでください。これらは2つのまったく異なるプロセスです。

私が好む良い例:ロシアの電話番号を入力します。 検証のためには、入力された行に11桁が含まれていれば十分です(最初の桁は7で、他の文字の任意の数と位置があります)。 そうでない場合、検証は失敗します。 サニタイザーのタスクは、この値から数値以外のすべてを削除することです。これにより、標準化されたmsisdnをデータベースに保存できます。

最後に読んで違いを理解してください: php.net/manual/ru/filter.filters.php



結局のところ、何が問題なのでしょうか?



検証エラーのコレクションは例外ではありません。



これらすべてが素晴らしい
 ->getErrors()
      
      



例外ではありません。 したがって、次のような多くの利点が奪われています。



  1. 例外が入力されます。 上記のようなフレームワークでは、階層FormException-> FormFieldException-> FormPasswordFieldException-> FormPasswordFieldNotSameExceptionを作成できません。 これは非常に重要です。特にPHP 7のリリースでは、パイプヒントが最終的に標準および標準になります。
  2. 例外はそれ自体で多くをカプセル化します。 これはOOPです! 例:どのページ(URL)で検証エラーが発生しましたか? ユーザーは誰ですか? 特定のフォームフィールドとは何ですか? どの検証ルールが機能しましたか? 最後に、「このメッセージをエストニア語に翻訳してください。」 これはすべて、エラーメッセージの単純な配列によって実行できますか? もちろん違います。 (ところで、__toString()メソッドを実装するだけで、テンプレート内の例外は単純なエラーメッセージのように動作し続けます)
  3. 例外がフローを制御します。 私は彼を去ることができます。 ポップアップします。 私は彼を捕まえることができますが、私は彼を捕まえて投げることができます。 $ errors配列はコードフローを制御する権利を拒否されているため、非常に不便です。 コントローラーまたはアプリケーションコンポーネントなどに$エラーを使用して、上記のモデルから検証エラーの処理をエスカレーションするにはどうすればよいですか?




そして何をすべきか?



タスクを設定してみましょう。 コードに何を見たいですか? さて、このようなことを言ってみましょう:



アクティブなコードのどこかに:

 try { $user = new User; $user->fill($_POST); $user->save(); redirect('hello.php'); catch (ValidationErrors $e) { $this->view->assign('errors', $e); }
      
      







テンプレートのどこかに:

 <?php foreach ($errors as $error): ?> <div class="alert alert-danger"><?php echo $error->getMessage(); ?></div> <?php endforeach; ?>
      
      







提案された建築テンプレートの本質は、非常に簡潔に表現できます: 多重排除。 他の例外のコレクションである例外。



これを達成する方法は? 幸いなことに、最新のPHPではこのようなトリックを実行できません。



例外をコレクションに変える





最も興味深いのはここです!
オブジェクトを配列に変換するのに役立つすべてのインターフェイスを継承するインターフェイス:

 interface IArrayAccess extends \ArrayAccess, \Countable, \IteratorAggregate, \Serializable { }
      
      







このインターフェイスを実装する特性は次のとおりです。

 trait TArrayAccess { protected $storage = []; protected function innerIsset($offset) { return array_key_exists($offset, $this->storage); } protected function innerGet($offset) { return isset($this->storage[$offset]) ? $this->storage[$offset] : null; } protected function innerSet($offset, $value) { if ('' == $offset) { if (empty($this->storage)) { $offset = 0; } else { $offset = max(array_keys($this->storage))+1; } } $this->storage[$offset] = $value; } protected function innerUnset($offset) { unset($this->storage[$offset]); } public function offsetExists($offset) { return $this->innerIsset($offset); } public function offsetGet($offset) { return $this->innerGet($offset); } public function offsetSet($offset, $value) { $this->innerSet($offset, $value); } public function offsetUnset($offset) { $this->innerUnset($offset); } public function count() { return count($this->storage); } public function isEmpty() { return empty($this->storage); } } //   .       IArrayAccess //             -     innerIsset(),   true,    ,   null. ,    .
      
      







私は個人的に別の便利なインターフェースとその実装を特性とともに追加しますが、もちろん完全にオプションです:

 interface ICollection { public function add($value); public function prepend($value); public function append($value); public function slice($offset, $length=null); public function existsElement(array $attributes); public function findAllByAttributes(array $attributes); public function findByAttributes(array $attributes); public function asort(); public function ksort(); public function uasort(callable $callback); public function uksort(callable $callback); public function natsort(); public function natcasesort(); public function sort(callable $callback); public function map(callable $callback); public function filter(callable $callback); public function reduce($start, callable $callback); public function collect($what); public function group($by); public function __call($method, array $params = []); }
      
      







そして最後に、すべてをまとめます。



 class MultiException extends \Exception implements IArrayAccess { use TArrayAccess; }
      
      









簡単な応用例





モデルにデータを入力する方法。



検証ルールがモデルに作成されます。 モデルフィールドに割り当てられた値が検証に失敗するたびに例外をスローします。 例:

 protected function validatePassword($value) { if (strlen($value) < 3) { throw new Exception('  '); } ... return true; }
      
      







フィールドのバリデーターを自動的に呼び出すマジックセッターを作成します。 同時に、スローされた例外を、検証エラーメッセージだけでなくフィールド名も含む別のタイプに変換します。

 public function __set($key, $val) { $validator = 'validate' . ucfirst($key); if (method_exists($this, $validator)) { try { if ($this->$validator($value)) { parent::__set($key, $val); } } catch (Exception $e) { throw new ModelColumnException($key, $e->getMessage()); } } }
      
      







fill($ data)メソッドを作成します。このメソッドは、モデルにデータを入力し、すべての検証エラーを慎重に別のフィールドに収集しようとします。

 public function fill($data) { $errors = new Multiexception; foreach ($data as $key => $val) { try { $this->$key = $val; } catch (ModelColumnException $e) { $errors[] = $e; } } if (!$errors->isEmpty()) { throw $errors; } }
      
      







実際には、それだけです。 応募できます。 プラスの束:





結論の代わりに



それはすべて、少しニュアンスはありますが、私が長い間使用してきた戦闘コードであり、生徒にそれを使用することさえ教えています。 このようなシンプルなコンセプトをどこにも見たことがないことに驚きました。 突然私が最初の場合は、この記事で説明したアイデアとコードをパブリックドメインに渡します。 最初でない場合-著者のミスを許してください



コメントに基づくUPD

すべてのコメンテーターの貴重な考えや意見に感謝します。



記事の本質は検証されていません。 一般的に。 検証はハリウッドの失敗例に過ぎず、より良いものを思い付くことができませんでした。



ポイントは非常に簡単です。 PHPでは、オブジェクトが存在する可能性があります。これは、例外であると同時に他の例外のコレクションでもあります。 そして便利です。



All Articles