O padrão MVC
Esta aula apresenta o padrão arquitetural MVC (Model-View-Controller), explicando seus três componentes principais, a separação de responsabilidades que ele promove, os benefícios de sua adoção e um exemplo conceitual em PHP para ilustrar sua implementação básica.
O padrão MVC (Model-View-Controller) é uma das arquiteturas mais influentes e amplamente adotadas no desenvolvimento de software, especialmente em aplicações web. Ele organiza o código em três componentes distintos — Model, View e Controller — com o objetivo de separar a lógica de negócios da apresentação e da interação do usuário. Essa separação facilita a manutenção, a escalabilidade e o trabalho em equipe, pois cada componente tem uma responsabilidade bem definida.
Nesta aula, vamos explorar em profundidade cada um desses componentes, entender como eles se comunicam, por que essa separação é importante e como aplicar o padrão em projetos PHP. Ao final, você terá uma base sólida para implementar MVC em seus próprios projetos ou para compreender frameworks populares como Laravel, Symfony e CodeIgniter, que seguem essa arquitetura.
Model, View, Controller
O padrão MVC divide a aplicação em três partes interconectadas:
- Model: responsável pelos dados e pela lógica de negócio. Ele gerencia a obtenção, validação, persistência e regras de negócio da aplicação. Em PHP, o Model frequentemente interage com bancos de dados via PDO ou ORMs, mas pode ser qualquer fonte de dados (APIs, arquivos, etc.).
- View: responsável pela apresentação. Ela exibe os dados ao usuário, geralmente em HTML, mas também pode gerar JSON, XML, PDF, etc. A View não deve conter lógica de negócio; apenas recebe dados e os formata.
- Controller: atua como intermediário entre o Model e a View. Ele recebe requisições do usuário (via rotas), processa a entrada, decide qual Model acionar e qual View renderizar. Em aplicações web, o Controller lida com parâmetros de URL, formulários e sessões.
Vamos ver um exemplo simples de cada componente em PHP.
// Model - User.php
class User {
private $db;
public function __construct(PDO $db) {
$this->db = $db;
}
public function findById(int $id): ?array {
$stmt = $this->db->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
return $user ?: null;
}
}
// View - user_view.php
// Suponha que $user seja passado pelo controller
?>
<!DOCTYPE html>
<html>
<head>
<title>Perfil do Usuário</title>
</head>
<body>
<h1><?= htmlspecialchars($user['name']) ?></h1>
<p>Email: <?= htmlspecialchars($user['email']) ?></p>
</body>
</html>
// Controller - UserController.php
class UserController {
public function show(int $id) {
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$model = new User($db);
$user = $model->findById($id);
if (!$user) {
http_response_code(404);
echo 'Usuário não encontrado';
return;
}
// Passa dados para a view
include 'user_view.php';
}
}
No exemplo, o Controller instancia o Model, obtém os dados e os disponibiliza para a View, que apenas exibe. O Model não sabe nada sobre HTML, e a View não consulta o banco diretamente.
Separação de responsabilidades
A separação de responsabilidades é o princípio fundamental do MVC. Cada componente tem um papel único e isolado, o que reduz o acoplamento e aumenta a coesão. Isso significa que alterações em um componente têm impacto mínimo nos outros, desde que as interfaces sejam respeitadas.
Por exemplo, se você deseja mudar a forma como os dados são armazenados (de MySQL para PostgreSQL), você pode alterar o Model sem tocar na View ou Controller. Da mesma forma, se quiser reformular a interface do usuário, pode alterar a View sem afetar a lógica de negócio.
Em PHP, essa separação pode ser implementada de várias formas:
- Estrutura de pastas: organize os arquivos em diretórios como
app/Models,app/Views,app/Controllers. - Uso de interfaces: defina contratos claros para os Models, como métodos
find(),save(), etc. - Injeção de dependência: passe dependências (como o objeto de banco) para os construtores, em vez de instanciá-las dentro dos componentes.
A seguir, um exemplo de estrutura de pastas típica:
projeto/
├── app/
│ ├── Controllers/
│ │ └── UserController.php
│ ├── Models/
│ │ └── User.php
│ └── Views/
│ └── user/
│ └── show.php
├── public/
│ └── index.php
└── config/
└── database.php
Essa organização facilita a navegação e a manutenção do código.
Por que usar
Adotar o padrão MVC traz inúmeros benefícios para o desenvolvimento de software, especialmente em projetos de médio e grande porte.
- Manutenibilidade: como cada componente tem uma responsabilidade única, corrigir bugs ou adicionar funcionalidades tende a ser mais simples e menos arriscado.
- Escalabilidade: a separação permite que diferentes desenvolvedores trabalhem em paralelo em Model, View e Controller sem conflitos constantes.
- Reutilização de código: Models podem ser reutilizados em diferentes controllers e views; Views podem ser reutilizadas com diferentes dados.
- Testabilidade: é mais fácil escrever testes unitários para Models e Controllers, pois eles não dependem da interface gráfica.
- Padronização: frameworks como Laravel e Symfony seguem MVC, então aprender o padrão facilita a adoção dessas ferramentas.
No entanto, é importante notar que MVC não é uma bala de prata. Em aplicações muito simples, a estrutura pode ser considerada excessiva. Mas, para a maioria das aplicações web, os benefícios superam a complexidade inicial.
Um exemplo prático: considere um sistema de blog. Sem MVC, você poderia ter um único arquivo PHP que mistura consultas SQL, HTML e lógica de formulário. Com MVC, você separa:
- Model Post: responsável por buscar posts no banco.
- PostController: recebe a requisição (ex.: /post/123), chama o Model e decide qual View renderizar.
- View post.php: exibe o post formatado.
Isso torna o código mais organizado e compreensível.
Exemplo conceitual
Vamos construir um exemplo conceitual mais completo de uma aplicação simples de cadastro de usuários usando MVC. O objetivo é mostrar o fluxo de uma requisição.
Primeiro, o front controller (index.php) que roteia as requisições para o controller adequado. Em um framework real, isso é feito por rotas, mas aqui faremos de forma manual.
// public/index.php
require_once '../app/Controllers/UserController.php';
// Roteamento simples
$uri = $_GET['url'] ?? '';
$parts = explode('/', trim($uri, '/'));
$controllerName = ucfirst($parts[0] ?? 'home') . 'Controller';
$method = $parts[1] ?? 'index';
$params = array_slice($parts, 2);
if (class_exists($controllerName)) {
$controller = new $controllerName();
if (method_exists($controller, $method)) {
call_user_func_array([$controller, $method], $params);
} else {
http_response_code(404);
echo 'Método não encontrado';
}
} else {
http_response_code(404);
echo 'Controller não encontrado';
}
O Controller de usuários pode ter métodos para listar, criar e exibir usuários.
// app/Controllers/UserController.php
require_once '../app/Models/User.php';
class UserController {
private $model;
public function __construct() {
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$this->model = new User($db);
}
public function index() {
$users = $this->model->all();
include '../app/Views/users/index.php';
}
public function show($id) {
$user = $this->model->findById($id);
if (!$user) {
http_response_code(404);
echo 'Usuário não encontrado';
return;
}
include '../app/Views/users/show.php';
}
public function create() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'];
$email = $_POST['email'];
$this->model->create($name, $email);
header('Location: /index.php?url=user/index');
exit;
}
include '../app/Views/users/create.php';
}
}
O Model agora tem métodos para listar, buscar e criar.
// app/Models/User.php
class User {
private $db;
public function __construct(PDO $db) {
$this->db = $db;
}
public function all(): array {
$stmt = $this->db->query('SELECT * FROM users');
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function findById(int $id): ?array {
$stmt = $this->db->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
return $user ?: null;
}
public function create(string $name, string $email): bool {
$stmt = $this->db->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
return $stmt->execute([$name, $email]);
}
}
E as Views são simples:
// app/Views/users/index.php
?>
<ul>
<?php foreach ($users as $user): ?>
<li><a href="/index.php?url=user/show/<?= $user['id'] ?>"><?= htmlspecialchars($user['name']) ?></a></li>
<?php endforeach; ?>
</ul>
// app/Views/users/show.php
?>
<h1><?= htmlspecialchars($user['name']) ?></h1>
<p>Email: <?= htmlspecialchars($user['email']) ?></p>
Esse exemplo ilustra o fluxo completo: o usuário acessa uma URL, o front controller identifica o controller e método, o controller usa o Model para obter dados e, por fim, inclui a View que renderiza o HTML.
Observe que o Controller não contém SQL nem HTML; ele apenas orquestra. O Model é a única parte que fala com o banco, e a View é a única que gera saída para o navegador.
Boas práticas
- Mantenha os Controllers finos: eles devem apenas receber requisições, delegar trabalho e retornar respostas. Lógica de negócio complexa deve ficar no Model ou em serviços.
- Use namespaces e autoloading: em projetos maiores, utilize PSR-4 e Composer para carregar classes automaticamente.
- Escape a saída: sempre use
htmlspecialchars()nas Views para evitar XSS. - Não use SQL na View: a View deve receber dados já prontos.
- Considere usar um framework: frameworks como Laravel implementam MVC de forma robusta, com rotas, middlewares e injeção de dependência.
Referências
- Documentação Oficial do PHP
- MDN - MVC
- Laravel - Estrutura de Diretórios
- Symfony - Controllers
- W3Schools - PHP MVC
- Codecademy - O que é MVC?
Exercícios
- Explique com suas palavras o papel de cada componente do MVC (Model, View, Controller) em uma aplicação web PHP.
- Qual é a principal vantagem da separação de responsabilidades no MVC? Dê um exemplo de uma mudança que seria facilitada por essa separação.
- Escreva um código PHP simples que demonstre a comunicação entre Controller e Model, sem incluir a View.
- Identifique um problema comum que ocorre quando não se usa MVC (por exemplo, código espaguete) e descreva como o MVC resolveria esse problema.
- Crie uma estrutura de pastas para um projeto MVC em PHP, incluindo pastas para Models, Views, Controllers e o front controller (index.php).
// Model
class User {
public function getById($id) {
// Simula busca no banco
return ['id' => $id, 'name' => 'João'];
}
}
// Controller
class UserController {
public function show($id) {
$model = new User();
$user = $model->getById($id);
// Aqui você passaria $user para a View
echo 'Dados do usuário: ' . $user['name'];
}
}
// Uso
$controller = new UserController();
$controller->show(1);
meu-projeto/
├── app/
│ ├── Models/
│ │ └── User.php
│ ├── Views/
│ │ └── user/
│ │ ├── index.php
│ │ └── show.php
│ └── Controllers/
│ └── UserController.php
├── public/
│ └── index.php
└── config/
└── database.php