DEV Community

Davi Koehler
Davi Koehler

Posted on

O que é o MVC e para que ele serve?

Esse é o meu primeiro artigo aqui e decidi trazer algo de relevância para quem está aprendendo a linguagem PHP e que provavelmente já escutou falar, porém nunca colocou em prática ou nunca sequer chegou a conhecer como funciona a estrutura MVC.

Para entendermos essa estrutura, iremos analisar alguns dos seguintes tópicos:

  • O que é o MVC?
  • O que ele resolve?
  • Fluxo do MVC
  • Vantagens e Desvantagens
  • Entendendo suas camadas
  • Exemplo Prático

O que é o MVC?
O MVC é um padrão de arquitetura de software que foi criado no final da década de 70 por Trygve Reenskaug, e foi desenvolvido para a linguagem Smalltalk, que foi uma linguagem criada visando o aprendizado por parte de alunos e profissionais de TI da Orientação a Objetos.

Por ser um padrão, sua principal função é resolver um problema ao qual já existe e que foi pensado em uma solução. Então a pergunta é:

O que esse padrão resolve?
O MVC é um padrão dividido em 3 camadas:

  • Model
  • View
  • Controller

Essas 3 camadas (que explicaremos detalhadamente mais para frente) basicamente são responsáveis por separar a sua regra de negócio da interface do usuário, te ajuda a fazer maior reuso do seu código, facilita na implementação de testes automatizados e entre algumas outras vantagens que citarei depois.

Entendendo o fluxo do MVC
Nós já sabemos que o MVC é formado pelas camadas de Models, Views e Controllers, então vamos imaginar que o usuário está preenchendo um formulário de cadastro. Vamos ver e explicar como se comporta esse fluxo logo abaixo:

Image description

  • Usuário acessa o formulário através da View
  • Preenche o formulário e clica em Salvar
  • Quando ele clicar em salvar, o sistema vai buscar o Controller
  • O seu Controller chama a sua Model
  • A sua Model entra em contato com o banco de dados para salvar os dados enviados pelo formulário
  • Baseado no retorno do banco de dados, a sua Model retorna ao Controller a resposta do banco
  • O seu Controller passa para a View o seu retorno
  • A sua View vai exibir uma mensagem de sucesso ou de erro referente ao cadastro feito pelo usuário

Agora que entendemos o fluxo que o MVC faz, vamos ver algumas vantagens e desvantagens de se utilizar o padrão MVC.

Vantagens:

  • Torna sua aplicação escalável, permitindo o crescimento dela em um futuro
  • Seu código fica bem escrito
  • Divide em camadas as responsabilidades
  • Aumenta a sua produtividade e com isso reduz o tempo de desenvolvimento
  • Facilita a implementação de Testes Automatizados

Desvantagens:

  • Como seu código fica mais complexo, você precisa ter um maior conhecimento técnico para saber o que está fazendo
  • Pode trazer uma complexidade desnecessária ao seu código, dependendo do projeto

Agora que entendemos mais dos conceitos, o fluxo, algumas vantagens e desvantagens de se utilizar esse padrão, começaremos a destrinchar as suas camadas e suas responsabilidades, para que você consiga entender como um todo o que é e para que serve esse padrão.

Model
A camada Model, ou modelo, em português, tem como principal responsabilidade armazenar a sua regra de negócio, a comunicação com o banco de dados.
Para o pessoal mais iniciante, vale explicar também o que é a regra de negócio. Simplificando, é como sua aplicação tem que funcionar, é a regra que define como ela tem que agir em determinadas situações. Vamos pegar um sistema financeiro como exemplo, na sua camada de modelo, ela deve ter todas as classes e métodos que sejam responsáveis por fazer transferências, contas de saldos, todo o seu CRUD de lançamentos, ou seja, tudo que for mais sensível e que não faz sentido nenhum ter na interface do usuário.

View
A camada View, ou visualização, em português, tem como principal responsabilidade trazer a interface para o usuário, com os dados já prontos, sem que seja da responsabilidade dela fazer essa busca dentro do banco de dados.

Controller
A camada Controller, ou controlador, em protuguês, tem como principal responsabilidade fazer a ponte entre a sua camada View e a sua camada Model, ou seja, ela apenas serve para chamar uma Model caso precise, e mandar para a View aquilo que está vindo da Model. O controller tem apenas essa função, então é bom evitar criar regras de negócio dentro do seu Controller, mantendo ele mais enxuto e mantendo assim a sua responsabilidade.

