PHP::CRUD COM MVC

php_logo2

PHP::CRUD COM MVC

Elefante PHP

O MVC (Model, View e Controller) é uma arquitetura ou padrão que divide as responsabilidades em camadas diferentes, a fim de permitir uma melhor organização do código e prover uma solução estruturada. Esta solução se bem aplicada é escalável e reutilizável. Regras de negócios precisam ficar encapsuladas na camadas chamadas Modelo (Model). Um controlador deve dar as direções para o fluxo de dados, dizendo quando acontece e passando a responsabilidade para quem deve fazer. A tomadas de decisões em geral, não devem ser realizadas pelo Controller, muito embora, certas decisões menores possam ser aplicadas ao controller. Pense no controller como sendo “aquele que não sabe como fazer mas conhece quem sabe fazer”. A camada modelo, deve tratar toda parte de massa de dados, tanto seu armazenamento quanto a tratativa aplicada a esses dados. A view é exatamente o que o nome diz: uma visão. A view deve exibir os dados para o usuário, seja por meio de relatórios, formulários, grades de dados, etc.

Conforme a Wikipédia, a interação entre os componentes, ou “posso até me arriscar de chamar entre as camadas” é:

Um controlador (controller) envia comandos para o modelo para atualizar o seu estado (por exemplo, editando um documento). O controlador também pode enviar comandos para a visão associada para alterar a apresentação da visão do modelo (por exemplo, percorrendo um documento).

Um modelo (model) armazena dados e notifica suas visões e controladores associados quando há uma mudança em seu estado. Estas notificações permitem que as visões produzam saídas atualizadas e que os controladores alterem o conjunto de comandos disponíveis. Uma implementação passiva do MVC monta estas notificações, devido a aplicação não necessitar delas ou a plataforma de software não suportá-las.

A visão (view) Gera uma representação (Visão) dos dados presentes no modelo solicitado.

