O que é o Padrão MVC e Por Que Aprender Sem Framework
MVC (Model-View-Controller) é um padrão arquitetural que separa a aplicação em três camadas: Model (lógica de dados), View (apresentação) e Controller (orquestração). Aprender MVC sem framework é fundamental porque você entenderá os princípios reais que frameworks como Laravel e Symfony implementam. Quando você constrói do zero, não é magia — é código bem organizado que você controla completamente.
Nesta aula, vamos construir uma aplicação simples de gerenciador de tarefas. Você verá exatamente como as camadas se comunicam, como as requisições são roteadas e como os dados fluem pela arquitetura. Isso prepara você para trabalhar com qualquer framework ou até criar seus próprios.
Estrutura Base do Projeto
Organização de Pastas
Vamos começar com uma estrutura clara e profissional:
/projeto
├── /public
│ └── index.php
├── /app
│ ├── /controllers
│ ├── /models
│ ├── /views
│ └── /core
├── /config
│ └── database.php
└── .htaccess
A pasta /public contém apenas o ponto de entrada (index.php). Todo o resto é protegido fora da raiz web. Isso é segurança básica.
Arquivo .htaccess
Primeiro, precisamos redirecionar todas as requisições para index.php:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
</IfModule>
Isso significa: se o arquivo ou pasta não existir, passe a requisição para index.php. É aqui que o roteamento acontece.
O Motor do MVC: Router e Dispatcher
Criando o Router Central
O arquivo /public/index.php é o coração da aplicação:
<?php
// public/index.php
define('ROOT', dirname(__DIR__));
require_once ROOT . '/config/database.php';
class Router {
private $controller = 'Home';
private $method = 'index';
private $params = [];
public function __construct() {
$url = $_GET['url'] ?? 'home/index';
$url = rtrim($url, '/');
$parts = explode('/', $url);
if (!empty($parts[0])) {
$this->controller = ucfirst(strtolower($parts[0]));
}
if (!empty($parts[1])) {
$this->method = strtolower($parts[1]);
}
$this->params = array_slice($parts, 2);
}
public function dispatch() {
$controllerPath = ROOT . '/app/controllers/' . $this->controller . 'Controller.php';
if (!file_exists($controllerPath)) {
die("Controlador não encontrado: {$this->controller}");
}
require_once $controllerPath;
$className = $this->controller . 'Controller';
$controller = new $className();
if (!method_exists($controller, $this->method)) {
die("Método não encontrado: {$this->method}");
}
call_user_func_array([$controller, $this->method], $this->params);
}
}
$router = new Router();
$router->dispatch();
Quando alguém acessa /tarefas/listar, o router extrai tarefas como controller e listar como method. Simples e poderoso.
Construindo as Três Camadas do MVC
Model: Gerenciamento de Dados
O Model é responsável por toda lógica de banco de dados. Aqui vamos criar uma classe base:
<?php
// app/core/Model.php
class Model {
protected $pdo;
protected $table;
public function __construct() {
global $pdo;
$this->pdo = $pdo;
}
public function all() {
$stmt = $this->pdo->query("SELECT * FROM {$this->table}");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function find($id) {
$stmt = $this->pdo->prepare("SELECT * FROM {$this->table} WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
public function create($data) {
$columns = implode(', ', array_keys($data));
$placeholders = implode(', ', array_fill(0, count($data), '?'));
$stmt = $this->pdo->prepare("INSERT INTO {$this->table} ({$columns}) VALUES ({$placeholders})");
return $stmt->execute(array_values($data));
}
public function update($id, $data) {
$set = implode(', ', array_map(fn($k) => "$k = ?", array_keys($data)));
$stmt = $this->pdo->prepare("UPDATE {$this->table} SET {$set} WHERE id = ?");
return $stmt->execute([...array_values($data), $id]);
}
public function delete($id) {
$stmt = $this->pdo->prepare("DELETE FROM {$this->table} WHERE id = ?");
return $stmt->execute([$id]);
}
}
Agora um Model específico para Tarefas:
<?php
// app/models/Tarefa.php
require_once ROOT . '/app/core/Model.php';
class Tarefa extends Model {
protected $table = 'tarefas';
}
Controller: Orquestração da Lógica
O Controller recebe a requisição, chama o Model se necessário e passa dados para a View:
<?php
// app/controllers/TarefasController.php
require_once ROOT . '/app/models/Tarefa.php';
class TarefasController {
private $tarefa;
public function __construct() {
$this->tarefa = new Tarefa();
}
public function listar() {
$tarefas = $this->tarefa->all();
require ROOT . '/app/views/tarefas/listar.php';
}
public function criar() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$this->tarefa->create([
'titulo' => $_POST['titulo'],
'descricao' => $_POST['descricao'] ?? null,
'concluida' => 0
]);
header('Location: /tarefas/listar');
exit;
}
require ROOT . '/app/views/tarefas/criar.php';
}
public function deletar($id) {
$this->tarefa->delete($id);
header('Location: /tarefas/listar');
exit;
}
}
View: Apresentação ao Usuário
As Views são simples arquivos PHP com HTML. Recebem variáveis do Controller:
<!-- app/views/tarefas/listar.php -->
<h1>Minhas Tarefas</h1>
<a href="/tarefas/criar">+ Nova Tarefa</a>
<table border="1">
<tr>
<th>ID</th>
<th>Título</th>
<th>Ações</th>
</tr>
<?php foreach ($tarefas as $tarefa): ?>
<tr>
<td><?= $tarefa['id'] ?></td>
<td><?= htmlspecialchars($tarefa['titulo']) ?></td>
<td>
<a href="/tarefas/deletar/<?= $tarefa['id'] ?>">Deletar</a>
</td>
</tr>
<?php endforeach; ?>
</table>
Note o uso de htmlspecialchars() — segurança contra XSS é essencial desde o início.
Configuração de Banco de Dados
<?php
// config/database.php
$dsn = 'mysql:host=localhost;dbname=meu_app;charset=utf8mb4';
$user = 'root';
$password = '';
try {
$pdo = new PDO($dsn, $user, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die('Erro na conexão: ' . $e->getMessage());
}
Conclusão
Você aprendeu que MVC separa responsabilidades: Models lidam com dados, Controllers com lógica de negócio e Views com apresentação. O Router é o maestro que mapeia URLs para Controllers e Methods — sem ele, não há orquestração. E seguir padrões desde o início (como usar htmlspecialchars() e preparar statements) evita débito técnico e vulnerabilidades no futuro.
Pratique expandindo este projeto: adicione validação, autenticação e cache. Quando você domina MVC puro, frameworks se tornam apenas ferramentas, não mistério.