Como fica isso na prática?
Bom, vimos que o MVC ele te ajuda a organizar o seu código e separa as responsabilidades em camadas, mas como fica isso na prática?
Irei demonstrar através de um exemplo bem simples, como a parte de edição em um CRUD simples de produtos.

Para esse exemplo, utilizei PHP, MySQL, HTML, Tailwind para fazer a estilização, além do Javascript para poder criar uma função básica de formatação de moedas.

Vamos começar mostrando a parte procedural:

<?php
//edit.php
$pdo = new PDO("mysql:host=localhost;dbname=my-products", "root", "");
if (isset($_GET['id']) && $_GET['id'] !== '') {
    $sqlGetProduct = "SELECT * FROM products WHERE id = :id";
    $getProduct = $pdo->prepare($sqlGetProduct);
    $getProduct->bindValue(':id', $_GET['id']);
    $getProduct->execute();

    $product = '';
    if ($getProduct->rowCount() >= 1) {
        $product = $getProduct->fetch(PDO::FETCH_OBJ);
    }
}
if ($_POST) {

    try {
        $amount = floatval(str_replace('.', '', str_replace(',', '', $_POST['amount'])));
        $timezone = new DateTimeZone('America/Sao_Paulo');
        $actualDate = new DateTime('now', $timezone);

        $queryUpdate = "UPDATE products SET status = :s, name = :n, description = :d, amount = :a, quantity = :q, updated_at = :u WHERE id = :i";
        $stmt = $pdo->prepare($queryUpdate);
        $stmt->bindValue(':s', $_POST['status']);
        $stmt->bindValue(':n', $_POST['name']);
        $stmt->bindValue(':d', $_POST['description']);
        $stmt->bindValue(':a', ($amount / 100));
        $stmt->bindValue(':q', $_POST['quantity']);
        $stmt->bindValue(':u', $actualDate->format('Y-m-d H:i:s'));
        $stmt->bindValue(':i', $_GET['id']);
        $stmt->execute();

        if ($stmt->rowCount() >= 1) {
            header("Location: http://localhost:85/my-products/pages/edit.php?id={$_GET['id']}");
        }
    } catch (PDOException $e) {
        throw new Exception($e->getMessage());
    }
}
?>
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>CRUD Products</title>
    <link rel="stylesheet" href="./../css/style.css">
</head>

