MVC: A arquitetura queridinha da Web
- b7web
- Javascript, PHP
- fev 13, 2022
Atualmente no curso de PHP oferecido pela nossa plataforma aqui na B7Web, nós ensinamos aos nossos alunos sobre como trabalhar com a arquitetura MVC, dessa forma os preparando para lidar com frameworks muito utilizados no mercado de trabalho, como o laravel, que são construídos ao redor dessa arquitetura.
E uma dúvida as vezes levantada por nossos alunos se refere a como a estrutura MVC – providenciada pelo nosso curso – funciona.
Por isso neste artigo iremos abordar desde a parte conceitual até o desenvolvimento da estrutura utilizada no curso, bem como uma análise sobre as escolhas de design tomadas durante a sua criação.
O que é o MVC
MVC é uma abreviação para Model, View e Controller, ele é um padrão de arquitetura de software criado por Trygve Reenskaug em 1979, que tem como principal objetivo a separação dos interesses, assim estruturando o código de forma que fosse possível separar a interface gráfica da lógica de negócios. Ele foi um padrão arquitetural desenvolvido para a criação de interfaces gráficas (uma grande ironia visto que hoje em dia a maior parte das pessoas entende MVC como algo de back-end) para desktop utilizando uma linguagem chamada Smalltalk, considerada a primeira linguagem orientada a objetos do mundo por muitos.
Alguns exempos de implementações da arquitetura MVC utilizados no mercado hoje em dia:
- Ruby on Rails (Framework de Ruby e quem popularizou o MVC moderno na web)
- Laravel (Framework de PHP)
- AdonisJS (Framework de Node.js)
- Spring MVC (Framework de Java)
- ASP.NET MVC (Framework de C#)
O que é um padrão arquitetural
Antes de avançarmos no assunto principal vou fazer uma pequena tangênte para explicar um conceito que talvez não tenha ficado claro e que é vital para o entendimento do MVC: arquitetura de software. Afinal o MVC é um padrão arquitetural, que é um padrão de como elaborar a arquitetura do seu sistema. E assim como casas e prédios precisam de uma boa arquitetura senão eles desabam, o seu projeto de software também necessita de uma boa arquitetura, que nada mais é que a forma que você organiza o seu projeto, quais os módulos dele (pode ser entendido como classes e funções), quem fica responsável por fazer o que, e como esses módulos vão interagir.
Uma coisa a se destacar é que embora seguir um padrão arquitetural a risca seja bom, você como programador e arquiteto do projeto não precisa segui-lo 100% a risca, você deve ponderar cada decisão arquitetural do padrão que você deseja seguir, e adaptar certas coisas de acordo com a necessidade do seu projeto.
Voltando a arquitetura em si
O MVC assim como eu já citei nos últimos paragrafos é a estrutura que nós vamos utilizar para criar o nosso sistema, e se formos seguir ela, nós deveríamos dividir o nosso sistema em 3 tipos principais de classes: Model, View e Controller.
Model
O Model vão ser todos os objetos que vão lidar com regras de negócio, e ser o que nós chamamos de objetos de domínio, ou seja, objetos que representam os elementos do problema que nossa aplicação busca resolver. Por exemplo, uma aplicação que vai ser uma lista de tarefas, uma tarefa é um dos elementos centrais, por isso teríamos um objeto que representaria uma tarefa dentro do nosso código.
Atualmente muita gente considera os models só como objetos responsáveis por interagir com o banco de dados, e embora essa definição não esteja errada afinal eles devem representar o estado da sua aplicação e esse estado geralmente está centralizado em um banco de dados, o mais correto é sempre lembrar que eles estão representando o estado desse elemento do domínio da sua aplicação, o banco é só um meio para guardar esse estado, e inclusive a manipulação dele as vezes nem deve ser feita pelo model, e sim por um objeto que saiba como lidar com o banco de maneira independente do Model, afim de não concentrar responsabilidades que não são exclusivas do domínio da aplicação em objetos que só deveriam se preocupar com o domínio dela.
View
A View indo para o outro extremo da aplicação é a camada onde vamos concentrar todo o código relacionado com a interface gráfica do nosso projeto (se você pensar em JavaScript no front-end, você pode considerar que é aqui onde nós faríamos as manipulações no DOM, e se você quer pensar em termos de back-end, poderia ser aqui onde nós utilizaríamos a nossa template engine). São os objetos que vão pegar o Model que nós elaboramos, e pedir os dados para ele para que eles possam ser mostrados na tela.
Controller
Perceba que nós fizemos de tudo para separar o código que tem a parte de negócio da nossa aplicação, do código que tem a parte de apresentação, então agora que eles estão totalmente isolados, vamos precisar juntar eles de novo, e é essa a função da camada de Controller.
Os Controllers são objetos que vão reagir aos inputs dos usuários como clicar em um botão, ou digitar em um formulário, e assim fazer as alterações necessárias nos models (perceba que uma relação interessante aqui é que as Views seria quem usa os getters do Model, e os controllers quem utilizaria os setters), que ao serem modificados deveriam notificar as Views para que elas saibam que o Model foi modificado e possam se rendenizar novamente na tela.
No contexto de web, pensando em uma aplicação divida no front-end e no back-end, não temos como comunicar um clique em um botão que está rodando no nosso cliente para um objeto que está rodando lá no nosso servidor. Então podemos dizer que ao invés de responder aos inputs do usuário, no contexto de web, os nossos controllers são os objetos que recebem as requisições HTTP e respondem a elas (requisições essas que podem vir de um link sendo clicado, um formulário sendo enviado, AJAX feito pelo JavaScript, e etc).
O MVC da B7Web
Agora que estabelecemos as bases teóricas sobre o MVC, vamos dar uma olhada na estrutura que utilizamos aqui no nosso curso de PHP, e que você pode encontrar aqui: https://github.com/suporteb7web/mvc.
Decisões de design
Ao analisar o código como um todo, e a didática utilizada pelo professor nas aulas, podemos estabelecer alguns princípios que guiaram o design dessa implementação:
- Ser semelhante a utilizar o Laravel
- Ser simples de usar
- Ser didático para iniciantes
Essas três coisas claramente foram as prioridades no design dessa implementação, e apesar de utilizar conceitos de orientação a objetos ser um objetivo importante também nesta implementação, ela fica em segundo plano várias vezes em detrimento desses 3 princípios, o que é uma coisa ruim já que o padrão foi pensado para ser implementado no paradigma orientado a objetos, porém também é uma coisa boa já que poderemos analisar a interação de mais de um paradigma na implementação da arquitetura MVC em um projeto.
Estrutura geral do projeto
Inicialmente o projeto tem 3 pastas principais:
- core
- public
- src
A pasta core é onde ficam as classes base que nos darão as funcionalidades básicas para implementarmos nossa aplicação nos concentrando em só criar as classes para lidar com o nosso problema, e nos livrando da preocupação com esses códigos de aplicação (que servem para fazer ela funcionar mas que não estão diretamente ligados ao problema sendo resolvido). Uma análogia interessante de se fazer aqui seria que assim como já recebemos classes prontas em um framework que servem de fundação para não termos que criar as mesmas classes todas as vezes, podemos pensar nas classes disponíveis na pasta core como essas classes de infraestrutura.
A pasta public é o ponto de entrada da nossa aplicação, e tem os arquivos que são públicos para navegadores acessarem, tais como arquivos css, javascript, imagens, e etc. Além de conter um arquivo index.php
que como citei anteriormente vai servir de ponto de entrada para a nossa aplicação, assim podemos direcionar todas as requisições para ele, semelhante a ideia do padrão FrontController apresentada por Martin Fowler.
A pasta src é onde, de fato, colocamos o nosso código na hora de desenvolver a nossa aplicação. É onde iremos escrever nossos controllers, models, views e outras coisas necessárias para o projeto.
E como no curso o uso dessa estrutura já é extensivamente abordado, então neste artigo iremos focar no conteúdo da pasta core
.
Views
As views nessa implementação não são uma classe em sí, elas são simplesmente os arquivos.php que contém HTML no sistema, e se encontram na pasta views. Uma interpretação mais próxima ao MVC clássico diria que teríamos que ter uma classe View para poder rendenizar essas views da pasta view na tela. Porém nessa estrutura o método utilizado para rendenizar a view se encontra no Controller, pois também podemos interpretar que rendenizar a view é uma responsabilidade do Controller, afinal isso ainda é um tipo válido para uma resposta HTTP, e lidar com as requisições e respostas HTTP é o trabalho do Controller.
Nessa estrutura, a pasta views tem duas sub-pastas: partials
e pages
, assim separando as views que são reutilízaveis no sistema das que representam uma página respectivamente.
Controllers
E falando neles, os Controllers como vimos anteriormente devem responder as ações dos usuários, assim ligando os códigos de View e de Model, para dar uma resposta para o usuário que fez a ação, e essa ação no nosso contexto de sistemas web seria uma requisição HTTP. Então vamos dar uma olhada em como isso foi implementado para analisarmos esses pontos nessa implementação:
<?php
namespace core;
use \\src\\Config;
class Controller
{
protected function redirect($url)
{
header("Location: " . $this->getBaseUrl() . $url);
exit;
}
private function getBaseUrl()
{
$base = (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') ? 'https://' : 'http://';
$base .= $_SERVER['SERVER_NAME'];
if ($_SERVER['SERVER_PORT'] != '80') {
$base .= ':' . $_SERVER['SERVER_PORT'];
}
$base .= Config::BASE_DIR;
return $base;
}
private function _render($folder, $viewName, $viewData = [])
{
if (file_exists('../src/views/' . $folder . '/' . $viewName . '.php')) {
extract($viewData);
$render = function ($vN, $vD = []) {
return $this->renderPartial($vN, $vD);
};
$base = $this->getBaseUrl();
require '../src/views/' . $folder . '/' . $viewName . '.php';
}
}
private function renderPartial($viewName, $viewData = [])
{
$this->_render('partials', $viewName, $viewData);
}
public function render($viewName, $viewData = [])
{
$this->_render('pages', $viewName, $viewData);
}
}
Aqui temos 5 métodos:
- redirect
- getBaseUrl
- _render
- renderPartial
- render
O redirect
assim como o nome sugere é responsável por redirecionar o usuário, pois ele defini um cabeçalho chamado location na resposta da requisição, que é um cabeçalho que quando recebido pelo navegador, faz com que ele redirecione para o endereço contido no cabeçalho.
O getBaseUrl
serve para montar uma URL para que as views consigam referenciar os arquivos estáticos da aplicação (as imagens, os CSSs, os arquivos JavaScript, e etc) e outras rotas também no caso das tags <a>
.
O _render
é um método que recebe as informações necessárias para localizar a view dentro da pasta views
, utiliza o extract
para criar váriaveis que ficaram acessíveis dentro da view a ser rendenizada ao passar um array associativo para ele, e por fim rendeniza a view ao utilizar o require
.
Os métodos render
e renderPartial
são especializações da _render
para rendenizar as views apontando para a pasta pages e para a pasta partials respectivamente.
Models
E agora que já falamos da camada de view e da camada de controller, só vai faltar a camada de Model para fechar a tríade. Essa em especial tem duas classes que foram utilizadas nessa camada: a Database
e a própria classe Model
, a seguir suas implementações:
<?php
namespace core;
use \\src\\Config;
class Database
{
private static $_pdo;
public static function getInstance()
{
if (!isset(self::$_pdo)) {
self::$_pdo = new \\PDO(Config::DB_DRIVER . ":dbname=" . Config::DB_DATABASE . ";host=" . Config::DB_HOST, Config::DB_USER, Config::DB_PASS);
}
return self::$_pdo;
}
private function __construct() {}
private function __clone() {}
private function __wakeup() {}
}
Essa classe Database é um Singleton (se você não sabe o que é isso clique aqui para conhecer o singleton) para a classe PDO, assim podemos utilizar ela para ter acesso a nossa conexão com o banco de dados em qualquer ponto da nossa aplicação.
Explicada a Database
vamos partir para a Model
:
<?php
namespace core;
use \\core\\Database;
use \\ClanCats\\Hydrahon\\Builder;
use \\ClanCats\\Hydrahon\\Query\\Sql\\FetchableInterface;
class Model
{
protected static $_h;
public function __construct()
{
self::_checkH();
}
public static function _checkH()
{
if (self::$_h == null) {
$connection = Database::getInstance();
self::$_h = new Builder('mysql', function ($query, $queryString, $queryParameters) use ($connection) {
$statement = $connection->prepare($queryString);
$statement->execute($queryParameters);
if ($query instanceof FetchableInterface) {
return $statement->fetchAll(\\PDO::FETCH_ASSOC);
}
});
}
self::$_h = self::$_h->table(self::getTableName());
}
public static function getTableName()
{
$className = explode('\\\\', get_called_class());
$className = end($className);
return strtolower($className) . 's';
}
public static function select($fields = [])
{
self::_checkH();
return self::$_h->select($fields);
}
public static function insert($fields = [])
{
self::_checkH();
return self::$_h->insert($fields);
}
public static function update($fields = [])
{
self::_checkH();
return self::$_h->update($fields);
}
public static function delete()
{
self::_checkH();
return self::$_h->delete();
}
}
Como eu citei no início desta seção, um dos pontos-chave que nortearam o design desta implentação foi que a experiência de utiliza-lo fosse parecida com a experiência de se utilizar o Laravel, e é esse o motivo de ao invés de implementar um Model utilizando um design mais orientado a objetos (talvez utilizando padrões de persistência como o DAO ou o Data Mapper), ele optou por fazer a classe Model um monte de métodos estáticos (que na prática é quase o mesmo que utilizar funções em um código procedural) para que quando ela fosse utilizada, ela lembra-se as [Facades do Laravel.](https://laravel.com/docs/8.x/facades])
Ela possuia os seguintes métodos:
- _checkH
- getTableName
- select
- insert
- update
- delete
O getTableName
é um método interessante pois ele se utiliza de reflection para adivinhar o nome da tabela no banco de dados, a partir do Nome da classe em que ela for chamada, e essa mágica toda é feita pela função get_called_class
(mais informações aqui: https://www.php.net/manual/en/function.get-called-class), utiliza o explode
e o end
para poder pegar só o nome da classe sem que ele venha junto ao seu namespace, e por fim converte ele para minúsculo e concatena com um “s”. Logo se as classes tiverem os nomes User UserRelation, eles iam ser convertidos em users e userrelations.
O outro coração dessa classe é o _checkH
que segue a mesma ideia de tornar a instância do Hydrahon (que foi o query builder utilizado aqui, um query builder é um objeto que monta as queries SQL para gente) um Singleton, porém em vez de só retornar a instância pura, ele retorna ela já definindo o nome da tabela que será utilizada para formar as consultas SQL (o nome vem do getTableName
).
E assim, ele sempre chama o _checkH
para garantir que a instância do Hydrahon vai estar configurada, e cria os métodos select, insert, update e delete como atalhos para os métodos correspondentes no hydrahon.
Agora que entendemos como ela funciona, vamos encerrar com uma observação interessante. Essa classe apesar de ser muito procedural no sentido de só ter métodos estáticos, utiliza uma das características da orientação a objetos que é a herança, assim tornando o código mais reutilizavel, e também respeita a regra para uma boa herança que é a regra de que os objetos devem ter uma relação de “é um”, e é exatamente essa relação que temos aqui, toda classe que herda de Model
é um Model, e pode ser utilizada no lugar dessa classe.
Referências legais
- https://folk.universitetetioslo.no/trygver/themes/mvc/mvc-index.html
- https://folk.universitetetioslo.no/trygver/1979/mvc-2/1979-12-MVC.pdf
- https://wiki.c2.com/?ModelViewControllerHistory
- https://en.wikipedia.org/wiki/Model–view–controller
- https://web.archive.org/web/20120729161926/http://st-www.cs.illinois.edu/users/smarch/st-docs/mvc.html
- https://herbertograca.com/2017/08/17/mvc-and-its-variants/
- https://herbertograca.com/2017/07/28/architectural-styles-vs-architectural-patterns-vs-design-patterns/