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

Exercícios

  1. Explique com suas palavras o papel de cada componente do MVC (Model, View, Controller) em uma aplicação web PHP.
  2. 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.
  3. Escreva um código PHP simples que demonstre a comunicação entre Controller e Model, sem incluir a View.
  4. 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.
  5. Crie uma estrutura de pastas para um projeto MVC em PHP, incluindo pastas para Models, Views, Controllers e o front controller (index.php).

✓ Resposta: O Model é responsável pelos dados e regras de negócio, gerenciando a persistência e validação. A View é responsável pela apresentação, exibindo os dados ao usuário em HTML ou outros formatos. O Controller recebe as requisições do usuário, processa a entrada, interage com o Model e seleciona a View apropriada para renderizar a resposta.

✓ Resposta: A principal vantagem é a manutenibilidade, pois alterações em um componente têm impacto mínimo nos outros. Por exemplo, se você precisar mudar o banco de dados de MySQL para PostgreSQL, você pode alterar apenas o Model, sem tocar na View ou Controller, desde que as interfaces permaneçam as mesmas.

✓ Resposta:
// 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);

✓ Resposta: Sem MVC, o código pode se tornar "espaguete", com HTML, SQL e lógica misturados em um único arquivo. Isso dificulta a manutenção, pois qualquer alteração exige revisar todo o arquivo. O MVC resolve isso separando as responsabilidades: o Model cuida dos dados, a View da apresentação e o Controller da lógica de controle, tornando o código mais organizado e fácil de manter.

✓ Resposta:
meu-projeto/
├── app/
│   ├── Models/
│   │   └── User.php
│   ├── Views/
│   │   └── user/
│   │       ├── index.php
│   │       └── show.php
│   └── Controllers/
│       └── UserController.php
├── public/
│   └── index.php
└── config/
    └── database.php