<body class="h-screen w-full flex bg-slate-300">
    <div class="h-full w-1/5 flex flex-col items-center justify-center">
        <div class="h-2/4 w-3/4 bg-white rounded-lg p-6">
            <ul class="h-full flex flex-col gap-2">
                <li class="w-100">
                    <a href="/my-products/" class="w-100 flex items-center gap-5 p-4 transition-all hover:bg-slate-900 hover:rounded-md hover:text-white">
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 21v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21m0 0h4.5V3.545M12.75 21h7.5V10.75M2.25 21h1.5m18 0h-18M2.25 9l4.5-1.636M18.75 3l-1.5.545m0 6.205l3 1m1.5.5l-1.5-.5M6.75 7.364V3h-3v18m3-13.636l10.5-3.819" />
                        </svg> Início
                    </a>
                </li>
                <li class="w-100">
                    <a href="/my-products/pages/products.php" class="w-100 flex items-center gap-5 p-4 bg-slate-900 rounded-md text-white transition-all hover:bg-slate-900 hover:rounded-md hover:text-white">
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z" />
                            <path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6z" />
                        </svg> Produtos
                    </a>
                </li>
            </ul>
        </div>
    </div>
    <main class="h-full w-4/5 flex flex-col justify-center items-center">
        <section class="h-3/4 w-4/5 flex flex-col gap-8 bg-white rounded-lg p-6">
            <h1 class="h-1/6 w-full p-2 text-center text-slate-900 text-4xl font-bold ">Cadastro de Produto</h1>
            <form class="h-3/4 flex flex-col gap-5 p-4" method="POST">
                <div class="w-full flex gap-4">
                    <div class="w-1/2 flex flex-col gap-2">
                        <label class="font-bold text-slate-900">Nome do Produto</label>
                        <input required class="w-full p-2 rounded outline-none border border-slate-500" type="text" name="name" id="name" placeholder="Ex.: Celular Samsung Galaxy S20" value="<?= $product->name ?>">
                    </div>
                    <div class="w-1/2 flex flex-col gap-2">
                        <label class="font-bold text-slate-900">Status do Produto</label>
                        <select required name="status" class="w-full p-2 rounded outline-none border border-slate-500">
                            <option value="1" <?php if ($product->status === 1) echo "selected" ?>>Ativo</option>
                            <option value="0" <?php if ($product->status === 0) echo "selected" ?>>Inativo</option>
                        </select>
                    </div>
                </div>
                <div class="w-full flex flex-col gap-2">
                    <label class="font-bold text-slate-900">Descrição do Produto</label>
                    <textarea required class="w-full p-2 resize-none rounded outline-none border border-slate-500" name="description" id="description" cols="30" rows="5"><?= $product->description ?></textarea>
                </div>
                <div class="w-full flex gap-4">
                    <div class="w-1/3 flex flex-col gap-2">
                        <label class="w-full font-bold text-slate-900">Valor do Produto</label>
                        <input required value="<?= number_format($product->amount, 2, ',', '.') ?>" class="w-1/2 p-2 rounded outline-none border border-slate-500" type="text" name="amount" id="amount" placeholder="Ex.: 19,20" onkeyup="formatarMoeda(this);">
                    </div>
                    <div class="w-1/3 flex flex-col gap-2">
                        <label class="w-full font-bold text-slate-900">Quantidade</label>
                        <input required value="<?= $product->quantity ?>" class="w-1/2 p-2 rounded outline-none border border-slate-500" type="number" name="quantity" id="quantity">
                    </div>
                </div>
                <div class=" w-full h-1/4 flex justify-end items-end gap-4">
                    <button class="w-1/6 p-2 rounded bg-emerald-400 font-semibold text-white text-lg">Salvar</button>
                    <a href="/my-products/" class="w-1/6 text-center p-2 rounded border border-red-400 font-semibold text-red-400 text-lg transition-all hover:bg-red-400 hover:text-white">Retornar</a>
                </div>
            </form>
        </section>
    </main>
    <script>
        function formatarMoeda(i) {
            var v = i.value.replace(/\D/g, '');
            v = (v / 100).toFixed(2) + '';
            v = v.replace(".", ",");
            v = v.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1.');
            i.value = v
        }
    </script>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

Provavelmente, você já viu tutoriais ou até mesmo já escreveu códigos assim, mas perceba que você tem muita coisa misturada, e isso, a longo prazo pode te trazer mais problemas no seu sistema. Perceba que no seu código você tem uma consulta com o banco de dados buscando o produto e quando tem uma requisição POST para salvar, ele faz um update no seu banco de dados. Lembrando que por ser uma coisa mais simples, não me atentei muito as validações, então provavelmente, no modelo procedural, o seu código estaria um pouco maior por conta das validações.
Vamos supor que você queira depois fazer uma consulta em um determinado produto, muito provavelmente você vai precisar reescrever esse SQL de novo. Com o MVC, você verá que podemos ter um reaproveitamento de código.

Começamos com a parte da View, que se chama EditProduct

<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>CRUD Products</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>

