Estou usando o padrão MVC no meu aplicativo da Web criado com PHP.
Estou sempre lutando para determinar se preciso de um novo controlador dedicado para um conjunto de ações ou se devo colocá-los dentro de um controlador já existente.
Existem boas regras práticas a seguir ao criar controladores?
Por exemplo, eu posso ter:
AuthenticationController
com ações:
index()
para exibir o formulário de login.submit()
para lidar com o envio de formulários.logout()
, auto-explicativo.
OU
LoginController
com ações:
index()
para exibir o formulário de login.submit()
para lidar com o envio de formulários.
LogoutController
com ação:
index()
para lidar com o logout.
OU
AccountController
com ações:
loginGet()
para exibir o formulário de login.loginPost()
para lidar com o envio do formulário de login.logoutGet()
para lidar com o logout.registerGet()
para exibir o formulário de inscrição.registerPost()
para lidar com o envio de formulários.E quaisquer outras ações envolvidas em uma conta.
Respostas:
Para encontrar o agrupamento certo para os controladores, pense nos testes .
(Mesmo que você não faça nenhum teste, pensar em como você testaria seus controladores fornecerá idéias muito boas sobre como estruturá-los.)
Um
AuthenticationController
não pode ser testado por si só, porque contém apenas a funcionalidade de logon e logout, mas seu código de teste precisará, de alguma forma, criar contas falsas para fins de teste antes de poder testar um login bem-sucedido. Você pode ignorar o subsistema em teste e ir diretamente ao seu modelo para a criação das contas de teste, mas terá um teste frágil em suas mãos: se o modelo for alterado, será necessário modificar não apenas o código dos testes o modelo, mas também o código que testa o controlador, mesmo que a interface e o comportamento do controlador permaneçam inalterados. Isso não é razoável.A
LoginController
é inadequado pelos mesmos motivos: você não pode testá-lo sem criar contas primeiro, e há ainda mais coisas que não pode testar, como, por exemplo, impedir logons duplicados, mas permitir que um usuário efetue login após o logout. (Como esse controlador não possui funcionalidade de logoff).Um
AccountController
fornecerá tudo o que você precisa para realizar seus testes: você pode criar uma conta de teste e tentar fazer login, você pode excluir a conta e garantir que não pode mais fazer login, alterar a senha e garantir que o senha correta deve ser usada para fazer login, etc.Para concluir: para escrever até o menor conjunto de testes, você precisará disponibilizar toda a funcionalidade do
AccountController
mesmo. Subdividi-lo para controladores menores parece estar gerando controladores deficientes com funcionalidade insuficiente para um teste adequado. Essa é uma indicação muito boa de que a funcionalidade deAccountController
é a menor subdivisão que faz sentido.E, de um modo geral, a abordagem "pensar no teste" funcionará não apenas nesse cenário específico, mas em qualquer cenário semelhante que você encontrar no futuro.
fonte
A resposta não é tão óbvia
Deixe-me esclarecer algumas coisas antes de fazer qualquer declaração de resposta. Em primeiro lugar:
Qual é o controlador?
O controlador é uma parte do sistema que controla a solicitação - após o envio. Assim, podemos defini-lo como um conjunto de ações relacionadas a ... o quê?
Qual é o escopo do controlador?
E isso é mais ou menos parte quando teremos qualquer resposta. O que você acha? É um controlador de coisas (por exemplo, uma conta) ou um controlador de ações? É claro que é um controlador de algum modelo ou algo mais abstrato que fornece ações sobre ele.
A resposta é...
Nah, autenticação é um processo. Não siga por esse caminho.
O mesmo aqui. Login - ação. Melhor não crie um controlador de ação (você não tem um modelo correlacionado).
Muito bom, mas não estou convencido de que vale a pena criar esse controlador de baixo nível (o controlador é a própria abstração). De qualquer forma, a criação de métodos com * Get ou * Post não é clara.
Alguma sugestão?
Sim, considere:
AccountController:
E modelo relacionado a ele, ofc Classe de conta. Isso lhe dará a oportunidade de mover seu par de modelo-controlador para outro lugar (se for necessário) e criar um código claro (é óbvio o que o
login()
método significa). Stincking para modelar é realmente famoso, especialmente com aplicativos CRUD e talvez seja uma maneira de você.fonte
Os controladores geralmente são criados para um determinado recurso (uma classe de entidade, uma tabela no banco de dados), mas também podem ser criados para agrupar ações que são responsáveis por uma determinada parte do aplicativo. Nos seus exemplos, isso seria um controlador que lida com a segurança do aplicativo:
Nota : não coloque as ações relacionadas à segurança e as ações de perfil de usuário no mesmo controlador; pode fazer sentido porque eles estão relacionados ao usuário, mas um deve lidar com autenticação e o outro com atualizações de email, nome etc.
Com os controladores criados para recursos (digamos
Task
), você teria as ações CRUD usuais :Obviamente, você tem a possibilidade de adicionar recursos relacionados ao mesmo controlador. Digamos, por exemplo, que você tenha a entidade
Business
e cada uma tenha váriasBusinessService
entidades. Um controlador para ele pode ficar assim:Essa abordagem faz sentido quando as entidades filhas relacionadas não podem existir sem a entidade pai.
Estas são as minhas recomendações:
Subscription
entidade que tenha uma disponibilidade com base em um número limitado de entradas, você pode adicionar uma nova ação ao controlador nomeadouse()
que tem o único objetivo de subtrair uma entrada daSubscription
)Alguns recursos para leitura adicional aqui .
fonte
Eu vejo duas "forças" antagônicas de design (que não são exclusivas dos controladores):
Do ponto de vista da coesão, todas as três ações (login, logout, registro) estão relacionadas, mas o login e logout são muito mais do que o registro. Eles são relacionados semanticamente (um é uma inversão do outro) e, possivelmente, também usarão os mesmos objetos de serviço (suas implementações também são coesas).
Meu primeiro instict seria agrupar o login e o logout em um controlador. Mas se as implementações dos controladores de logon e logout não forem tão simples (por exemplo, o logon possui captcha, mais métodos de autenticação etc.), eu não teria problema em dividi-las em LoginController e LogoutController para manter a simplicidade. Onde esse limite de complexidade (quando você deve começar a dividir o controlador) se encontra é um pouco pessoal.
Lembre-se também de que, independentemente do design do código, você pode (e deve) refatorá-lo à medida que ele muda. Nesse caso, é bastante comum começar com um design simples (tenha um AuthenticationController) e, com o tempo, você receberá mais requisitos que complicarão o código. Depois de cruzar o limite de complexidade, você deve refatorá-lo para dois controladores.
BTW, seu código sugere que você está desconectando o usuário com a solicitação GET. Essa é uma péssima idéia, já que o HTTP GET deve ser nulo-potencial (não deve modificar o estado do aplicativo).
fonte
Aqui estão algumas regras práticas:
Organize por assunto ou tópico, com o nome do controlador sendo o nome do tópico.
Lembre-se de que o nome do controlador aparecerá na URL, visível para os usuários, portanto, de preferência, deve fazer sentido para eles.
Na situação mencionada (autenticação), a equipe MVC já escreveu o controlador para você. Abra o Visual Studio 2013 e clique em
AccountController.cs contém todos os métodos para gerenciar contas de usuário:
Portanto, eles organizaram por tópico "Contas de usuário e autenticação", com o nome de tópico visível "Conta".
fonte
Terminologia
Eu acredito que é um grande equívoco chamar uma classe que contém alguns métodos relacionados a HTTP de "controlador".
Controller é um método que lida com solicitação, mas não uma classe que contém esses métodos . Assim,
index()
,submit()
,logout()
são controladores.A classe que contém esse tipo de método é denominada "controlador" apenas porque constitui um grupo de controladores e está desempenhando um papel de espaço para nome "nível inferior". Na linguagem FP (como Haskell), seria apenas um módulo. É uma boa prática manter essas classes "controladoras" o mais sem estado possível nas linguagens OOP, exceto as referências a serviços e outras coisas de todo o programa.
A resposta
Com a terminologia resolvida, a pergunta é "como devemos separar os controladores em namespaces / módulos?" Penso que a resposta é: os controladores dentro de um único espaço para nome / módulo devem lidar com o mesmo tipo de dados . Por exemplo,
UserController
lida principalmente com instâncias deUser
classe, mas ocasionalmente toca em outras coisas relacionadas, se necessário.Como
login
,logout
e outras ações desse tipo estão lidando principalmente com a sessão, provavelmente é melhor colocá-las dentroSessionController
, e oindex
controller, que apenas imprime um formulário, deve ser colocadoLoginPageController
, pois obviamente trata da página de login. Faz um pouco de sentido colocar a renderização HTML e o gerenciamento de sessões em uma única classe, o que violaria o SRP e provavelmente um monte de outras boas práticas.Princípio geral
Quando tiver problemas para decidir onde colocar um pedaço de código, comece pelos dados (e tipos) com os quais você lida.
fonte