Estou apenas entendendo a estrutura do MVC e sempre me pergunto quanto código deve haver no modelo. Eu costumo ter uma classe de acesso a dados que possui métodos como este:
public function CheckUsername($connection, $username)
{
try
{
$data = array();
$data['Username'] = $username;
//// SQL
$sql = "SELECT Username FROM" . $this->usersTableName . " WHERE Username = :Username";
//// Execute statement
return $this->ExecuteObject($connection, $sql, $data);
}
catch(Exception $e)
{
throw $e;
}
}
Meus modelos tendem a ser uma classe de entidade que é mapeada para a tabela do banco de dados.
O objeto de modelo deve ter todas as propriedades mapeadas do banco de dados, bem como o código acima, ou é correto separar esse código que realmente funciona no banco de dados?
Vou acabar tendo quatro camadas?
php
oop
model-view-controller
architecture
model
Dietpixel
fonte
fonte
Exception
não tem muito valor de documentação. Pessoalmente, se eu seguisse esse caminho, escolheria o PHPDoc@exception
, ou algum mecanismo semelhante, para que apareça na documentação gerada.Respostas:
A primeira coisa que devo esclarecer é: o modelo é uma camada .
Segundo: existe uma diferença entre o MVC clássico e o que usamos no desenvolvimento da web. Aqui está uma resposta mais antiga que escrevi, que descreve brevemente como elas são diferentes.
O que um modelo NÃO é:
O modelo não é uma classe ou nenhum objeto único. É um erro muito comum de cometer (também o fiz, embora a resposta original tenha sido escrita quando comecei a aprender o contrário) , porque a maioria das estruturas perpetua esse equívoco.
Nem é uma técnica de mapeamento objeto-relacional (ORM), nem uma abstração de tabelas de banco de dados. Qualquer pessoa que diga o contrário provavelmente tentará "vender" outro ORM novinho em folha ou uma estrutura inteira.
O que é um modelo:
Na adaptação adequada do MVC, o M contém toda a lógica de negócios do domínio e a Camada de Modelo é composta principalmente de três tipos de estruturas:
Objetos de domínio
É aqui que você define como validar os dados antes de enviar uma fatura ou calcular o custo total de um pedido. Ao mesmo tempo, os Objetos de Domínio desconhecem completamente o armazenamento - nem de onde (banco de dados SQL, API REST, arquivo de texto etc.) nem mesmo se forem salvos ou recuperados.
Mapeadores de dados
Esses objetos são responsáveis apenas pelo armazenamento. Se você armazenar informações em um banco de dados, é onde o SQL mora. Ou talvez você use um arquivo XML para armazenar dados, e seus Mapeadores de Dados estão analisando de e para arquivos XML.
Serviços
Você pode pensar neles como "Objetos de Domínio de nível superior", mas, em vez da lógica de negócios, os Serviços são responsáveis pela interação entre Objetos de Domínio e Mapeadores . Essas estruturas acabam criando uma interface "pública" para interagir com a lógica de negócios do domínio. Você pode evitá-los, mas sob pena de vazar alguma lógica de domínio nos Controladores .
Há uma resposta relacionada a esse assunto na pergunta de implementação da ACL - pode ser útil.
A comunicação entre a camada do modelo e outras partes da tríade MVC deve ocorrer apenas através dos Serviços . A separação clara tem alguns benefícios adicionais:
Como interagir com um modelo?
Obtendo acesso a instâncias de serviço
Para as instâncias View e Controller (o que você poderia chamar de "camada da interface do usuário") para acessar esses serviços, há duas abordagens gerais:
Como você pode suspeitar, o contêiner DI é uma solução muito mais elegante (embora não seja a mais fácil para iniciantes). As duas bibliotecas que recomendo considerar para essa funcionalidade seriam o componente DependencyInjection autônomo da Syfmony ou Auryn .
As soluções que usam uma fábrica e um contêiner de DI também permitem compartilhar as instâncias de vários servidores a serem compartilhadas entre o controlador selecionado e exibir um determinado ciclo de solicitação-resposta.
Alteração do estado do modelo
Agora que você pode acessar a camada de modelo nos controladores, é necessário começar a usá-los:
Seus controladores têm uma tarefa muito clara: pegue a entrada do usuário e, com base nessa entrada, altere o estado atual da lógica de negócios. Neste exemplo, os estados que são alterados são "usuário anônimo" e "usuário conectado".
O Controller não é responsável por validar a entrada do usuário, porque isso faz parte das regras de negócios e o controlador definitivamente não está chamando consultas SQL, como o que você veria aqui ou aqui (por favor, não as odeie, elas são mal orientadas, não são más).
Mostrando ao usuário a mudança de estado.
Ok, o usuário efetuou login (ou falhou). O que agora? O referido usuário ainda não o conhece. Então, você precisa realmente produzir uma resposta e essa é a responsabilidade de uma visão.
Nesse caso, a visualização produziu uma das duas respostas possíveis, com base no estado atual da camada do modelo. Para um caso de uso diferente, você teria a visualização escolhendo modelos diferentes para renderizar, com base em algo como "atual selecionado do artigo".
A camada de apresentação pode ficar bastante elaborada, como descrito aqui: Entendendo as visualizações MVC no PHP .
Mas estou apenas criando uma API REST!
Claro, existem situações em que isso é um exagero.
O MVC é apenas uma solução concreta para o princípio da Separação de Preocupações . O MVC separa a interface do usuário da lógica de negócios e, na interface do usuário, separou a manipulação da entrada do usuário e a apresentação. Isto é crucial. Embora muitas vezes as pessoas o descrevam como uma "tríade", na verdade não é composta de três partes independentes. A estrutura é mais ou menos assim:
Isso significa que, quando a lógica da sua camada de apresentação é quase inexistente, a abordagem pragmática é mantê-la como uma única camada. Também pode simplificar substancialmente alguns aspectos da camada do modelo.
Usando essa abordagem, o exemplo de login (para uma API) pode ser escrito como:
Embora isso não seja sustentável, quando você tem uma lógica complicada para renderizar um corpo de resposta, essa simplificação é muito útil para cenários mais triviais. Mas esteja avisado , essa abordagem se tornará um pesadelo, ao tentar usar em grandes bases de código com lógica de apresentação complexa.
Como construir o modelo?
Como não há uma única classe "Modelo" (como explicado acima), você realmente não "constrói o modelo". Em vez disso, você começa a criar serviços , capazes de executar determinados métodos. E, em seguida, implemente objetos de domínio e mapeadores .
Um exemplo de um método de serviço:
Nas duas abordagens acima, havia esse método de login para o serviço de identificação. Como seria realmente. Estou usando uma versão ligeiramente modificada da mesma funcionalidade de uma biblioteca , que escrevi .. porque sou preguiçoso:
Como você pode ver, nesse nível de abstração, não há indicação de onde os dados foram buscados. Pode ser um banco de dados, mas também pode ser apenas um objeto falso para fins de teste. Até os mapeadores de dados, que são realmente usados para isso, ficam ocultos nos
private
métodos desse serviço.Maneiras de criar mapeadores
Para implementar uma abstração de persistência, nas abordagens mais flexíveis é criar mapeadores de dados personalizados .
De: livro PoEAA
Na prática, eles são implementados para interação com classes ou superclasses específicas. Digamos que você tenha
Customer
eAdmin
em seu código (ambos herdados de umaUser
superclasse). Ambos provavelmente acabariam tendo um mapeador correspondente separado, pois eles contêm campos diferentes. Mas você também terminará com operações compartilhadas e comumente usadas. Por exemplo: atualizando o horário da "última vez online" . E, em vez de tornar os mapeadores existentes mais complicados, a abordagem mais pragmática é ter um "Mapeador de Usuários" geral, que apenas atualiza esse carimbo de data / hora.Alguns comentários adicionais:
Tabelas e modelo de banco de dados
Embora, às vezes, exista um relacionamento direto 1: 1: 1 entre uma tabela de banco de dados, objeto de domínio e mapeador , em projetos maiores, pode ser menos comum do que o esperado:
As informações usadas por um único Objeto de Domínio podem ser mapeadas de diferentes tabelas, enquanto o próprio objeto não tem persistência no banco de dados.
Exemplo: se você estiver gerando um relatório mensal. Isso coletaria informações de diferentes tabelas, mas não há
MonthlyReport
tabela mágica no banco de dados.Um único mapeador pode afetar várias tabelas.
Exemplo: ao armazenar dados do
User
objeto, este Objeto de Domínio pode conter uma coleção de outros objetos de domínio -Group
instâncias. Se você os alterar e armazenarUser
, o Mapeador de Dados precisará atualizar e / ou inserir entradas em várias tabelas.Os dados de um único objeto de domínio são armazenados em mais de uma tabela.
Exemplo: em sistemas grandes (pense: uma rede social de tamanho médio), pode ser pragmático armazenar dados de autenticação do usuário e dados frequentemente acessados separadamente de grandes partes do conteúdo, o que raramente é necessário. Nesse caso, você ainda pode ter uma única
User
classe, mas as informações que ela contém dependerão da obtenção de todos os detalhes.Para cada objeto de domínio , pode haver mais de um mapeador
Exemplo: você tem um site de notícias com um código compartilhado baseado no software de gerenciamento e público. Mas, embora ambas as interfaces usem a mesma
Article
classe, o gerenciamento precisa de muito mais informações nela preenchidas. Nesse caso, você teria dois mapeadores separados: "interno" e "externo". Cada um deles realiza consultas diferentes ou usa bancos de dados diferentes (como no mestre ou no escravo).Uma visualização não é um modelo
As instâncias de exibição no MVC (se você não estiver usando a variação do padrão MVP) são responsáveis pela lógica de apresentação. Isso significa que cada modo de exibição geralmente manipula pelo menos alguns modelos. Ele adquire dados da Camada de Modelo e, com base nas informações recebidas, escolhe um modelo e define valores.
Um dos benefícios que você ganha com isso é a reutilização. Se você criar uma
ListView
classe, então, com código bem escrito, poderá ter a mesma classe entregando a apresentação da lista de usuários e comentários abaixo de um artigo. Porque ambos têm a mesma lógica de apresentação. Você acabou de mudar de modelo.Você pode usar modelos PHP nativos ou algum mecanismo de modelagem de terceiros. Também pode haver algumas bibliotecas de terceiros, capazes de substituir completamente as instâncias do View .
E a versão antiga da resposta?
A única grande mudança é que, o que é chamado de Modelo na versão antiga, é na verdade um Serviço . O restante da "analogia da biblioteca" mantém-se muito bem.
A única falha que vejo é que essa seria uma biblioteca realmente estranha, porque retornaria informações do livro, mas não permitiria que você tocasse no livro em si, porque, caso contrário, a abstração começaria a "vazar". Talvez eu precise pensar em uma analogia mais adequada.
Qual é o relacionamento entre as instâncias do View e do Controller ?
A estrutura MVC é composta de duas camadas: interface do usuário e modelo. As principais estruturas na camada da interface do usuário são visualizações e controlador.
Quando você lida com sites que usam o padrão de design MVC, a melhor maneira é ter uma relação 1: 1 entre visualizações e controladores. Cada visualização representa uma página inteira no seu site e possui um controlador dedicado para lidar com todas as solicitações recebidas para essa visualização específica.
Por exemplo, para representar um artigo aberto, você teria
\Application\Controller\Document
e\Application\View\Document
. Isso conteria todas as principais funcionalidades da camada de interface do usuário, quando se trata de artigos (é claro que você pode ter alguns componentes XHR que não estão diretamente relacionados aos artigos) .fonte
Tudo o que é lógica de negócios pertence a um modelo, seja uma consulta ao banco de dados, cálculos, uma chamada REST, etc.
Você pode ter acesso a dados no próprio modelo, o padrão MVC não o impede de fazer isso. Você pode usá-lo com serviços, mapeadores e o que não, mas a definição real de um modelo é uma camada que lida com a lógica de negócios, nada mais, nada menos. Pode ser uma classe, uma função ou um módulo completo com um zilhão de objetos, se é isso que você deseja.
É sempre mais fácil ter um objeto separado que efetivamente executa as consultas ao banco de dados, em vez de executá-las diretamente no modelo: isso será especialmente útil no teste de unidade (devido à facilidade de injetar uma dependência de banco de dados simulada no seu modelo):
Além disso, no PHP, você raramente precisa capturar / repetir exceções porque o backtrace é preservado, especialmente em um caso como o seu exemplo. Apenas deixe a exceção ser lançada e pegue-a no controlador.
fonte
User
classe basicamente estende o modelo, mas não é um objeto. O usuário deve ser um objeto e possuir propriedades como: id, nome ... Você está implementando aUser
classe como auxiliar.User
significa um objeto e deve ter propriedades de um usuário, não métodos comoCheckUsername
, o que você deve fazer se quiser criar um novoUser
objeto?new User($db)
Na Web "MVC", você pode fazer o que quiser.
O conceito original (1) descreveu o modelo como a lógica de negócios. Ele deve representar o estado do aplicativo e aplicar alguma consistência dos dados. Essa abordagem é frequentemente descrita como "modelo de gordura".
A maioria das estruturas PHP segue uma abordagem mais superficial, onde o modelo é apenas uma interface de banco de dados. Mas, no mínimo, esses modelos ainda devem validar os dados e as relações recebidas.
De qualquer forma, você não estará muito longe se você separar as chamadas SQL ou de banco de dados em outra camada. Dessa forma, você só precisa se preocupar com os dados / comportamento reais, não com a API de armazenamento real. (No entanto, não é razoável exagerar. Você nunca poderá substituir um back-end de banco de dados por um armazenamento de arquivos, se isso não tiver sido projetado adiante.)
fonte
Mais oftenly a maioria dos aplicativos terá dados, exibição e parte de processamento e nós apenas colocar todos aqueles nas cartas
M
,V
eC
.Model (
M
) -> Possui os atributos que mantêm o estado de aplicação e não sabe nada sobreV
eC
.View (
V
) -> Possui formato de exibição para o aplicativo e apenas conhece o modelo de como digerir e não se preocupaC
.Controller (
C
) ----> Possui parte de processamento do aplicativo e atua como fiação entre M e V e depende de ambosM
,V
diferente deM
eV
.No total, há uma separação de preocupações entre cada um. No futuro, qualquer alteração ou aprimoramento poderá ser adicionada com muita facilidade.
fonte
No meu caso, eu tenho uma classe de banco de dados que lida com toda a interação direta com o banco de dados, como consultas, buscas e outras coisas. Portanto, se eu tivesse que mudar meu banco de dados do MySQL para PostgreSQL , não haveria nenhum problema. Portanto, adicionar essa camada extra pode ser útil.
Cada tabela pode ter sua própria classe e métodos específicos, mas, para obter os dados, ela permite que a classe do banco de dados lide com isso:
Arquivo
Database.php
Objeto de tabela classL
Espero que este exemplo ajude você a criar uma boa estrutura.
fonte
Database
no exemplo não é uma classe. É apenas um invólucro para funções. Além disso, como você pode ter "classe de objeto de tabela" sem um objeto?