<body class="h-screen w-full flex bg-slate-300">
    <div class="h-full w-1/5 flex flex-col items-center justify-center">
        <div class="h-2/4 w-3/4 bg-white rounded-lg p-6">
            <ul class="h-full flex flex-col gap-2">
                <li class="w-100">
                    <a href="/my-products/" class="w-100 flex items-center gap-5 p-4 transition-all hover:bg-slate-900 hover:rounded-md hover:text-white">
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 21v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21m0 0h4.5V3.545M12.75 21h7.5V10.75M2.25 21h1.5m18 0h-18M2.25 9l4.5-1.636M18.75 3l-1.5.545m0 6.205l3 1m1.5.5l-1.5-.5M6.75 7.364V3h-3v18m3-13.636l10.5-3.819" />
                        </svg> Início
                    </a>
                </li>
                <li class="w-100">
                    <a href="/my-products/pages/products.php" class="w-100 flex items-center gap-5 p-4 bg-slate-900 rounded-md text-white transition-all hover:bg-slate-900 hover:rounded-md hover:text-white">
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z" />
                            <path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6z" />
                        </svg> Produtos
                    </a>
                </li>
            </ul>
        </div>
    </div>
    <main class="h-full w-4/5 flex flex-col justify-center items-center">
        <section class="h-3/4 w-4/5 flex flex-col gap-8 bg-white rounded-lg p-6">
            <h1 class="h-1/6 w-full p-2 text-center text-slate-900 text-4xl font-bold ">Cadastro de Produto</h1>
            <form class="h-3/4 flex flex-col gap-5 p-4" action="/product/<?= $data->id ?>" method="POST">
                <input type="hidden" name="_method" value="PUT" />
                <div class="w-full flex gap-4">
                    <div class="w-1/2 flex flex-col gap-2">
                        <label class="font-bold text-slate-900">Nome do Produto</label>
                        <input required class="w-full p-2 rounded outline-none border border-slate-500" type="text" name="name" id="name" placeholder="Ex.: Celular Samsung Galaxy S20" value="<?= $data->name ?>">
                    </div>
                    <div class="w-1/2 flex flex-col gap-2">
                        <label class="font-bold text-slate-900">Status do Produto</label>
                        <select required name="status" class="w-full p-2 rounded outline-none border border-slate-500">
                            <option value="1" <?php if ($data->status === 1) echo "selected" ?>>Ativo</option>
                            <option value="0" <?php if ($data->status === 0) echo "selected" ?>>Inativo</option>
                        </select>
                    </div>
                </div>
                <div class="w-full flex flex-col gap-2">
                    <label class="font-bold text-slate-900">Descrição do Produto</label>
                    <textarea required class="w-full p-2 resize-none rounded outline-none border border-slate-500" name="description" id="description" cols="30" rows="5"><?= $data->description ?></textarea>
                </div>
                <div class="w-full flex gap-4">
                    <div class="w-1/3 flex flex-col gap-2">
                        <label class="w-full font-bold text-slate-900">Valor do Produto</label>
                        <input required value="<?= number_format($data->amount, 2, ',', '.') ?>" class="w-1/2 p-2 rounded outline-none border border-slate-500" type="text" name="amount" id="amount" placeholder="Ex.: 19,20" onkeyup="formatarMoeda(this);">
                    </div>
                    <div class="w-1/3 flex flex-col gap-2">
                        <label class="w-full font-bold text-slate-900">Quantidade</label>
                        <input required value="<?= $data->quantity ?>" class="w-1/2 p-2 rounded outline-none border border-slate-500" type="number" name="quantity" id="quantity">
                    </div>
                </div>
                <div class=" w-full h-1/4 flex justify-end items-end gap-4">
                    <button class="w-1/6 p-2 rounded bg-emerald-400 font-semibold text-white text-lg">Salvar</button>
                    <a href="/" class="w-1/6 text-center p-2 rounded border border-red-400 font-semibold text-red-400 text-lg transition-all hover:bg-red-400 hover:text-white">Retornar</a>
                </div>
            </form>
        </section>
    </main>
    <script>
        function formatarMoeda(i) {
            var v = i.value.replace(/\D/g, '');
            v = (v / 100).toFixed(2) + '';
            v = v.replace(".", ",");
            v = v.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1.');
            i.value = v
        }
    </script>
</body>

</html> 
Enter fullscreen mode Exit fullscreen mode

Perceba que agora não temos mais a nossa View comunicando diretamente com o banco de dados. Ele apenas traz as variáveis que vem através do nosso controller, que podemos chamar de ProductController.

<?php

namespace App\Controllers;

use App\Models\Product;

class ProductController
{
    public function edit(string $id)
    {
        $product = (new Product())->find($id);
        return render('EditProduct', $product);
    }

