Première réponse: Un rôle clé du modèle est de maintenir l'intégrité. Cependant, le traitement des entrées utilisateur est la responsabilité d'un contrôleur.
Autrement dit, le contrôleur doit traduire les données utilisateur (qui la plupart du temps ne sont que des chaînes) en quelque chose de significatif. Cela nécessite une analyse (et peut dépendre de choses telles que les paramètres régionaux, étant donné que, par exemple, il existe différents opérateurs décimaux, etc.).
La validation proprement dite, comme dans «les données sont-elles bien formées?», Doit donc être effectuée par le contrôleur. Cependant la vérification, comme dans "les données ont-elles un sens?" doit être effectuée dans le modèle.
Pour clarifier cela avec un exemple:
Supposons que votre application vous permette d'ajouter des entités, avec une date (un problème avec un délai par exemple). Vous pouvez avoir une API, où les dates peuvent être représentées comme de simples horodatages Unix, tandis que lorsque vous venez d'une page HTML, ce sera un ensemble de valeurs différentes ou une chaîne au format MM / JJ / AAAA. Vous ne voulez pas ces informations dans le modèle. Vous voulez que chaque contrôleur essaie individuellement de déterminer la date. Cependant, lorsque la date est ensuite transmise au modèle, le modèle doit conserver son intégrité. Par exemple, il peut être judicieux de ne pas autoriser les dates du passé ou des dates qui sont des jours fériés / dimanches, etc.
Votre contrôleur contient des règles d'entrée (de traitement). Votre modèle contient des règles métier. Vous souhaitez que vos règles métier soient toujours appliquées, quoi qu'il arrive. En supposant que vous disposiez de règles métier dans le contrôleur, vous devrez les dupliquer si vous créez un autre contrôleur.
Deuxième réponse: L'approche a du sens, mais la méthode pourrait être rendue plus puissante. Au lieu que le dernier paramètre soit un tableau, il doit s'agir d'une instance IContstraint
définie comme suit:
interface IConstraint {
function test($value);//returns bool
}
Et pour les chiffres, vous pourriez avoir quelque chose comme
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);
}
}
Je ne vois pas non plus ce que l' 'Age'
on veut représenter, pour être honnête. S'agit-il du nom réel de la propriété? En supposant qu'il existe une convention par défaut, le paramètre pourrait simplement aller à la fin de la fonction et être facultatif. S'il n'est pas défini, il correspondrait par défaut à to_camel_case du nom de la colonne DB.
Ainsi, l'exemple d'appel ressemblerait à ceci:
register_property('age', new NumConstraint(1, 10, 30));
L'intérêt d'utiliser des interfaces est que vous pouvez ajouter de plus en plus de contraintes au fur et à mesure et qu'elles peuvent être aussi compliquées que vous le souhaitez. Pour qu'une chaîne corresponde à une expression régulière. Pour une date au moins 7 jours à l'avance. Etc.
Troisième réponse: chaque entité modèle doit avoir une méthode comme Result checkValue(string property, mixed value)
. Le contrôleur doit l'appeler avant de définir les données. Le Result
doit disposer de toutes les informations permettant de savoir si la vérification a échoué et, dans le cas contraire, donner des raisons, afin que le responsable du traitement puisse les propager à la vue en conséquence.
Si une mauvaise valeur est transmise au modèle, le modèle doit simplement répondre en levant une exception.
Je ne suis pas complètement d'accord avec "back2dos": Ma recommandation est de toujours utiliser une couche de formulaire / validation distincte, que le contrôleur peut utiliser pour valider les données d'entrée avant qu'elles ne soient envoyées au modèle.
D'un point de vue théorique, la validation du modèle fonctionne sur des données fiables (état du système interne) et devrait idéalement être répétable à tout moment, tandis que la validation d'entrée opère explicitement une fois sur des données provenant de sources non fiables (selon le cas d'utilisation et les privilèges de l'utilisateur).
Cette séparation permet de créer des modèles, des contrôleurs et des formulaires réutilisables qui peuvent être couplés de manière lâche via l'injection de dépendances. Considérez la validation des entrées comme une validation de liste blanche («accepter le bien connu») et la validation du modèle comme une validation de liste noire («rejeter le mauvais connu»). La validation de la liste blanche est plus sécurisée tandis que la validation de la liste noire empêche votre couche de modèle d'être trop limitée à des cas d'utilisation très spécifiques.
Les données de modèle non valides doivent toujours provoquer une exception (sinon l'application peut continuer à s'exécuter sans remarquer l'erreur) tandis que les valeurs d'entrée non valides provenant de sources externes ne sont pas inattendues, mais plutôt courantes (sauf si vous avez des utilisateurs qui ne font jamais d'erreurs).
Voir aussi: https://lastzero.net/2015/11/why-im-using-a-separate-layer-for-input-data-validation/
la source
Oui, le modèle doit effectuer une validation. L'interface utilisateur doit également valider l'entrée.
Il est clairement de la responsabilité du modèle de déterminer des valeurs et des états valides. Parfois, ces règles changent souvent. Dans ce cas, je nourrirais le modèle à partir des métadonnées et / ou le décorerais.
la source
Grande question!
En ce qui concerne le développement du World Wide Web, que se passe-t-il si vous posez également les questions suivantes.
"Si une mauvaise entrée utilisateur est fournie à un contrôleur à partir d'une interface utilisateur, le contrôleur doit-il mettre à jour la vue dans une sorte de boucle cyclique, forçant les commandes et les données d'entrée à être précises avant de les traiter ? Comment? Comment la vue est-elle mise à jour dans des conditions normales Est-ce qu'une vue est étroitement couplée à un modèle? La validation des entrées utilisateur est-elle la logique métier principale du modèle, ou est-elle préliminaire et devrait donc se produire à l'intérieur du contrôleur (car les données d'entrée utilisateur font partie de la demande)?
(En effet, peut-on et devrait-on retarder l'instanciation d'un modèle jusqu'à ce qu'une bonne entrée soit acquise?)
Mon avis est que les modèles doivent gérer une circonstance pure et vierge (autant que possible), sans être gênée par une validation d'entrée de requête HTTP de base qui devrait se produire avant l'instanciation du modèle (et certainement avant que le modèle n'obtienne des données d'entrée). Étant donné que la gestion des données d'état (persistantes ou non) et des relations API est le monde du modèle, laissez la validation d'entrée de requête HTTP de base se produire dans le contrôleur.
Résumant.
1) Validez votre itinéraire (analysé à partir de l'URL), car le contrôleur et la méthode doivent exister avant que quoi que ce soit d'autre puisse avancer. Cela devrait certainement se produire dans le domaine du contrôleur frontal (classe Router), avant d'arriver au vrai contrôleur. Duh. :-)
2) Un modèle peut avoir de nombreuses sources de données d'entrée: une requête HTTP, une base de données, un fichier, une API et, oui, un réseau. Si vous allez placer toute votre validation d'entrée dans le modèle, vous envisagez la validation d'entrée de demande HTTP partie des exigences commerciales du programme. Affaire classée.
3) Pourtant, il est myope de passer par le coût de l'instanciation de beaucoup d'objets si l' entrée de requête HTTP n'est pas bonne! Vous pouvez savoir si ** l'entrée de requête HTTP ** est bonne ( fournie avec la requête ) en la validant avant d'instancier le modèle et toutes ses complexités (oui, peut-être même plus de validateurs pour les données d'entrée / sortie API et DB).
Testez les éléments suivants:
a) La méthode de requête HTTP (GET, POST, PUT, PATCH, DELETE ...)
b) Contrôles HTML minimum (en avez-vous assez?).
c) Contrôles HTML maximum (en avez-vous trop?).
d) Corriger les contrôles HTML (avez-vous les bons?).
e) Encodage d'entrée (est-ce généralement l'encodage UTF-8?).
f) Taille d'entrée maximale (l'une des entrées est-elle largement hors limites?).
N'oubliez pas que vous pouvez obtenir des chaînes et des fichiers, donc attendre que le modèle soit instancié peut devenir très coûteux lorsque les demandes arrivent sur votre serveur.
Ce que j'ai décrit ici correspond à l' intention de la demande via le contrôleur. L'omission de la vérification d' intention rend votre application plus vulnérable. L'intention ne peut être que bonne (en respectant vos règles fondamentales) ou mauvaise (sortir de vos règles fondamentales).
L'intention d'une demande HTTP est une proposition tout ou rien. Tout passe ou la demande n'est pas valide . Pas besoin d'envoyer quoi que ce soit au modèle.
Ce niveau de base de l' intention de demande HTTP n'a rien à voir avec les erreurs de saisie utilisateur standard et la validation. Dans mes applications, une requête HTTP doit être valide des cinq façons ci-dessus pour que je puisse l'honorer. Dans un mode de défense en profondeur , vous n'obtenez jamais de validation d'entrée utilisateur côté serveur si l' une de ces cinq choses échoue.
Oui, cela signifie que même l'entrée de fichiers doit être conforme à vos tentatives frontales de vérifier et d'indiquer à l'utilisateur la taille de fichier maximale acceptée. Uniquement HTML? Pas de JavaScript? D'accord, mais l'utilisateur doit être informé des conséquences du téléchargement de fichiers trop volumineux (principalement, il perdra toutes les données du formulaire et sera expulsé du système).
4) Cela signifie-t-il que les données d'entrée de requête HTTP ne font pas partie de la logique métier de l'application? Non, cela signifie simplement que les ordinateurs sont des appareils finis et que les ressources doivent être utilisées à bon escient. Il est logique d'arrêter les activités malveillantes le plus tôt possible. Vous payez plus en ressources de calcul pour attendre de l'arrêter plus tard.
5) Si l' entrée de demande HTTP est mauvaise, la demande entière est mauvaise . Voilà comment je le vois. La définition d'une bonne entrée de requête HTTP est dérivée des exigences commerciales du modèle, mais il doit y avoir un point de démarcation des ressources. Combien de temps laisserez-vous vivre une mauvaise demande avant de la tuer et de dire: "Oh, hé, tant pis. Mauvaise demande."
Le jugement n'est pas simplement que l'utilisateur a fait une erreur de saisie raisonnable, mais qu'une requête HTTP est tellement hors limites qu'elle doit être déclarée malveillante et arrêtée immédiatement.
6) Donc, pour mon argent, la requête HTTP (MÉTHODE, URL / route et données) est TOUT bonne ou RIEN d'autre ne peut continuer. Un modèle robuste a déjà des tâches de validation à gérer, mais un bon berger de ressources dit: "Mon chemin, ou le chemin du haut. Venez correct, ou ne venez pas du tout."
Mais c'est votre programme. "Il y a plus d'une façon de le faire." Certains moyens coûtent plus cher en temps et en argent que d'autres. La validation ultérieure des données de requête HTTP (dans le modèle) devrait coûter plus cher pendant la durée de vie d'une application (en particulier si elle est mise à l'échelle ou augmentée).
Si vos validateurs sont modulaires, la validation de l'entrée de requête HTTP * de base ** dans le contrôleur ne devrait pas poser de problème. Il vous suffit d'utiliser une classe Validator stratégique, où les validateurs sont parfois également composés de validateurs spécialisés (e-mail, téléphone, jeton de formulaire, captcha, ...).
Certains voient cela comme complètement faux, mais HTTP en était à ses balbutiements lorsque le gang des quatre a écrit Design Patterns: Elements of Re-usable Object-Oriented Software .
================================================== ========================
Maintenant, en ce qui concerne la validation d'entrée utilisateur normale (une fois que la requête HTTP a été jugée valide), il met à jour la vue lorsque l'utilisateur gâche et que vous devez y penser! Ce type de validation d'entrée utilisateur doit se produire dans le modèle.
Vous n'avez aucune garantie de JavaScript sur le front-end. Cela signifie que vous n'avez aucun moyen de garantir la mise à jour asynchrone de l'interface utilisateur de votre application avec des états d'erreur. Une véritable amélioration progressive couvrirait également le cas d'utilisation synchrone.
La prise en compte du cas d'utilisation synchrone est un art qui se perd de plus en plus parce que certaines personnes ne veulent pas passer par le temps et se tracasser pour suivre l'état de toutes leurs astuces d'interface utilisateur (afficher / masquer les contrôles, désactiver / activer les contrôles , indications d'erreur, messages d'erreur) sur le serveur principal (généralement en suivant l'état des tableaux).
Mise à jour : Dans le diagramme, je dis que le
View
devrait faire référence auModel
. Non. Vous devez transmettre les données àView
partir duModel
pour conserver le couplage lâche.la source