PHP

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

9 min de leitura

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

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

<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">&lt;IfModule mod_rewrite.c&gt;

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-f

RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]

&lt;/IfModule&gt;</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">&lt;?php

// public/index.php

define(&#039;ROOT&#039;, dirname(__DIR__));

require_once ROOT . &#039;/config/database.php&#039;;

class Router {

private $controller = &#039;Home&#039;;

private $method = &#039;index&#039;;

private $params = [];

public function __construct() {

$url = $_GET[&#039;url&#039;] ?? &#039;home/index&#039;;

$url = rtrim($url, &#039;/&#039;);

$parts = explode(&#039;/&#039;, $url);

if (!empty($parts[0])) {

$this-&gt;controller = ucfirst(strtolower($parts[0]));

}

if (!empty($parts[1])) {

$this-&gt;method = strtolower($parts[1]);

}

$this-&gt;params = array_slice($parts, 2);

}

public function dispatch() {

$controllerPath = ROOT . &#039;/app/controllers/&#039; . $this-&gt;controller . &#039;Controller.php&#039;;

if (!file_exists($controllerPath)) {

die(&quot;Controlador não encontrado: {$this-&gt;controller}&quot;);

}

require_once $controllerPath;

$className = $this-&gt;controller . &#039;Controller&#039;;

$controller = new $className();

if (!method_exists($controller, $this-&gt;method)) {

die(&quot;Método não encontrado: {$this-&gt;method}&quot;);

}

call_user_func_array([$controller, $this-&gt;method], $this-&gt;params);

}

}

$router = new Router();

$router-&gt;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">&lt;?php

// app/core/Model.php

class Model {

protected $pdo;

protected $table;

public function __construct() {

global $pdo;

$this-&gt;pdo = $pdo;

}

public function all() {

$stmt = $this-&gt;pdo-&gt;query(&quot;SELECT * FROM {$this-&gt;table}&quot;);

return $stmt-&gt;fetchAll(PDO::FETCH_ASSOC);

}

public function find($id) {

$stmt = $this-&gt;pdo-&gt;prepare(&quot;SELECT * FROM {$this-&gt;table} WHERE id = ?&quot;);

$stmt-&gt;execute([$id]);

return $stmt-&gt;fetch(PDO::FETCH_ASSOC);

}

public function create($data) {

$columns = implode(&#039;, &#039;, array_keys($data));

$placeholders = implode(&#039;, &#039;, array_fill(0, count($data), &#039;?&#039;));

$stmt = $this-&gt;pdo-&gt;prepare(&quot;INSERT INTO {$this-&gt;table} ({$columns}) VALUES ({$placeholders})&quot;);

return $stmt-&gt;execute(array_values($data));

}

public function update($id, $data) {

$set = implode(&#039;, &#039;, array_map(fn($k) =&gt; &quot;$k = ?&quot;, array_keys($data)));

$stmt = $this-&gt;pdo-&gt;prepare(&quot;UPDATE {$this-&gt;table} SET {$set} WHERE id = ?&quot;);

return $stmt-&gt;execute([...array_values($data), $id]);

}

public function delete($id) {

$stmt = $this-&gt;pdo-&gt;prepare(&quot;DELETE FROM {$this-&gt;table} WHERE id = ?&quot;);

return $stmt-&gt;execute([$id]);

}

}</code></pre>

<p>Agora um Model específico para Tarefas:</p>

<pre><code class="language-php">&lt;?php

// app/models/Tarefa.php

require_once ROOT . &#039;/app/core/Model.php&#039;;

class Tarefa extends Model {

protected $table = &#039;tarefas&#039;;

}</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">&lt;?php

// app/controllers/TarefasController.php

require_once ROOT . &#039;/app/models/Tarefa.php&#039;;

class TarefasController {

private $tarefa;

public function __construct() {

$this-&gt;tarefa = new Tarefa();

}

public function listar() {

$tarefas = $this-&gt;tarefa-&gt;all();

require ROOT . &#039;/app/views/tarefas/listar.php&#039;;

}

public function criar() {

if ($_SERVER[&#039;REQUEST_METHOD&#039;] === &#039;POST&#039;) {

$this-&gt;tarefa-&gt;create([

&#039;titulo&#039; =&gt; $_POST[&#039;titulo&#039;],

&#039;descricao&#039; =&gt; $_POST[&#039;descricao&#039;] ?? null,

&#039;concluida&#039; =&gt; 0

]);

header(&#039;Location: /tarefas/listar&#039;);

exit;

}

require ROOT . &#039;/app/views/tarefas/criar.php&#039;;

}

public function deletar($id) {

$this-&gt;tarefa-&gt;delete($id);

header(&#039;Location: /tarefas/listar&#039;);

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">&lt;!-- app/views/tarefas/listar.php --&gt;

&lt;h1&gt;Minhas Tarefas&lt;/h1&gt;

&lt;a href=&quot;/tarefas/criar&quot;&gt;+ Nova Tarefa&lt;/a&gt;

&lt;table border=&quot;1&quot;&gt;

&lt;tr&gt;

&lt;th&gt;ID&lt;/th&gt;

&lt;th&gt;Título&lt;/th&gt;

&lt;th&gt;Ações&lt;/th&gt;

&lt;/tr&gt;

&lt;?php foreach ($tarefas as $tarefa): ?&gt;

&lt;tr&gt;

&lt;td&gt;&lt;?= $tarefa[&#039;id&#039;] ?&gt;&lt;/td&gt;

&lt;td&gt;&lt;?= htmlspecialchars($tarefa[&#039;titulo&#039;]) ?&gt;&lt;/td&gt;

&lt;td&gt;

&lt;a href=&quot;/tarefas/deletar/&lt;?= $tarefa[&#039;id&#039;] ?&gt;&quot;&gt;Deletar&lt;/a&gt;

&lt;/td&gt;

&lt;/tr&gt;

&lt;?php endforeach; ?&gt;

&lt;/table&gt;</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">&lt;?php

// config/database.php

$dsn = &#039;mysql:host=localhost;dbname=meu_app;charset=utf8mb4&#039;;

$user = &#039;root&#039;;

$password = &#039;&#039;;

try {

$pdo = new PDO($dsn, $user, $password);

$pdo-&gt;setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

} catch (PDOException $e) {

die(&#039;Erro na conexão: &#039; . $e-&gt;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>

Comentários

Mais em PHP

Boas Práticas de Logging Profissional com Monolog em PHP para Times Ágeis
Boas Práticas de Logging Profissional com Monolog em PHP para Times Ágeis

Por que Logging Profissional Importa Logging é frequentemente negligenciado p...

O que Todo Dev Deve Saber sobre Camada Model: Repositórios e Mapeamento de Dados
O que Todo Dev Deve Saber sobre Camada Model: Repositórios e Mapeamento de Dados

Compreendendo a Camada Model: Fundamentos A camada Model é o coração de qualq...

Guia Completo de Variáveis, Tipos de Dados e Operadores em PHP
Guia Completo de Variáveis, Tipos de Dados e Operadores em PHP

Variáveis em PHP Uma variável é um espaço de memória que armazena um valor. E...