Dominando Padrão MVC em PHP: Construindo do Zero sem Framework em Projetos Reais Já leu

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: A pasta contém apenas o ponto de entrada ( ). Todo o resto é protegido fora da raiz web. Isso é segurança básica. Arquivo .htaccess Primeiro, precisamos redirecionar todas as requisições

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.

Referências


Artigos relacionados