(https://pt.wikipedia.org/wiki/MVC – Acesso em 27/06/2018)

Objetivo deste artigo:

Vamos criar um pequeno projeto, bem simplificado que ilustra a responsabilidade entre as camadas. Como o objetivo é basicamente um CRUD, para modelo vamos usar apenas uma classe, o que na prática é apenas um pouco diferente. Quero dizer com apenas, porque mais classes são utilizadas para o trabalho: Classes para gerenciar os dados do banco de dados, classes para tratar os dados, etc. No projeto que se segue, apenas uma classe será utilizada para gerenciar os dados com o banco de dados. Não será utilizada nenhuma regra de negócio e portanto não adicionarei uma classe para Modelo. Mas há um artigo que trata um pouco sobre o Active Record, e mostro como criar uma classe que é estendida para atuar como classe Modelo.

Se você nunca utilizou um framework MVC ou nem sabe por onde começar quando se deparar com um, acredito que o conteúdo desse artigo vai te dar uma boa visão.

Sobre projeto:

O pequeno projeto que é foi desenvolvido para este arquivo, poderia até dizer que seria uma atualização da agenda de contatos do artigo CRUD completo com PDO. Mas, é mais do que isso porque, estaremos reconstruindo a aplicação, utilizando conceitos de orientação a objetos e MVC.
Claro que é apenas um CRUD, mas tem a proposta de prover todas as funcionalidades já vistas no artigo mencionado, desta vez utilizando classes e uma arquitetura moderna.

Requisitos básicos:

Para iniciarmos o pequeno projeto MVC, temos alguns requisitos:

Não precisaria dizer que você deve ter algum meio de executar scripts php e um servidor MySQL disponível para criar o banco de dados. Este detalhe, sobre como instalar cada serviço, não será abrangido aqui.

Quanto ao projeto:

Precisamos criar 4 classes:

  • Conexao.php
  • Request.php
  • Contato.php (Será a Model)
  • ContatosController.php

Dois arquivos para Views:

  • form.php
  • grade.php

Um arquivo para fazer um front controller:

  • index.php

Iniciando a primeira etapa: criação do banco de dados

Não vamos desenhar o projeto aqui porque ele já foi preparado antes para o artigo, por isso ele já existe. Então já vamos começar criando o banco de dados, que é composto apenas de uma tabela, composta por 4 colunas. Evidentemente, quando você entendê-lo, e se lhe for útil para além da proposta deste artigo, você poderá expandi-lo!

Inicie um prompt no Windows, ou terminal no Linux e inicie o MySql no modo terminal. Não será tratado aqui como fazer em cada sistema. Se você estiver utilizando um bash instalado no Windows, muito provavelmente se parecerá com o modo feito no linux. No linux, você faz da seguinte forma para iniciá-lo:

# mysql -u root -p

Entenda que o símbolo cerquilha (#) no exemplo acima, representa o prompt. Note na imagem a seguir:

Acesso ao MySQL monitor no terminal do Linux
Acesso ao MySQL monitor no terminal do Linux

Estando no prompt do MySQL monitor, faremos a criação do banco de dados, selecionaremos o banco de dados e criaremos a tabela para o projeto.

Primeiro para criar o banco de dados, digite o seguinte comando:

create database projeto_mvc;

Se você revebeu como resposta um “Query OK”, poderá prosseguir para a próxima etapa. Caso tenha recebido uma mensagem de erro, leia atentamente o comando e digite novamente, concluíndo sempre com um ponto e vírgula (;) no final.

Na sequência, vamos selecionar a tabela, utilizando o comando:

use projeto_mvc;

Uma resposta como “Database changed” deverá ser apresentada. Caso não tenha acontecido, novamente, reveja o comando e não se esqueça do ponto e vírgula.

Estando com o banco de dados selecionado, vamos criar a tabela. Antes de prosseguir, entenda que ao chegar no final de cada linha, tecle enter, mas NÃO digite desta vez o ponto e vírgula a cada linha! Apenas no final do comando! A sequencia é a seguinte:

ANTES DE DIGITAR !! (Se você já conhece o MySQL monitor ou vai utilizar outro aplicativo gerenciador do MySQL, pode pular esta explicação) :
Note que há uma tabulação no comando seguinte. Bem, esta você NÃO deverá digitar estes espaços ou teclar TAB mas note que o comando ocupa linhas diferentes. O que ocorre é, quando você digitar: “create table contatos (“ deverá aquí teclar Enter. A cada final de linha, tecle um Enter. O MySQL monitor inclui um sinal de seta “->” para mostrar que o comando prossegue a nesta linha “apontada”. Portanto, continue digitando: id int … nesta linha até a víngula “,” e então tecle Enter. Veja que na penúltima linha: “email varchar(80) default null” não termina com vírgula como nas três linhas anteriores. Porque esta já é parte da sintaxe do comando SQL. Você só utiliza a vírgula para separar as declarações de cada coluna. Neste caso, serão quatro colunas: id, nome, telefone e email. Destas colunas, a coluna “nome” não aceita valores nulos, mas telefone e e-mail aceitam. Por isso, a diferença entre esta declaração para “nome” conter “not null” e para as outras colunas, a declaração contém “default null”. Esteja atento a declaração para coluna id, porque aquí há quatro paravras e não cinco! Veja que auto_increment contém um underline para unir auto e increment. Posto isso, acredito que você esteja pronto para prosseguir!


create table contatos (
id int unsigned auto_increment primary key,
nome varchar(80) not null,
telefone varchar(20) default null,
email varchar(80) default null
);

Se você digitou a linha corretamente o comando, deverá receber uma resposta “Query OK”, caso contrário, reveja o comando linha a linha e esteja atento as considerações anteriores. Não tente digitar “e-mail” para o nome da coluna mas sim “email”. O sinal “-” será considerado erro de sintaxe. Note na captura de tela como foi digitado:

Tabela criada para o projeto CRUD MVC
Tabela criada para o projeto CRUD MVC

Você pode exibir os detalhes da tabela contatos, que acabou de criar, para verificar se não esqueceu de nenhuma coluna. Para isso utilize o comando:

desc contatos;

A próxima captura mostra como o dados serão apresentados:

Detalhes da tabela contatos sendo exibida - CRUD MVC
Detalhes da tabela contatos sendo exibida – CRUD MVC

Tendo concluído com êxito estas etapas, poderemos seguir para as próximas etapas.

Segunda Etapa, criação das classes de Conexão e Modelo

Vamos começar pela classe de Conexao. Aqui vamos utilizar o padrão de projetos Singleton que consiste basicamente em criar um classe que só pode ser instanciada a partir de dentro. Ou seja, deve existir um método que ao ser invocado, devolve uma instância dele mesmo. Porém aqui temos também uma mistura com o projeto Factory, pelo menos um princípio dela. Esse projeto consiste basicamente e, dizendo de forma bem simplista, em fabricar um objeto, ou seja, gerar uma instância e, que neste caso, será uma instância PDO.

Antes de digitar o código, entenda que há outras questões de segurança a ser implementado nesta classe para torná-la plenamente Singleton, não vamos entrar nestes detalhes porque desta forma complicaria muito. Mas há um artigo que explica em mais detalhes uma classe Singleton com factory, bem mais completa. Você pode dar uma olhada neste artigo DEPOIS!.

Este é o código da classe Conexao. Crie um arquivo com o mesmo nome da classe, Conexao.php e insira este código. Recomendo neste momento esquecer que é amigo dos famosos CTRL+C e CTRL+V e partir para digitação linha a linha. Não preciso explicar porque isso lhe ajudará melhor em seu aprendizado.



class Conexao
{
    private static $conexao;

    private function __construct()
    {}

    public static function getInstance()
    {
        if (is_null(self::$conexao)) {
            self::$conexao = new \PDO('mysql:host=localhost;port=3306;dbname=projeto_mvc', 'root', '123456');
            self::$conexao->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
            self::$conexao->exec('set names utf8');
        }
        return self::$conexao;
    }
}

Note que só há um método publico, mas é estático e mesmo o construtor deve ser declarado como privado, para impedir que seja possível instanciar a classe Conexao. Perceba que no meu caso, estou usando uma senha default do meu ambiente ‘123456’. Mas aqui você, obviamente deverá alterar e entrar com a senha de conexão com seu banco de dados. Se não for o usuário não for o ‘root’, não se esqueça de mudar isso também. A classe conexão é só isso!

Vamos para a classe Modelo. Em nosso caso vamos adicionar todos os métodos de operação de CRUD nesta classe, mas em um projeto real, isso raramente acontece. Ainda mais que, em geral você poderá estar utilizando algum framework para trabalhar com projetos MVC. Como funciona em um Framework MVC? Uma classe modelo contém todos os métodos relacionados a negócios. Esta classe normalmente herda métodos de operações CRUD a partir de uma ou mais classes que tratam a camada de acesso com a tabela do banco. Em muitos projetos, se utiliza um padrão de projetos chamado Active Record. Não vamos detalhar aqui este projeto, mas eu tenho um artigo que também fala sobre isso e lhe dará mais detalhes a respeito deste funcionamento. Por hora, vamos criar um pequeno Active Record, bem simplista, apenas com as operações do banco e que ao mesmo tempo, já será a classe Modelo. Esta classe representará uma linha da tabela contatos e ao mesmo tempo possuirá métodos básicos de CRUD para operar a tabela.

Esta classe é a Contato, no “singular”. Crie um arquivo Contato.php.

A classe é composta pelos métodos de CRUD:
Save – Insere um novo registro ou atualiza um registro – Atua tanto como CREATE como UPDATE do CRUD.
Destroy – Exclui um registro da tabela. Atua como o DELETE do CRUD.
Find – Localiza um registro conforme o id informado. Atua como READ do CRUD.
All – Listar todos registros da tabela. Também atua como READ do CRUD.
Count – É um método bonus para firula! Mostra a quantidade de registros na tabela. Acaba tendo um certo princípio de funcionalidade READ do CRUD.

Há alguns métodos auxiliares:

  • Preparar – Irá retornar um array com dados no formato perfeito para se criar a query para Inserir ou Atualizar os dados. Este método dá suporte ao método save.
  • Escapar – Irá verificar cada atributo da classe Modelo, ou seja a própria classe Contato e irá escapar ou tornar os dados aceitáveis para sintaxe SQL. Este método dá suporte ao método Preparar.

Além disso, os atributos são dinâmicos. Não são declarados explicitamente na classe Modelo. Isso quer dizer que você não encontrará um atributo “nome”. Estes atributos são criados dinamicamente em um super atributo do tipo array, chamado simplesmente “atributos”. Para esta façanha foram usados os métodos mágicos _set(), _get() e _isset().

O seguinte código da classe Contato segue a seguir:



/**
 * Classe Contato - baseado no modelo Active Record (Simplificado) 
 * @author Alexandre Bezerra Barbosa
 */
class Contato
{
    private $atributos;

    public function __construct()
    {

    }

    public function __set(string $atributo, $valor)
    {
        $this->atributos[$atributo] = $valor;
        return $this;
    }

    public function __get(string $atributo)
    {
        return $this->atributos[$atributo];
    }

    public function __isset($atributo)
    {
        return isset($this->atributos[$atributo]);
    }

    /**
     * Salvar o contato
     * @return boolean
     */
    public function save()
    {
        $colunas = $this->preparar($this->atributos);
        if (!isset($this->id)) {
            $query = "INSERT INTO contatos (".
                implode(', ', array_keys($colunas)).
                ") VALUES (".
                implode(', ', array_values($colunas)).");";
        } else {
            foreach ($colunas as $key => $value) {
                if ($key !== 'id') {
                    $definir[] = "{$key}={$value}";
                }
            }
            $query = "UPDATE contatos SET ".implode(', ', $definir)." WHERE id='{$this->id}';";
        }
        if ($conexao = Conexao::getInstance()) {
            $stmt = $conexao->prepare($query);
            if ($stmt->execute()) {
                return $stmt->rowCount();
            }
        }
        return false;
    }

    /**
     * Tornar valores aceitos para sintaxe SQL
     * @param type $dados
     * @return string
     */
    private function escapar($dados)
    {
        if (is_string($dados) & !empty($dados)) {
            return "'".addslashes($dados)."'";
        } elseif (is_bool($dados)) {
            return $dados ? 'TRUE' : 'FALSE';
        } elseif ($dados !== '') {
            return $dados;
        } else {
            return 'NULL';
        }
    }

    /**
     * Verifica se dados são próprios para ser salvos
     * @param array $dados
     * @return array
     */
    private function preparar($dados)
    {
        $resultado = array();
        foreach ($dados as $k => $v) {
            if (is_scalar($v)) {
                $resultado[$k] = $this->escapar($v);
            }
        }
        return $resultado;
    }

    /**
     * Retorna uma lista de contatos
     * @return array/boolean
     */
    public static function all()
    {
        $conexao = Conexao::getInstance();
        $stmt    = $conexao->prepare("SELECT * FROM contatos;");
        $result  = array();
        if ($stmt->execute()) {
            while ($rs = $stmt->fetchObject(Contato::class)) {
                $result[] = $rs;
            }
        }
        if (count($result) > 0) {
            return $result;
        }
        return false;
    }

    /**
     * Retornar o número de registros
     * @return int/boolean
     */
    public static function count()
    {
        $conexao = Conexao::getInstance();
        $count   = $conexao->exec("SELECT count(*) FROM contatos;");
        if ($count) {
            return (int) $count;
        }
        return false;
    }

    /**
     * Encontra um recurso pelo id
     * @param type $id
     * @return type
     */
    public static function find($id)
    {
        $conexao = Conexao::getInstance();
        $stmt    = $conexao->prepare("SELECT * FROM contatos WHERE id='{$id}';");
        if ($stmt->execute()) {
            if ($stmt->rowCount() > 0) {
                $resultado = $stmt->fetchObject('Contato');
                if ($resultado) {
                    return $resultado;
                }
            }
        }
        return false;
    }

    /**
     * Destruir um recurso
     * @param type $id
     * @return boolean
     */
    public static function destroy($id)
    {
        $conexao = Conexao::getInstance();
        if ($conexao->exec("DELETE FROM contatos WHERE id='{$id}';")) {
            return true;
        }
        return false;
    }
}

Agora temos a classe de Conexão e a classe Modelo do nosso projeto.

Terceira Etapa, criação das classes para Controller

Precisamos de uma classe que faça o meio de campo. Esta classe é a Controller. Em nosso caso, vamos adicionar uma classe controller bem básica com métodos que serão extendidos para uma Controller específica. O que? Como assim? Assim como poderiamos ter uma super classe Model que seria extendida para criar a Model Contato, a fim de evitar misturar os metodos de regras de negócios, com os que dão suporte a banco de dados, da mesma maneira fazemos com um controller. Podemos criar métodos que serão responsável por enviar dados a uma view, ou até ler os dados ou repassar para um Model. Mas estes métodos não são necessáriamente parte do negócio, então, o ideal é ficarem separadas. Embora aí foi mencionado “parte do negócio”, o controller não deve ser o responsável por tratar nada de negócios a nar ser dar as direções. Todavia, o ideal é dar as direções relacionadas as “ações” utilizadas pelo negócio. Neste caso, quer dizer que, se o projeto se relaciona a um aplicativo que armazena contatos, o controller deve ter métodos para realizar ações relacionadas aos contatos, tais como salvar um contato, excluir um contato, listar um contato. Para isso, cria-se uma classe ContatosController que é o controller de contatos.

Os métodos que essa classe possui:

 

  • Listar – O controller deve buscar com a Model uma lista de contatos e enviar para View.
  • Criar – O controller deve prover um meio para que o usuário entre com um novo contato, então solicita a view para exibir um formulário.
  • Editar – O controller deve buscar um contato solicitado pelo usuário, contatando a Model para isso e deve exibir isso para o usuário, enviando para a View.
  • Salvar – O controller deve salvar um contato novo criado pelo usuário no formulário da View. Controller recebe os dados através de uma requisição post e envia os dados para a Model salvar no banco de dados.
  • Atualizar – Funciona parecido com Salvar, a diferença é que o Controller deve pedir a model um contato a ser atualizado, e enviar para model o contato atualizado, que será salvo no banco de dados. Em outras palavras, o Controller vai localizar o contato que o usuário quer atualizar e faz isso por pedir a Model, utilizando seu método Find. Quando tiver um contato, lança os dados atualizados nele, conformes recebidos por um “Request”. Usa novamente a Model para salvar os dados no banco.
  • Excluir – O controller deve solicitar a Model a exclusão de um contato, conforme selecionado pelo usuário na Grade de listagem de contatos.

Este é o código do controller. Crie um novo arquivo ContatosController.php e insira este código:



class ContatosController extends Controller
{

    /**
     * Lista os contatos
     */
    public function listar()
    {
        $contatos = Contato::all();
        return $this->view('grade', ['contatos' => $contatos]);
    }

    /**
     * Mostrar formulario para criar um novo contato
     */
    public function criar()
    {
        return $this->view('form');
    }

    /**
     * Mostrar formulário para editar um contato
     */
    public function editar($dados)
    {
        $id      = (int) $dados['id'];
        $contato = Contato::find($id);

        return $this->view('form', ['contato' => $contato]);
    }

    /**
     * Salvar o contato submetido pelo formulário
     */
    public function salvar()
    {
        $contato           = new Contato;
        $contato->nome     = $this->request->nome;
        $contato->telefone = $this->request->telefone;
        $contato->email    = $this->request->email;
        if ($contato->save()) {
            return $this->listar();
        }
    }

    /**
     * Atualizar o contato conforme dados submetidos
     */
    public function atualizar($dados)
    {
        $id                = (int) $dados['id'];
        $contato           = Contato::find($id);
        $contato->nome     = $this->request->nome;
        $contato->telefone = $this->request->telefone;
        $contato->email    = $this->request->email;
        $contato->save();

        return $this->listar();
    }

    /**
     * Apagar um contato conforme o id informado
     */
    public function excluir($dados)
    {
        $id      = (int) $dados['id'];
        $contato = Contato::destroy($id);
        return $this->listar();
    }
}

Note como existe uma interação entre View e Model. Mas é claro que você deve ter notado que esta classe está estendendo a classe controller. Antes de adicionar esta classe Controller, vamos criar uma pequena classe para gerenciar requisições post dar suporte a classe controller. Este é o código da classe Request. Crie um arquivo chamado Request.php e adicione este código:



class Request
{
    protected $request;

    public function __construct()
    {
        $this->request = $_REQUEST;
    }

    public function __get($nome)
    {
        if (isset($this->request[$nome])) {
            return $this->request[$nome];
        }
        return false;
    }
}

De fato essa pequena classe será uma enorme quebra galhos para deixar o código legível e você já vai perceber porque. Crie agora um arquivo chamado Controller.php a adicione o seguinte código:



class Controller
{
    public $request;

    public function __construct()
    {
        $this->request = new Request;
    }

    public function view($arquivo, $array = null)
    {
        if (!is_null($array)) {
            foreach ($array as $var => $value) {
                ${$var} = $value;
            }
        }
        ob_start();
        include "{$arquivo}.php";
        ob_flush();
    }
}

Neste caso, a classe Controller está apenas utilizando um método para se comunicar com a View e instanciando um objeto da Request já em sua construção, para permitir aos métodos do controller, fazer uso dos dados enviados via request. Daí o segredo daquela “mágica” que acontece nos métodos salvar e atualizar.

Até aqui já temos a Model, o Controller e a classe de conexão. Faltam dois elementos importantes para começar a tudo funcionar: View e uma especie de Dispatcher ou front controller para dar vida ao controller. Em frameworks modernos, se utiliza toda uma estrutura de rotas com base na url. Os seja, quando você configura uma rota, precisa mapear uma URL para um método de um controller. Essa rota deve permitir definir parâmetros que serão passados para os métodos dos controllers. Existem muitos e muitos mais detalhes sobre roteamento, que se tratados aqui tornarão este projeto complexo para o momento. Por isso, não será abrangido sitema de rotas aqui. Rotas exige um artigo só para o próprio tema.

Etapas seguintes, criação de views e um modo de roteamento básico

Devido algumas limitações para apresentação do código aqui no WordPress, apresento adiante todos os arquivos que estão no Gist e Github. De agora endiante, você poderá tanto rever os códigos anteriores como os que os demais que compõe a View e o Front Controller (arquivo index.php):

<?php
class Conexao
{
private static $conexao;
private function __construct()
{}
public static function getInstance()
{
if (is_null(self::$conexao)) {
self::$conexao = new \PDO('mysql:host=localhost;port=3306;dbname=projeto_mvc', 'root', 'P@ssw0rd');
self::$conexao->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
self::$conexao->exec('set names utf8');
}
return self::$conexao;
}
}
view raw Conexao.php hosted with ❤ by GitHub
<?php
/**
* Classe Contato - baseado no modelo Active Record (Simplificado)
* @author Alexandre Bezerra Barbosa
*/
class Contato
{
private $atributos;
public function __construct()
{
}
public function __set(string $atributo, $valor)
{
$this->atributos[$atributo] = $valor;
return $this;
}
public function __get(string $atributo)
{
return $this->atributos[$atributo];
}
public function __isset($atributo)
{
return isset($this->atributos[$atributo]);
}
/**
* Salvar o contato
* @return boolean
*/
public function save()
{
$colunas = $this->preparar($this->atributos);
if (!isset($this->id)) {
$query = "INSERT INTO contatos (".
implode(', ', array_keys($colunas)).
") VALUES (".
implode(', ', array_values($colunas)).");";
} else {
foreach ($colunas as $key => $value) {
if ($key !== 'id') {
$definir[] = "{$key}={$value}";
}
}
$query = "UPDATE contatos SET ".implode(', ', $definir)." WHERE id='{$this->id}';";
}
if ($conexao = Conexao::getInstance()) {
$stmt = $conexao->prepare($query);
if ($stmt->execute()) {
return $stmt->rowCount();
}
}
return false;
}
/**
* Tornar valores aceitos para sintaxe SQL
* @param type $dados
* @return string
*/
private function escapar($dados)
{
if (is_string($dados) & !empty($dados)) {
return "'".addslashes($dados)."'";
} elseif (is_bool($dados)) {
return $dados ? 'TRUE' : 'FALSE';
} elseif ($dados !== '') {
return $dados;
} else {
return 'NULL';
}
}
/**
* Verifica se dados são próprios para ser salvos
* @param array $dados
* @return array
*/
private function preparar($dados)
{
$resultado = array();
foreach ($dados as $k => $v) {
if (is_scalar($v)) {
$resultado[$k] = $this->escapar($v);
}
}
return $resultado;
}
/**
* Retorna uma lista de contatos
* @return array/boolean
*/
public static function all()
{
$conexao = Conexao::getInstance();
$stmt = $conexao->prepare("SELECT * FROM contatos;");
$result = array();
if ($stmt->execute()) {
while ($rs = $stmt->fetchObject(Contato::class)) {
$result[] = $rs;
}
}
if (count($result) > 0) {
return $result;
}
return false;
}
/**
* Retornar o número de registros
* @return int/boolean
*/
public static function count()
{
$conexao = Conexao::getInstance();
$count = $conexao->exec("SELECT count(*) FROM contatos;");
if ($count) {
return (int) $count;
}
return false;
}
/**
* Encontra um recurso pelo id
* @param type $id
* @return type
*/
public static function find($id)
{
$conexao = Conexao::getInstance();
$stmt = $conexao->prepare("SELECT * FROM contatos WHERE id='{$id}';");
if ($stmt->execute()) {
if ($stmt->rowCount() > 0) {
$resultado = $stmt->fetchObject('Contato');
if ($resultado) {
return $resultado;
}
}
}
return false;
}
/**
* Destruir um recurso
* @param type $id
* @return boolean
*/
public static function destroy($id)
{
$conexao = Conexao::getInstance();
if ($conexao->exec("DELETE FROM contatos WHERE id='{$id}';")) {
return true;
}
return false;
}
}
view raw Contato.php hosted with ❤ by GitHub
<?php
class ContatosController extends Controller
{
/**
* Lista os contatos
*/
public function listar()
{
$contatos = Contato::all();
return $this->view('grade', ['contatos' => $contatos]);
}
/**
* Mostrar formulario para criar um novo contato
*/
public function criar()
{
return $this->view('form');
}
/**
* Mostrar formulário para editar um contato
*/
public function editar($dados)
{
$id = (int) $dados['id'];
$contato = Contato::find($id);
return $this->view('form', ['contato' => $contato]);
}
/**
* Salvar o contato submetido pelo formulário
*/
public function salvar()
{
$contato = new Contato;
$contato->nome = $this->request->nome;
$contato->telefone = $this->request->telefone;
$contato->email = $this->request->email;
if ($contato->save()) {
return $this->listar();
}
}
/**
* Atualizar o contato conforme dados submetidos
*/
public function atualizar($dados)
{
$id = (int) $dados['id'];
$contato = Contato::find($id);
$contato->nome = $this->request->nome;
$contato->telefone = $this->request->telefone;
$contato->email = $this->request->email;
$contato->save();
return $this->listar();
}
/**
* Apagar um contato conforme o id informado
*/
public function excluir($dados)
{
$id = (int) $dados['id'];
$contato = Contato::destroy($id);
return $this->listar();
}
}
<?php
class Controller
{
public $request;
public function __construct()
{
$this->request = new Request;
}
public function view($arquivo, $array = null)
{
if (!is_null($array)) {
foreach ($array as $var => $value) {
${$var} = $value;
}
}
ob_start();
include "{$arquivo}.php";
ob_flush();
}
}
view raw Controller.php hosted with ❤ by GitHub
<div class="container">
<form action="?controller=ContatosController&<?php echo isset($contato->id) ? "method=atualizar&id={$contato->id}" : "method=salvar"; ?>" method="post" >
<div class="card" style="top:40px">
<div class="card-header">
<span class="card-title">Contatos</span>
</div>
<div class="card-body">
</div>
<div class="form-group form-row">
<label class="col-sm-2 col-form-label text-right">Nome:</label>
<input type="text" class="form-control col-sm-8" name="nome" id="nome" value="<?php
echo isset($contato->nome) ? $contato->nome : null;
?>" />
</div>
<div class="form-group form-row">
<label class="col-sm-2 col-form-label text-right">Telefone:</label>
<input type="text" class="form-control col-sm-8" name="telefone" id="telefone" value="<?php
echo isset($contato->telefone) ? $contato->telefone : null;
?>" />
</div>
<div class="form-group form-row">
<label class="col-sm-2 col-form-label text-right">Email:</label>
<input type="text" class="form-control col-sm-8" name="email" id="email" value="<?php
echo isset($contato->email) ? $contato->email : null;
?>" />
</div>
<div class="card-footer">
<input type="hidden" name="id" id="id" value="<?php echo isset($contato->id) ? $contato->id : null; ?>" />
<button class="btn btn-success" type="submit">Salvar</button>
<button class="btn btn-secondary">Limpar</button>
<a class="btn btn-danger" href="?controller=ContatosController&method=listar">Cancelar</a>
</div>
</div>
</form>
</div>
view raw form.php hosted with ❤ by GitHub
<h1>Contatos</h1>
<hr>
<div class="container">
<table class="table table-bordered table-striped" style="top:40px;">
<thead>
<tr>
<th>Nome</th>
<th>Telefone</th>
<th>Email</th>
<th><a href="?controller=ContatosController&method=criar" class="btn btn-success btn-sm">Novo</a></th>
</tr>
</thead>
<tbody>
<?php
if ($contatos) {
foreach ($contatos as $contato) {
?>
<tr>
<td><?php echo $contato->nome; ?></td>
<td><?php echo $contato->telefone; ?></td>
<td><?php echo $contato->email; ?></td>
<td>
<a href="?controller=ContatosController&method=editar&id=<?php echo $contato->id; ?>" class="btn btn-primary btn-sm">Editar</a>
<a href="?controller=ContatosController&method=excluir&id=<?php echo $contato->id; ?>" class="btn btn-danger btn-sm">Excluir</a>
</td>
</tr>
<?php
}
} else {
?>
<tr>
<td colspan="5">Nenhum registro encontrado</td>
</tr>
<?php
}
?>
</tbody>
</table>
</div>
view raw grade.php hosted with ❤ by GitHub
<?php
error_reporting(E_ALL);
ini_set('display_errors', true);
spl_autoload_register(function($class) {
if (file_exists("$class.php")) {
require_once "$class.php";
return true;
}
});
?>
<!DOCTYPE html>
<html lang='pt-br'>
<header>
<meta charset="utf-8">
<title>Agenda de contatos</title>
<link href='https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css' rel='stylesheet' integrity='sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB' crossorigin='anonymous' />
<link href='https://use.fontawesome.com/releases/v5.1.0/css/all.css' rel='stylesheet' integrity='sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt' crossorigin='anonymous' />
<script src='https://code.jquery.com/jquery-3.3.1.slim.min.js' integrity='sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo' crossorigin='anonymous'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js' integrity='sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49' crossorigin='anonymous'></script>
<script src='https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js' integrity='sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T' crossorigin='anonymous'></script>
</header>
<body>
<?php
if ($_GET) {
$controller = isset($_GET['controller']) ? ((class_exists($_GET['controller'])) ? new $_GET['controller'] : NULL ) : null;
$method = isset($_GET['method']) ? $_GET['method'] : null;
if ($controller && $method) {
if (method_exists($controller, $method)) {
$parameters = $_GET;
unset($parameters['controller']);
unset($parameters['method']);
call_user_func(array($controller, $method), $parameters);
} else {
echo "Método não encontrado!";
}
} else {
echo "Controller não encontrado!";
}
} else {
echo '<h1>Contatos</h1><hr><div class="container">';
echo 'Bem-vindo ao aplicativo MVC Contatos! <br /><br />';
echo '<a href="?controller=ContatosController&method=listar" class="btn btn-success">Vamos Começar!</a></div>';
}
?>
</body>
</html>
view raw index.php hosted with ❤ by GitHub
<?php
class Request
{
protected $request;
public function __construct()
{
$this->request = $_REQUEST;
}
public function __get($nome)
{
if (isset($this->request[$nome])) {
return $this->request[$nome];
}
return false;
}
}
view raw Request.php hosted with ❤ by GitHub

Algumas considerações sobre o arquivo Index:

Note neste arquivo, já demos uma cara para página index com Bootstrap, etc. Tudo isso poderia até estar separado e ser exibido como uma view, deixando assim no arquivo index apenas aquelas lógicas do autoloader e do get isoladas, esperando parâmetros na url. Mas o foco aqui é não é explorar o roteamento ou “um despachante”. Deixarei isso para outro artigo.

Fazendo breves considerações sobre a lógica simples: As classes são carregadas dinamicamente, e por isso faz sentido criar um autoloader para isso. Como criamos todas as classes, não estando utilizando nenhum pacote de terceiros, não pudemos utilizar o composer, mas criamos de forma simples um autoloader com a função nativa spl_autoload_register():

spl_autoload_register(function($class) {
    if (file_exists("$class.php")) {
        require_once "$class.php";
        return true;
    }
});

As primeiras duas linhas do código são apenas para depuração. Caso as mensagens de erros estejam desativadas nas configurações do php, estas linhas irão ativar:

error_reporting(E_ALL);
ini_set('display_errors', true);

A dinâmica do front controller acontece nessa lógica simplificada:

if ($_GET) {
            $controller = isset($_GET['controller']) ? ((class_exists($_GET['controller'])) ? new $_GET['controller'] : NULL ) : null;
            $method     = isset($_GET['method']) ? $_GET['method'] : null;
            if ($controller && $method) {
                if (method_exists($controller, $method)) {
                    $parameters = $_GET;
                    unset($parameters['controller']);
                    unset($parameters['method']);
                    call_user_func(array($controller, $method), $parameters);
                } else {
                    echo "Método não encontrado!";
                }
            } else {
                echo "Controller não encontrado!";
            }
        } else {
            echo '<h1>Contatos</h1><hr><div class="container">';
            echo 'Bem-vindo ao aplicativo MVC Contatos!'; 
            echo '<a href="?controller=ContatosController&method=listar" class="btn btn-success">Vamos Começar!</a></div>';
        }

No Gist, como você já viu acima, estão os arquivos form.php e grade.php, que são os arquivos de views.

Em alguns frameworks modernos, você praticamente não utiliza código php de forma explícita como aparecem acima nos arquivos de views. Todavia, não está errado também a maneira como foi apresentado. A questão que é muita vezes lançadas por muitos e fonte de muita discussão, é por deixar o código nada elegante se você estiver misturando cógidos php e html. A idéia sustentada por muitos e criar arquivos de views com praticamente códigos para front-end: html, javascript, css, etc. Alguns sistemas de templates permitem uma página html limpa e com diretivas bem discretas. No Framework Laravel, o sistema de templates Blade blilha com diretivas próprias que tem o papel de omitir qualquer código php. Se você busca um framework com ótima estrutura e robutez, pessoalmente prefiro e me sinto a vontade com Laravel.

Conclusão

Espero que tenha apreciado o artigo e lhe seja bastante útil. Acredito que aquí lhe apresenta uma boa base para seu aprendizado. Encontrei na internet vários artigos sobre MVC, mas ao menos em grande maioria estão, ou apresentando muitos detalhes para quem está iniciando, ou quebrando um assunto em diversos artigos. Meus artigos algumas vezes ficam extensos, mas visam abranger se possível um tema do seu início ao fim. Não deixe de expor sua opnião, ela é muito importante para meu trabalho, tendo em vista que estou planejando futuros cursos de PHP, mas avançados.

O projeto apresentou de forma simples a interação entre classe modelo, classes de controle e respostas para view. Além de que, claro a solicitação de ações por parte do usuário na view, sendo encaminhadas para o controller, roteadas por uma espécie de “lógica despachante”. Não utilizamos roteamento por ser um tema logo e que desviaria o foco. Se você entendeu a funcionalidade do pequeno aplicativo, será capaz tanto de desenvolver pequenos exercícios, bem como atém mesmo explorar um framework tão robusto como Laravel. Claro que isso não significa que eu tenha lhe ensinado Laravel neste artigo, mas se você nunca testou o Laravel, vai notar que conceltualmente ficará mais familiar, pois a idéia de MVC é única. Muito embora, deixo claro que é possível programar utilizando o framework Laravel para contruir aplicações, sem se apegar fielmente ao MVC.

Se você estiver primeiro lendo o artigo para entender o projeto e chegou até aqui, aproveite para ver algumas capturas de telas do projeto:

Tela de apresentação - CRUD MVC
Tela de apresentação – CRUD MVC
Grade vazia, primeiro acesso -CRUD MVC
Grade vazia, primeiro acesso -CRUD MVC
Adicionando novo registro - CRUD MVC
Adicionando novo registro – CRUD MVC
Grade apresentando registro recém criado - CRUD MVC
Grade apresentando registro recém criado – CRUD MVC

Se você quiser mais um pouco, pode se divertir com alguns brinquedos, por exemplo: O LittleBoy é um framework de brinquedo, desenvolvido durante estudo e pode até virar algo diferente amanhã ou depois. Este framework já tem roteamento com fluxo completo request a response, instanciado por uma classe App, que pode se transformar em um Kernel.
São classes simples, inspiradas até mesmo na ideia de outros grandes frameworks. Um aplicativo identico a este foi criado com ele e está pronto. Vale a pena conferir:
https://github.com/alxbbarbosa/LittleBoy/blob/master/Framework/src/App.php

[]’s

38 comentários

  1. Excelente sua explicação. Simplesmente fantástico. Será que você poderia criar artigo sobre sistema de rotas para esse crud?

  2. Estou com um pequeno problema, estou utilizando seu tutorial porém eu organizei nas respectivas pastas que geralmente encontramos no mvc, como Models, Controller e Views.
    Apos fazer isso coloquei os arquivos Controller e ContatoController na mesma pasta tudo certinho.
    Toda vez que rodo meu projeto agora ele retorna mensagem de que o Controller não pode ser encontrado.
    Não estou conseguindo visualizar o problema .

    1. OLá Camilo! Você começou já criando em pastas separadas ou seguiu o artigo e obteve êxito antes, primeiro? O motivo da pergunta é para primeiro termos a certeza de que as classes estão todas ok. Se sim, então o problema vai estar no na função: spl_autoload_register(). Acontece que no artigo, a função não foi implementada da forma para varrer subpastas ou pastas diferentes. Sendo assim, precisamos modificá-la. Mas, embora este artigo ainda esteja utilizando esta função, não recomendo continuar usando-a. O esforço poderá ser grande para manter o código funcional. Então, temos o composer sendo um amigão nessa hora! ele substitui com confiança esse trabalho! É bem provável que em breve eu esteja fazendo uma atualização para mudar isso no artigo. Se você já sabe como fazer, manda bala e comenta aí o resultado. Mas se não, pode me enviar um e-mail que eu te respondo o mais rápido possível! Já tenho “alguns artigos” para colocar no blog, mas uma falha nele está impedindo e então ainda estou trabalhando nisso! Obrigado por acompanhar!

      Para concluir, talvez um brinquedo mais evoluído que virá para o blog alguma hora, é este: https://github.com/alxbbarbosa/aplicacao_mvc
      Dê uma olhada no código e veja o que você acha. Este é um mini-framework bem didático. Este ainda não estava usando composer, mas tem uma classe para carregar as classes em pastas diferentes.

      Abraço!

      1. Boa noite Alexandre,
        O caso é o seguinte, respondendo sua pergunta, seguindo este tutorial consegui sim fazer com que tudo funcionasse direitinho, apenas alguns errinhos bobos nada muito grave.
        Mas como a intenção é montar uma explicação um pouco mais trabalhada do MVC, de todos os tutoriais que peguei na internet considerei o seu mais prático e mais facil para iniciar um projeto mvc do 0, podendo cada um criar o seu projeto conhecendo-o como um todo.
        Problema da maior parte dos projetos encontrados é a complicação e algumas particularidades que a maioria coloca e nao explicam, impossibilitando que as pessoas que seguem o modelo possam entender tudo.
        Bom enfim, gostaria se possível, ajudasse a resolver esse pequeno probleminha. Em resumo segui este tuto e fiz tudo rodar na raiz mesmo, depois que analisei o codigo e vi que daria pra iniciar um projeto bacana dividi tudo em suas respectivas pastas de acordo com o que sabemos de mvc, mas ai deu aquele problema que citei e voce especificou, se puder me ajudar agradeço e aguardo.

  3. modifiquei o spl_autoload_register() para incluir as pastas, mudou um pouco o erro, lhe enviei um email mas nao me respondeu será que pode auxiliar no novo erro ?

  4. Parabens Alexandre muito bom!!!
    Só uma dúvida que me deparei diante desse padrão. Se quisesse por exemplo na saída de um campo qualquer executar uma função no controller para que este requisitasse do model para buscar alguns campos de uma tabela (exemplo tela de vendas buscar dados do cliente digitando seu codigo) como faria seguindo seu padrão MVC. Tô quebrando a cabeça. Se puder ajudar agradeceria. Ou quem puder ajudar!!!!

    1. Olá Fábio! Obrigado por prestigiar o material, dê uma olhada também em como criar o sistema de roteamento, em uma série de artigos aqui, dessa forma será mais fácil tratar o fluxo de dados. Sobre a sua necessidade, se entendi bem, você precisa criar um método no controller para tratar essa ação. Porém a classe modelo desse artigo é bem simples (para não tornar o entendimento completo), então nela você não tem nenhum tipo de filtro para devolver registros com base em critérios. A sugestão, se você espera implementar nesse modelo, é adicionar um filtro opcional como argumento no método all do Modelo. A assinatura do método ficaria assim: public all(array $criteria = [])
      Daí, você poderia modificar para receber parâmetros de filtragem assim (por exemplo) : [‘nome’, ‘=’, ‘João’]
      E então modificar o método para aceitar (exemplo):
      if(count($criteria) > 0) {
      $stmt = $conexao->prepare(“SELECT * FROM contatos where ” . implode(‘ ‘, $criteria) . ‘;’);
      } else {
      $stmt = $conexao->prepare(“SELECT * FROM contatos;”);
      }
      Esse aí é um caminho! Mas é claro que precisa ser melhor trabalhado, talvez as linhas acima não sejam suficientes. Na view, talvez de grade, você então criaria um form, com input para receber a palavra chave. Daí no controller no método que você adicionasse, poderia chamar algo como: $contatos = Contato::all([‘nome’, ‘like’, $request->search]);
      Tente fazer! Se der certo comente aí!

      1. Gostei da ideia de colocar três botões nos forms: Salvar, Limpar e Cancelar.
        No novo registro é legal, mas no editar o Limpar não funciona.

        Mudei esta linha do form.php:

        Limpar

        Adicionei o type=”reset”, que no Novo registro é bem legal, pois limpa o form.

  5. Olá, estou recebendo esse erro,

    Warning: call_user_func() expects parameter 1 to be a valid callback, cannot access private method ContatosController::listar() in C:\Dev\Projeto_MVC\index.php on line 34

    Alguem pode me ajudar ?

    Obg.

    1. Olá Edio, como você definiu a visibilidade na assinatura do método listar em ContatosController? Se estiver private mude para public pois ele está reclamando a falta de acesso! Se não poste o trecho do código que você criou para analisarmos.
      abraço

  6. Cara pela primeira vez consegui acompanha e fazer. Em outros tutorial eu sempre me enrolava hora na conexão com banco, hora na lógica. Parabéns, muito bem explicado.

    1. Olá Alexandre, obrigado por prestigiar o material. Essa sintaxe se refere a criação de variável dinâmica. Por exemplo, o nome da nova variável será conforme obtido de $var. As chaves determinam um placeholder, só para deixar mais organizado, mas poderia ser assim também: $$var. Eu não gosto de usar desta última maneira apresentada porque se parece a um erro de digitação. Para mim desta forma ${$var} é “concisa”. Abraço!

  7. Alexandre, parabéns! Extremamente didático. Como alguns disseram, a distribuição do MVC em pastas seria ideal, mas para o seu propósito, foi muito bom.
    Uma dúvida: se imaginarmos mais entidades na base, as funções “escapar” e “preparar” poderiam ser públicas em uma outra classe?
    Mais uma vez, parabéns pelo artigo.

    1. Olá Pedro Castro, muito obrigado por prestigiar o material também. Referente a sua dúvida, imagino que neste caso seria melhor tratar em uma abstração separada e herdar. Eu tenho um artigo sobre active record. Ele já está precisando de uma atualização, mas vai responder a sua pergunta com uma base maior. Abraço!

      1. Entendi. Mais uma dúvida: fiz alguns testes e consegui colocar um parâmetro string na função all ( function all(string $criteria=””). E na chamada do all direto está funcionando ok. Como faria para passar esta string pela função listar? Se coloco listar(string $criteria=””), na chamada do index.php apresenta a mensagem de erro “Módulo não encontrado”. Por exemplo, numa base de estados e cidades, ao clicar em um estado na lista de estados, apresentaria a lista de todas as cidades daquele estado.
        Desde já, obrigado por sua paciência.

  8. Sou iniciante e gerei o código e me aparece esse erro, não estou a conseguir solucionar, por favor alguém pode me ajudar??

    Warning: include({$arquivo}.php): Failed to open stream: No such file or directory in C:\xampp\htdocs\PWeb\Controller.php on line 21

    Warning: include(): Failed opening ‘{$arquivo}.php’ for inclusion (include_path=’C:\xampp\php\PEAR’) in C:\xampp\htdocs\PWeb\Controller.php on line 21

      1. Obrigada Alexandre.
        O primeiro erro foi superado agora está aparecer esse erro de sintaxe
        Parse error: syntax error, unexpected identifier “ob_flush” in C:\xampp\htdocs\PWeb\Controller.php on line 21

  9. Oi, Alexandre!

    Se eu tivesse outro controller, por exemplo, FuncionariosController.
    E eu quisesse pegar todas as pessoas que tivessem o email *@empresa.com.br

    Eu criaria um método getFuncionarios dentro desse controller (FuncionariosController) , um método parecido no ContatosController e criaria um __getFuncionarios dentro do model Contatos? Ou “apenas”
    criaria um método getFuncionarios dentro desse controller (FuncionariosController) e criar um __getFuncionarios dentro do model Contatos?

    Seria mais ou menos isso:

    1) FuncionariosController->getFuncionarios()->ContatosController->getFuncionarios()->Contato->__getFuncionarios()

    ou

    2) FuncionariosController->getFuncionarios()->Contato->__getFuncionarios()

    Obrigado!

    1. Olá Rodrigo! tudo bem?

      Você precisa ver o controller como “aquele cara” que não sabe necessáriamente fazer as coisas mas sabe para quem pedir ou perguntar. Antes de dizer onde quero chegar, uma diga é: quando desenvolver projetos pessoais, crie os métodos em português mesmo, com bons nomes. Por exemplo: obterTodos(), obterPorId(int $id), obterPorCriterios(array $critérios). Isso vai te ajudar muito a pensar.

      Voltando na ideia inicial, sobre o cara que sabe onde buscar o que o usuário quer. Então, você não pode dar o poder ao Controller de ele mesmo ir até o banco de dados, mas ele sabe que um Model ou um “Serviço” sabe de onde obter os dados. No seu caso, seria interessante criar um serviço para tratar o que vêm do Funcionário, tipo um FuncionarioService. Daí talvez nessa classe você poderia fazer a mesclagens entre os dados. Por exemplo, de uma maneira bem simples vou ilustrar a ideia a seguir:

      Supondo que você tenha um model Funcionario que tenha um atributo onde você armazena os Ids de contatos:

      class FuncionarioModel 
      {
            /** @var array armazena Ids dos contatos*/
            private array $contatos;
      
           . . .
       
            /**
             * @return array
             */
            public function obterContatos(): array
            {
                 return $this->contatos;
            }
          
            . . .
      }
      

      classe de serviço FuncionarioService com um método obterTodos:

      
      public function obterTodos(): array
      {
            $retorno = [];
            $funcionarios = $this->funcionarioModel->obterTodos();
      
            $retorno = array_map(function ($funcionario) {
                  $funcionario['contatos'] =  $this->obterContatosPorFuncionarios($funcionario->obterContatos());
                   return $funcionario;
            }, $funcionarios);
             
            return $retorno;
      }
      
      private function obterContatosPorFuncionarios(array $funcionarios): array
      {
            $retorno = [];
            foreach ($funcionarios as $funcionarioId) {
                 $retorno[] = $this->funcionarioModel->obterPorId($funcionarioId);
            } 
            
            return $retorno;
      }
      

      Essa é uma maneira, mas poderá desenvolver dentro do Model mesmo. O ideal é sempre trabalhar com serviços. Incluisive um padrão que alguns constuma criar é uma classe de Repositório para lidar com as coleções de dados. Funciona semelhante a essa idéia acima.

      Espero que aí lhe dê um uma boa visão para seguir.

      []’s

      1. No meu caso eu não tenho um FuncionarioModel eu “só” tenho o Model Contatos (como no seu post). E o FuncionariosController teria que pegar as infos do Contatos. Gostei mesmo da ideia do FuncionarioService. Neste caso, como o FuncionarioServece pegaria as informações do Contatos (Modelo)?

        Desde já agradeço pelo post e excelente explicação!

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.