Primeira resposta: Um papel fundamental do modelo é manter a integridade. Entretanto, o processamento da entrada do usuário é de responsabilidade de um controlador.
Ou seja, o controlador deve converter os dados do usuário (que na maioria das vezes são apenas strings) em algo significativo. Isso requer análise (e pode depender de coisas como a localidade, pois, por exemplo, existem diferentes operadores decimais etc.).
Portanto, a validação real, como em "os dados estão bem formados?", Deve ser realizada pelo controlador. No entanto, a verificação, como em "os dados fazem sentido?" deve ser realizado dentro do modelo.
Para esclarecer isso com um exemplo:
Suponha que seu aplicativo permita adicionar algumas entidades, com uma data (um problema com um prazo final, por exemplo). Você pode ter uma API, na qual as datas podem ser representadas como meros carimbos de data e hora do Unix, enquanto, quando proveniente de uma página HTML, será um conjunto de valores diferentes ou uma sequência no formato MM / DD / AAAA. Você não deseja essas informações no modelo. Você deseja que cada controlador tente individualmente descobrir a data. No entanto, quando a data é passada para o modelo, o modelo deve manter a integridade. Por exemplo, pode fazer sentido não permitir datas no passado ou datas de feriados / domingos etc.
Seu controlador contém regras de entrada (processamento). Seu modelo contém regras de negócios. Você deseja que suas regras de negócios sejam sempre aplicadas, não importa o que aconteça. Supondo que você tenha regras de negócios no controlador, será necessário duplicá-las, caso crie um controlador diferente.
Segunda resposta: A abordagem faz sentido, no entanto, o método pode ser mais poderoso. Em vez de o último parâmetro ser uma matriz, deve ser uma instância IContstraint
definida como:
interface IConstraint {
function test($value);//returns bool
}
E para números, você pode ter algo como
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);
}
}
Também não vejo o que 'Age'
se pretende representar, para ser honesto. É o nome da propriedade real? Supondo que exista uma convenção por padrão, o parâmetro pode simplesmente ir até o final da função e ser opcional. Se não estiver definido, o padrão será o to_camel_case do nome da coluna DB.
Assim, a chamada de exemplo seria semelhante a:
register_property('age', new NumConstraint(1, 10, 30));
O objetivo do uso de interfaces é que você pode adicionar mais e mais restrições à medida que avança e elas podem ser tão complicadas quanto você desejar. Para uma string corresponder a uma expressão regular. Para uma data com pelo menos 7 dias de antecedência. E assim por diante.
Terceira resposta: Toda entidade Modelo deve ter um método como Result checkValue(string property, mixed value)
. O controlador deve chamá-lo antes de definir os dados. Ele Result
deve ter todas as informações sobre se a verificação falhou e, caso tenha ocorrido, forneça razões, para que o controlador possa propagar essas para a visualização de acordo.
Se um valor errado for passado para o modelo, o modelo deve simplesmente responder levantando uma exceção.
Não concordo totalmente com o "back2dos": minha recomendação é sempre usar uma camada de formulário / validação separada, que o controlador possa usar para validar os dados de entrada antes de serem enviados ao modelo.
Do ponto de vista teórico, a validação do modelo opera com dados confiáveis (estado interno do sistema) e, idealmente, pode ser repetida a qualquer momento, enquanto a validação de entrada opera explicitamente uma vez em dados provenientes de fontes não confiáveis (dependendo do caso de uso e dos privilégios do usuário).
Essa separação possibilita a criação de modelos, controladores e formulários reutilizáveis que podem ser acoplados livremente através da injeção de dependência. Pense na validação de entrada como validação da lista de permissões ("aceite bom conhecido") e validação de modelo como validação de lista negra ("rejeite conhecido ruim"). A validação da lista de permissões é mais segura, enquanto a validação da lista negra impede que sua camada de modelo seja excessivamente restrita a casos de uso muito específicos.
Dados de modelo inválidos sempre devem gerar uma exceção (caso contrário, o aplicativo pode continuar em execução sem perceber o erro), enquanto valores de entrada inválidos provenientes de fontes externas não são inesperados, mas sim comuns (a menos que você tenha usuários que nunca cometerão erros).
Veja também: https://lastzero.net/2015/11/why-im-using-a-separate-layer-for-input-data-validation/
fonte
Sim, o modelo deve executar a validação. A interface do usuário deve validar a entrada também.
É claramente da responsabilidade do modelo determinar valores e estados válidos. Às vezes, essas regras mudam com frequência. Nesse caso, eu alimentaria o modelo a partir de metadados e / ou o decoraria.
fonte
Ótima pergunta!
Em termos de desenvolvimento na World Wide Web, e se você perguntasse o seguinte, também.
"Se uma entrada incorreta do usuário for fornecida a um controlador a partir de uma interface com o usuário, o controlador deve atualizar o View em um tipo de loop cíclico, forçando comandos e dados de entrada a serem precisos antes de processá-los ? Como? Como? Como o modo de exibição é atualizado normalmente? É uma visão fortemente acoplada a um modelo? A validação de entrada do usuário é a lógica de negócios principal do modelo, ou é preliminar a ele e, portanto, deve ocorrer dentro do controlador (porque os dados de entrada do usuário fazem parte da solicitação)?
(Com efeito, pode e deve-se atrasar a instanciação de um modelo até que boas informações sejam adquiridas?)
Minha opinião é que os modelos devem gerenciar uma circunstância pura e pura (o máximo possível), livre da validação básica de entrada de solicitação HTTP que deve ocorrer antes da instanciação do modelo (e definitivamente antes que o modelo obtenha dados de entrada). Como gerenciar dados de estado (persistentes ou não) e relacionamentos de API é o mundo do modelo, permita que a validação básica de entrada de solicitação HTTP ocorra no controlador.
Resumindo.
1) Valide sua rota (analisada a partir da URL), pois o controlador e o método devem existir antes que qualquer outra coisa possa avançar. Definitivamente, isso deve acontecer no domínio do controlador frontal (classe Router), antes de chegar ao verdadeiro controlador. Duh. :-)
2) Um modelo pode ter muitas fontes de dados de entrada: uma solicitação HTTP, um banco de dados, um arquivo, uma API e, sim, uma rede. Se você deseja colocar toda a validação de entrada no modelo, considere a validação de entrada de solicitação HTTP parte dos requisitos de negócios do programa. Caso encerrado.
3) No entanto, é míope passar pela despesa de instanciar muitos objetos se a entrada da solicitação HTTP não for boa! Você pode saber se ** entrada de solicitação HTTP ** é boa ( que veio com a solicitação ) validando-a antes de instanciar o modelo e todas as suas complexidades (sim, talvez ainda mais validadores para API e DB de dados de entrada / saída).
Teste o seguinte:
a) O método de solicitação HTTP (GET, POST, PUT, PATCH, DELETE ...)
b) Controles HTML mínimos (você tem o suficiente?).
c) Máximo de controles HTML (você tem muitos?).
d) Controles HTML corretos (você tem os corretos?).
e) Codificação de entrada (normalmente, é a codificação UTF-8?).
f) Tamanho máximo da entrada (alguma das entradas está muito fora dos limites?).
Lembre-se, você pode obter seqüências de caracteres e arquivos, portanto, aguardar o modelo instanciar pode ficar muito caro à medida que as solicitações atingirem o servidor.
O que descrevi aqui ocorre com a intenção da solicitação que chega pelo controlador. Omitir a verificação de intenção deixa seu aplicativo mais vulnerável. A intenção só pode ser boa (seguindo suas regras fundamentais) ou ruim (indo além de suas regras fundamentais).
A intenção de uma solicitação HTTP é uma proposta de tudo ou nada. Tudo passa ou a solicitação é inválida . Não há necessidade de enviar nada para o modelo.
Esse nível básico de intenção de solicitação HTTP não tem nada a ver com erros e validação regulares de entrada do usuário. Nos meus aplicativos, uma solicitação HTTP deve ser válida das cinco maneiras acima para que eu a honre. Em uma defesa em profundidade maneira de falar, você nunca chegar a validação de entrada do usuário no lado do servidor, se nenhum destes cinco coisas falham.
Sim, isso significa que mesmo a entrada do arquivo deve estar em conformidade com as suas tentativas de front-end para verificar e informar ao usuário o tamanho máximo do arquivo aceito. Apenas HTML? Sem JavaScript? Tudo bem, mas o usuário deve ser informado das consequências do upload de arquivos muito grandes (principalmente, que eles perderão todos os dados do formulário e serão expulsos do sistema).
4) Isso significa que os dados de entrada da solicitação HTTP não fazem parte da lógica de negócios do aplicativo? Não, apenas significa que os computadores são dispositivos finitos e os recursos devem ser usados com sabedoria. Faz sentido interromper atividades maliciosas mais cedo ou mais tarde. Você paga mais em recursos de computação pela espera para interrompê-lo mais tarde.
5) Se a entrada da solicitação HTTP estiver incorreta, a solicitação inteira estará incorreta . É assim que eu olho para isso. A definição de boa entrada de solicitação HTTP é derivada dos requisitos de negócios do modelo, mas deve haver algum ponto de demarcação de recurso. Por quanto tempo você deixará um pedido ruim permanecer antes de matá-lo e dizer: "Ei, não importa. Pedido ruim".
O julgamento não é simplesmente que o usuário cometeu um erro razoável de entrada, mas que uma solicitação HTTP é tão fora dos limites que deve ser declarada maliciosa e interrompida imediatamente.
6) Portanto, para meu dinheiro, a solicitação HTTP (MÉTODO, URL / rota e dados) é TUDO boa ou NADA mais pode prosseguir. Um modelo robusto já tem tarefas de validação com as quais se preocupar, mas um bom pastor de recursos diz: "Meu caminho, ou o caminho mais alto. Seja correto, ou nem venha".
É o seu programa, no entanto. "Há mais de uma maneira de fazer isso". Algumas formas custam mais tempo e dinheiro do que outras. A validação dos dados da solicitação HTTP posteriormente (no modelo) deve custar mais ao longo da vida útil de um aplicativo (especialmente se estiver aumentando ou diminuindo).
Se seus validadores forem modulares, a validação da entrada básica * de solicitação HTTP ** no controlador não deve ser um problema. Basta usar uma classe Validator estrategizada, onde os validadores às vezes também são compostos por validadores especializados (email, telefone, token de formulário, captcha, ...).
Alguns vêem isso completamente errado, mas o HTTP estava em sua infância quando o Gang of Four escreveu Design Patterns: Elements of Re-usable Oriented Object Oriented .
==================================================== ========================
Agora, no que se refere à validação normal de entrada do usuário (depois que a solicitação HTTP for considerada válida), ela está atualizando a visualização quando o usuário faz uma bagunça que você precisa pensar! Esse tipo de validação de entrada do usuário deve ocorrer no modelo.
Você não tem garantia de JavaScript no front-end. Isso significa que você não tem como garantir a atualização assíncrona da interface do usuário do seu aplicativo com status de erro. O verdadeiro aprimoramento progressivo também abrangeria o caso de uso síncrono.
Contabilizar o caso de uso síncrono é uma arte que está se perdendo cada vez mais, porque algumas pessoas não desejam passar pelo tempo e pelos problemas de rastrear o estado de todos os seus truques de interface do usuário (mostrar / ocultar controles, desativar / ativar controles , indicações de erro, mensagens de erro) no back-end (geralmente acompanhando o estado nas matrizes).
Atualização : no diagrama, eu digo que o
View
deve fazer referência aModel
. Não. Você deve passar os dadosView
doModel
para preservar o acoplamento solto.fonte