    public function update(string $id)
    {
        $product = (new Product())->update($_POST, $id);
        if ($product) {
            header("Location: http://localhost:8000/product/{$id}");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Dentro do nosso ProductController, nós temos dois métodos, um chamado edit e outro chamado update. Basicamente o nosso método edit, chama a Model Product, pesquisa pelo id o produto que queremos e retorna através da função render na nossa view EditProduct os dados que precisamos daquele produto.
O método update já é utilizado quando nós clicamos no botão de salvar dentro da nossa View. Perceba que ele chama mais uma vez o Model Product porém utilizando o método update do meu Model. Esse método faz o update e logo após ele verifica se fez a atualização ele te redireciona para a tela do mesmo produto com os dados atualizados.

Na camada de Model, nós temos o nosso modelo que chamei de Product apenas. Ele é uma classe que vai ter acesso ao nosso Banco de Dados e tem toda a nossa regra de negócios. Lembrando mais uma vez, que por ser um exemplo simples, não utilizei validações, então fica a recomendação de sempre fazer as validações necessárias no seu código.

<?php

namespace App\Models;

use PDO;
use DateTime;
use Exception;
use DateTimeZone;
use PDOException;
use stdClass;

class Product extends Model
{
    private PDO $pdo;
    public function __construct()
    {
        $this->pdo = $this->connect();
    }

    public function find(string $id): stdClass | false
    {
        $sqlGetProduct = "SELECT * FROM products WHERE id = :id";
        $getProduct = $this->con->prepare($sqlGetProduct);
        $getProduct->bindValue(':id', $id);
        $getProduct->execute();

        if ($getProduct->rowCount() >= 1) {
            return $getProduct->fetch(PDO::FETCH_OBJ);
        }
        return false;
    }

    public function update(array $dataProducts, string $id): bool
    {
        try {
            $amount = floatval(str_replace('.', '', str_replace(',', '', $dataProducts['amount'])));
            $timezone = new DateTimeZone('America/Sao_Paulo');
            $actualDate = new DateTime('now', $timezone);

            $queryUpdate = "UPDATE products SET status = :s, name = :n, description = :d, amount = :a, quantity = :q, updated_at = :u WHERE id = :i";
            $stmt = $this->con->prepare($queryUpdate);
            $stmt->bindValue(':s', $dataProducts['status']);
            $stmt->bindValue(':n', $dataProducts['name']);
            $stmt->bindValue(':d', $dataProducts['description']);
            $stmt->bindValue(':a', ($amount / 100));
            $stmt->bindValue(':q', $dataProducts['quantity']);
            $stmt->bindValue(':u', $actualDate->format('Y-m-d H:i:s'));
            $stmt->bindValue(':i', $id);
            $stmt->execute();

            if ($stmt->rowCount() >= 1) {
                return true;
            }
            return false;
        } catch (PDOException $e) {
            throw new Exception($e->getMessage());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Na minha classe Product, ele extende uma classe Model, que também extende uma classe abstrata de Connection, fazendo assim a conexão com meu banco de dados e no meu método construtor de Product, a única coisa que precisei foi chamar o meu método connect(). Feito isso, já tenho a minha conexão com o banco de dados e utilizo ela nos métodos find e update, tanto para encontrar algum produto ou fazer a atualização dele no banco de dados, retornando um valor booleano ou no caso do método find, retornando os dados encontrados no meu Banco de dados.

Baseado nessa estrutura, em qualquer momento eu posso utilizar novamente o método find, o método update caso eu tenha alguma regra de negócio que me exija atualizar o produto de alguma forma, ou se eu precisar encontrar algum produto utilizando o id, vou conseguir achar utilizando o método find, sem que eu precise reescrever o mesmo bloco de código novamente. Também nessa estrutura, percebemos que a nossa interface (view) não tem mais acesso direto ao banco de dados e ele nem possui mais essa responsabilidade, tornando o sistema fácil para dar manutenção, trazendo mais segurança e realizando o desacoplamento, que é o problema que o MVC resolve.

Agora, provavelmente você é capaz de entender o que é esse padrão e que tipos de problema ele resolve, então use com sabedoria. Todo padrão, todo design pattern foi criado para solucionar algum problema, e não é só porque aprendeu um conceito novo ou algo novo que você precisa sair utilizando em todos o projetos, isso pode trazer mais problemas a longo prazo.

O padrão MVC pode ser sim sua porta de entrada para um novo projeto, deixando o código mais organizado, mais separado, permitindo um desacoplamento, mas lembre-se que não é uma bala de prata, talvez mais para frente você precise utilizar outros conceitos que provavelmente terão que ser implementados com o MVC, ou talvez, você perceba que nem precisava de um MVC para asua aplicação, por isso é importante saber o que é um padrão de projeto, um padrão de arquitetura e saber como e quando utilizar.

Espero que de alguma forma tenha ajudado em seu aprendizado e que você consiga alcançar voos muito altos nessa profissão que só tem crescido!

Até uma próxima!

Top comments (0)