<h2>O que é o Padrão MVC e Por Que Aprender Sem Framework</h2>
<p>MVC (Model-View-Controller) é um padrão arquitetural que separa a aplicação em três camadas: <strong>Model</strong> (lógica de dados), <strong>View</strong> (apresentação) e <strong>Controller</strong> (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.</p>
<p>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.</p>
<h2>Estrutura Base do Projeto</h2>
<h3>Organização de Pastas</h3>
<p>Vamos começar com uma estrutura clara e profissional:</p>
<pre><code>/projeto
├── /public
│ └── index.php
├── /app
│ ├── /controllers
│ ├── /models
│ ├── /views
│ └── /core
├── /config
│ └── database.php
└── .htaccess</code></pre>
<p>A pasta <code>/public</code> contém apenas o ponto de entrada (<code>index.php</code>). Todo o resto é protegido fora da raiz web. Isso é segurança básica.</p>
<h3>Arquivo .htaccess</h3>
<p>Primeiro, precisamos redirecionar todas as requisições para <code>index.php</code>:</p>
<pre><code class="language-apache"><IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
</IfModule></code></pre>
<p>Isso significa: se o arquivo ou pasta não existir, passe a requisição para <code>index.php</code>. É aqui que o roteamento acontece.</p>
<h2>O Motor do MVC: Router e Dispatcher</h2>
<h3>Criando o Router Central</h3>
<p>O arquivo <code>/public/index.php</code> é o coração da aplicação:</p>
<pre><code class="language-php"><?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();</code></pre>
<p>Quando alguém acessa <code>/tarefas/listar</code>, o router extrai <code>tarefas</code> como controller e <code>listar</code> como method. Simples e poderoso.</p>
<h2>Construindo as Três Camadas do MVC</h2>
<h3>Model: Gerenciamento de Dados</h3>
<p>O Model é responsável por toda lógica de banco de dados. Aqui vamos criar uma classe base:</p>
<pre><code class="language-php"><?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]);
}
}</code></pre>
<p>Agora um Model específico para Tarefas:</p>
<pre><code class="language-php"><?php
// app/models/Tarefa.php
require_once ROOT . '/app/core/Model.php';
class Tarefa extends Model {
protected $table = 'tarefas';
}</code></pre>
<h3>Controller: Orquestração da Lógica</h3>
<p>O Controller recebe a requisição, chama o Model se necessário e passa dados para a View:</p>
<pre><code class="language-php"><?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;
}
}</code></pre>
<h3>View: Apresentação ao Usuário</h3>
<p>As Views são simples arquivos PHP com HTML. Recebem variáveis do Controller:</p>
<pre><code class="language-php"><!-- 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></code></pre>
<p>Note o uso de <code>htmlspecialchars()</code> — segurança contra XSS é essencial desde o início.</p>
<h3>Configuração de Banco de Dados</h3>
<pre><code class="language-php"><?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());
}</code></pre>
<h2>Conclusão</h2>
<p>Você aprendeu que <strong>MVC separa responsabilidades</strong>: Models lidam com dados, Controllers com lógica de negócio e Views com apresentação. <strong>O Router é o maestro</strong> que mapeia URLs para Controllers e Methods — sem ele, não há orquestração. E <strong>seguir padrões desde o início</strong> (como usar <code>htmlspecialchars()</code> e preparar statements) evita débito técnico e vulnerabilidades no futuro.</p>
<p>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.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://www.php.net/manual/en/pdo.prepared-statements.php" target="_blank" rel="noopener noreferrer">PHP: Prepared Statements - Manual Oficial</a></li>
<li><a href="https://developer.mozilla.org/pt-BR/docs/Glossary/MVC" target="_blank" rel="noopener noreferrer">MDN: Model-View-Controller (MVC)</a></li>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html" target="_blank" rel="noopener noreferrer">OWASP: XSS Prevention Cheat Sheet</a></li>
<li><a href="https://www.php-fig.org/" target="_blank" rel="noopener noreferrer">PHP Best Practices - PHP FIG</a></li>
<li><a href="https://laravel.com/docs" target="_blank" rel="noopener noreferrer">Clean Code in PHP - Laravel Documentation</a></li>
</ul>