Exemplo de MVC com PHP

Nesse artigo irei demonstrar na prática, a aplicação do padrão de projeto MVC com PHP. Para isso, construirei um aplicativo de Agenda de Contatos Telefônicos conforme requisitos funcionais, diagramas e código que serão mostrados ao longo desse texto. Mas, antes de começar, aconselho a vocês que leiam o artigo de introdução a MVC, o mesmo poderá ser visualizado acessando o link Entendendo o MVC (Model-View-Controller).

Hierarquia de Diretórios

Conforme a Imagem 1, irei seguir um padrão de hierarquia de diretórios e arquivos, vou explicar sobre as responsabilidades dos diretórios e arquivos logo abaixo.

Imagem 1 – Hierarquia de Diretórios e Pastas

Diretório “controllers”

Conforme se pode deduzir pelo nome, irei usá-lo para guardar as classes da camada de controle do sistema, as famosas classes controladoras ou controllers, responsáveis por fazer o intermédio entre a camada de dados (models ou modelos) e visualização (views), conforme o artigo de introdução a MVC citado acima.

Diretório “databases”

Por se tratar de um projeto apenas de exemplo, irei persistir os dados dos contatos e telefones usando um banco de dados SQLite, é nesse diretório que o arquivo referente ao banco ficará.

Caso queria conectar a aplicação com o MySQL, veja como no fim do artigo

Diretório “lib”

Nesse diretório, irei guardar as classes diretamente ligadas ao sistema, como por exemplo, classes de filtros de dados, validações genéricas, helpers (caso haja algum), interfaces e abstrações não ligadas à camada de negócio do sistema. Se estivesse construindo um framework em PHP, guardaria as classes do mesmo nesse diretório.

Diretório “models”

Aqui, guardarei as classes de dados diretamente abstraídas e ligadas às regras de negócio do sistema, como por exemplo, as classes Contato e Telefone, entre outras.

Diretório “views”

Esse é o diretório onde guardarei os arquivos HTML do sistema, tais arquivos representam a camada de visualização (view), da qual foi falada no artigo de introdução ao MVC.

Arquivo “index.php”

É um arquivo índex como qualquer outro, é nele que a execução do sistema irá começar.

Implementação das Classes das Camadas

Classes Primárias ou Genéricas

Depois de todos os diretórios explicados, mostrarei a implementação de algumas classes necessárias para a separação do sistemas em camadas (Modelo – Model, Visão – View , Controlador – Controller). A primeira delas que mostrarei, é a de controlador genérico, não diretamente ligado às regras de negócio do sistema de Agenda Telefônica. Veja o exemplo abaixo.

<?php
/**
* Controlador que deverá ser chamado quando não for
* especificado nenhum outro 
* 
* @package Exemplo simples com MVC
* @author DigitalDev
* @version 0.1.1
* 
* Camada - Controladores ou Controllers
* Diretório Pai - controllers 
* Arquivo - IndexController.php
*/
class IndexController
{
	/**
	* Ação que deverá ser executada quando 
	* nenhuma outra for especificada, do mesmo jeito que o
	* arquivo index.html ou index.php é executado quando nenhum
	* é referenciado
	*/
	public function indexAction()
	{
		//redirecionando para a pagina de lista de contatos
		header('Location: ?controle=Contato&acao=listarContato');
	}
}
?>

Conforme vocês devem ter visto acima, usei o sufixo Controller no nome da classe e Action no nome do método. Esse é o padrão de nomenclatura que irei utilizar em todas as outras classes da camada Controle e seus métodos. Além disso, nomearei os arquivos que irão conter as classes com o mesmo nome da própria classe.

A segunda classe que implementarei será a Application. A responsabilidade da mesma será a de verificar qual classe da camada de controle (Controller) e qual método da classe (Action) o usuário deseja executar. Dê uma olhada no código abaixo, espero que ele seja simples o suficiente para esclarecer seu modo de funcionamento.

<?php
/**
* Camada - Controller
* Diretório Pai - lib
* Arquivo - Application.php
**/

/**
* Essa função garante que todas as classes 
* da pasta lib serão carregadas automaticamente
*/
function __autoload($st_class)
{
	if(file_exists('lib/'.$st_class.'.php'))
		require_once 'lib/'.$st_class.'.php';
}

/**
* Verifica qual classe controlador (Controller) o usuário deseja chamar
* e qual método dessa classe (Action) deseja executar
* Caso o controlador (controller) não seja especificado, o IndexControllers será o padrão
* Caso o método (Action) não seja especificado, o indexAction será o padrão
* 
* @package Exemplo simples com MVC
* @author DigitalDev
* @version 0.1.1
**/
class Application
{
    /**
    * Usada pra guardar o nome da classe
    * de controle (Controller) a ser executada
    * @var string
    */
    protected $st_controller;
 
    /**
    * Usada para guardar o nome do metodo da
    * classe de controle (Controller) que deverá ser executado
    * @var string
    */
    protected $st_action;
 
    /**
    * Verifica se os parâmetros de controlador (Controller) e ação (Action) foram
    * passados via parâmetros "Post" ou "Get" e os carrega tais dados
    * nos respectivos atributos da classe
    */
    private function loadRoute()
    {
        /*
        * Se o controller nao for passado por GET,
        * assume-se como padrão o controller 'IndexController';
        */
        $this->st_controller = isset($_REQUEST['controle']) ?  $_REQUEST['controle'] : 'Index';
 
        /*
        * Se a action nao for passada por GET,
        * assume-se como padrão a action 'IndexAction';
        */
        $this->st_action = isset($_REQUEST['acao']) ?  $_REQUEST['acao'] : 'index';
    }
 
    /**
    *
    * Instancia classe referente ao Controlador (Controller) e executa
    * método referente e  acao (Action)
    * @throws Exception
    */
    public function dispatch()
    {
        $this->loadRoute();
 
        //verificando se o arquivo de controle existe
        $st_controller_file = 'controllers/'.$this->st_controller.'Controller.php';
        if(file_exists($st_controller_file))
            require_once $st_controller_file;
        else
            throw new Exception('Arquivo '.$st_controller_file.' nao encontrado');
 
        //verificando se a classe existe
        $st_class = $this->st_controller.'Controller';
        if(class_exists($st_class))
            $o_class = new $st_class;
        else
            throw new Exception("Classe '$st_class' nao existe no arquivo '$st_controller_file'");
 
        //verificando se o metodo existe
        $st_method = $this->st_action.'Action';
        if(method_exists($o_class,$st_method))
            $o_class->$st_method();
        else
            throw new Exception("Metodo '$st_method' nao existe na classe $st_class'");
    }
 
    /**
    * Redireciona a chamada http para outra página
    * @param string $st_uri
    */
    static function redirect( $st_uri )
    {
        header("Location: $st_uri");
    }
}
?>

Apesar da classe acima não ter semelhança alguma com a classe IndexController que escrevi, ela também faz parte da camada de controle (Controller), mas está num nível mais acima. O fato dela ser responsável por verificar qual controlador (Controller) e qual método (Action) irá executar, faz da mesma o coração do sistema.

A terceira classe que irei implementar será responsável por cuidar da camada de visualização. Apesar de código um pouco extenso, ela é uma classe sem muitas funcionalidade, mas poderá ser enriquecida de acordo com nossas necessidades futuras.

<?php
/**
* Essa classe é responsável por renderizar os arquivos HTML
* 
* @package Exemplo simples com MVC
* @author DigitalDev
* @version 0.1.1
* 
* Diretório Pai - lib
* Arquivo - View.php 
*/
class View
{
    /**
    * Armazena o conteúdo HTML
    * @var string
    */
    private $st_contents;
     
    /**
    * Armazena o nome do arquivo de visualização
    * @var string
    */
    private $st_view;
     
    /**
    * Armazena os dados que devem ser mostrados ao reenderizar o 
    * arquivo de visualização
    * @var Array
    */
    private $v_params;
     
    /**
    * É possivel efetuar a parametrização do objeto ao instanciar o mesmo,
    * $st_view é o nome do arquivo de visualização a ser usado e 
    * $v_params são os dados que devem ser utilizados pela camada de visualização
    * 
    * @param string $st_view
    * @param Array $v_params
    */
    function __construct($st_view = null, $v_params = null) 
    {
        if($st_view != null)
            $this->setView($st_view);
        $this->v_params = $v_params;
    }   
     
    /**
    * Define qual arquivo html deve ser renderizado
    * @param string $st_view
    * @throws Exception
    */
    public function setView($st_view)
    {
        if(file_exists($st_view))
            $this->st_view = $st_view;
        else
            throw new Exception("View File '$st_view' don't exists");       
    }
     
    /**
    * Retorna o nome do arquivo que deve ser renderizado
    * @return string 
    */
    public function getView()
    {
        return $this->st_view;
    }
     
    /**
    * Define os dados que devem ser repassados à view
    * @param Array $v_params
    */
    public function setParams(Array $v_params)
    {
        $this->v_params = $v_params; 
    }
     
    /**
    * Retorna os dados que foram ser repassados ao arquivo de visualização
    * @return Array
    */
    public function getParams()
    {
        return $this->v_params;
    }
     
    /**
    * Retorna uma string contendo todo 
    * o conteudo do arquivo de visualização
    * 
    * @return string
    */
    public function getContents()
    {
        ob_start();
        if(isset($this->st_view))
            require_once $this->st_view;
        $this->st_contents = ob_get_contents();
        ob_end_clean();
        return $this->st_contents;   
    }
     
    /**
    * Imprime o arquivo de visualização 
    */
    public function showContents()
    {
        echo $this->getContents();
        exit;
    }
}
?>

Os dados de algumas classes da camada de modelo deverão persistir no banco de dados, para isso, será necessário a implementação de pequeno bloco de código responsável pela conexão entre o sistema e o SGDB. A alternativa que adotarei será a de implementar uma classe abstrata, que deverá ser herdada pelas classes em que os dados deverão ser armazenados no banco de dados. Veja código da classe abstrata a seguir.

<?php
 /**
 * Classe Abstrata responsável por centralizar a conexão
 * com o banco de dados
 * 
 * @package Exemplo simples com MVC
 * @author DigitalDev
 * @version 0.1.1
 *  
 * Diretório Pai - lib
 * Arquivo - PersistModelAbstract.php
 */
abstract class PersistModelAbstract
{
    /**
    * Variável responsável por guardar dados da conexão do banco
    * @var resource
    */
    protected $o_db;
     
    function __construct()
    {
        
    	// Inicio de conexão com SQLite     
        $this->o_db = new PDO("sqlite:./databases/db.sq3");
        $this->o_db->setAttribute ( PDO::ATTR_ERRMODE , PDO::ERRMODE_EXCEPTION );
        // Fim de conexão com SQLite
         
        
        /*
		//Inicio de conexão com MySQL 
		$st_host = 'ip ou host';
		$st_banco = 'bancodedados';
		$st_usuario = 'usuario';
		$st_senha = 'senha';
		
		 
		$st_dsn = "mysql:host=$st_host;dbname=$st_banco"; 
		$this->o_db = new PDO
		(
			$st_dsn,
			$st_usuario,
			$st_senha
		);
		//Fim de conexão com MySQL
		*/ 	
    }
}
?>

Conforme Vocês já devem ter visto, a classe PersistModelAbstract faz referência ao arquivo db.sq3 dentro do diretório databases, esse arquivo será o responsável por guardar nossos dados. Irei mostrar isso na prática com o continuar desse artigo.

Implementarei também, duas outras pequenas classes, a primeira será usada para filtrar os dados passados via POST e GET e a segunda será usada para validar os dados. Irei chamá-las de DataFilter e DataValidator, respectivamente.

<?php
 /**
 * Classe designada a filtragem de dados
 * 
 * @package Exemplo simples com MVC
 * @author DigitalDev
 * @version 0.1.1
 * 
 * Diretório Pai - lib
 * Arquivo - DataFilter.php
 */
class DataFilter
{
    /**
    * Retira pontuacao da string 
    * @param string $st_data
    * @return string
    */
    static function alphaNum( $st_data )
    {
        $st_data = preg_replace("([[:punct:]]| )",'',$st_data);
        return $st_data;
    }
     
    /**
    * Retira caracteres nao numericos da string
    * @param string $st_data
    * @return string
    */
    static function numeric( $st_data )
    {
        $st_data = preg_replace("([[:punct:]]|[[:alpha:]]| )",'',$st_data);
        return $st_data;    
    }
     
     
    /**
     * 
     * Retira tags HTML / XML e adiciona "\" antes
     * de aspas simples e aspas duplas
     * @param string $st_string
     */
    static function cleanString( $st_string )
    {
        return addslashes(strip_tags($st_string));
    }
}
?>

 

<?php
 /**
 * Classe designada a validacao de formato de dados
 * 
 * @package Exemplo simples com MVC
 * @author DigitalDev
 * @version 0.1.1
 * 
 * Diretório Pai - lib
 * Arquivo - DataValidator.php
 */
class DataValidator
{
    /**
    * Verifica se o dado passado esta vazio
    * @param mixed $mx_value
    * @return boolean
    */
    static function isEmpty( $mx_value )
    {
        if(!(strlen($mx_value) > 0))
            return true;    
        return false;
    }
     
    /**
    * Verifica se o dado passado e um numero
    * @param mixed $mx_value;
    * @return boolean
    */
    static function isNumeric( $mx_value )
    {
        $mx_value = str_replace(',', '.', $mx_value);
        if(!(is_numeric($mx_value)))
            return false;
        return true;
    }
     
    /**
    * Verifica se o dado passado e um numero inteiro
    * @param mixed $mx_value;
    * @return boolean
    */
    static function isInteger( $mx_value )
    {
        if(!DataValidator::isNumeric($mx_value))
            return false;
         
        if(preg_match('/[[:punct:]&^-]/', $mx_value) > 0)
            return false;
        return true;
    }
}
?>

Por fim, irei implementar o código do arquivo “index.php”, o mesmo será extremamente simples. Veja abaixo.

<?php
/**
* Primeiro arquivo a ser executado.
* É aqui que tudo começa 
* 
* @package Exemplo simples com MVC
* @author DigitalDev
* @version 0.1.1
* 
* Diretório Pai - raiz do site
* Arquivo - index.php
*/
//configurando o PHP para mostrar os erros na tela
ini_set('display_errors', 1);

//configurando o PHP para reportar todo e qualquer erro
error_reporting(E_ALL);

require_once 'lib/Application.php';
$o_Application = new Application();
$o_Application->dispatch();
?>

Nesse estágio de desenvolvimento, a hierarquia de diretórios e arquivos deve se dar como a Imagem 2 mostrada abaixo.


Imagem 2 – Hierarquia de Diretórios e Pastas

A Camada de Negócios

Agora, com as classes do sistema implementadas, irei finalmente me preocupar com as regras de negócio da Agenda Telefônica. Veja a documentação abaixo.

Lista de requisitos

  • Permitir ao usuário visualizar a lista de contatos na tela principal do sistema.
  • Permitir ao usuário administrar o cadastro de contatos.
  • Permitir ao usuário,  selecionar o contato e visualizar os telefones do mesmo.
  • Permitir ao usuário administrar o cadastro de telefones do contato selecionado.
  • Permitir ao usuário cadastrar “n” telefones para o contato selecionado.

Diagrama de Casos de Uso

Diagrama de Casos de Uso

Imagem 3 – Diagrama de Casos de Uso

Diagrama de Classe

Nesse diagrama, apenas documentarei as classes diretamente ligadas ao sistema de Agenda Telefônica, a diagramação das classes que implementei acima não será abordada.

Diagrama de Classes

Imagem 4 – Diagrama de Classes

Diagrama de Entidade Relacionamento

Pelo fato da necessidade da persistir os dados de contato e dos telefones, será necessário duas tabelas para guardar os dados das classes. Veja o Diagrama de Entidade Relacionamento (DER) abaixo.

Diagrama de Entidade Relacionamento

Imagem 5 – Diagrama de Entidade Relacionamento

Agora que já tenho a arquitetura da camada de negócio já definida, o proximo passo é implementar as classes responsáveis por gerenciar as mesmas. Isso mesmo, estou me referendo às classes “Contato” e “Telefone”, e como eu já havia falado, os dados das mesmas devem persistir no banco de dados, por esse motivo, elas devem herdar a classe PersistModelAbstract.

<?php
//incluindo o arquivo contendo a classe TelefoneModel
require_once 'models/TelefoneModel.php';
 
/**
* Responsável por gerenciar e persistir os dados dos  
* Contatos da Agenda Telefônica 
* 
* @package Exemplo simples com MVC 
* @author DigitalDev 
* @version 0.1.1 
*  
* Camada - Modelo ou Model.
* Diretório Pai - models  
* Arquivo - ContatoModel.php
**/
class ContatoModel extends PersistModelAbstract
{
    private $in_id;
    private $st_nome;
    private $st_email;
     
    function __construct()
    {
        parent::__construct();
        //executa método de criação da tabela de Telefone
        $this->createTableContato();
    }
     
     
    /**
     * Setters e Getters da
     * classe ContatoModel
     */
     
    public function setId( $in_id )
    {
        $this->in_id = $in_id;
        return $this;
    }
     
    public function getId()
    {
        return $this->in_id;
    }
     
    public function setNome( $st_nome )
    {
        $this->st_nome = $st_nome;
        return $this;
    }
     
    public function getNome()
    {
        return $this->st_nome;
    }
     
    public function setEmail( $st_email )
    {
        $this->st_email = $st_email;
        return $this;
    }
     
    public function getEmail()
    {
        return $this->st_email;
    }
     
    /**
    * Retorna um array contendo os contatos
    * @param string $st_nome
    * @return Array
    */
    public function _list( $st_nome = null )
    {
        if(!is_null($st_nome))
            $st_query = "SELECT * FROM tbl_contato WHERE con_st_nome LIKE '%$st_nome%';";
        else
            $st_query = 'SELECT * FROM tbl_contato;';   
         
        $v_contatos = array();
        try
        {
            $o_data = $this->o_db->query($st_query);
            while($o_ret = $o_data->fetchObject())
            {
                $o_contato = new ContatoModel();
                $o_contato->setId($o_ret->con_in_id);
                $o_contato->setNome($o_ret->con_st_nome);
                $o_contato->setEmail($o_ret->con_st_email);
                array_push($v_contatos, $o_contato);
            }
        }
        catch(PDOException $e)
        {}              
        return $v_contatos;
    }
     
    /**
    * Retorna os dados de um contato referente
    * a um determinado Id
    * @param integer $in_id
    * @return ContatoModel
    */
    public function loadById( $in_id )
    {
        $v_contatos = array();
        $st_query = "SELECT * FROM tbl_contato WHERE con_in_id = $in_id;";
        $o_data = $this->o_db->query($st_query);
        $o_ret = $o_data->fetchObject();
        $this->setId($o_ret->con_in_id);
        $this->setNome($o_ret->con_st_nome);
        $this->setEmail($o_ret->con_st_email);        
        return $this;
    }
     
    /**
    * Salva dados contidos na instancia da classe
    * na tabela de contato. Se o ID for passado,
    * um UPDATE será executado, caso contrário, um
    * INSERT será executado
    * @throws PDOException
    * @return integer
    */
    public function save()
    {
        if(is_null($this->in_id))
            $st_query = "INSERT INTO tbl_contato
                        (
                            con_st_nome,
                            con_st_email
                        )
                        VALUES
                        (
                            '$this->st_nome',
                            '$this->st_email'
                        );";
        else
            $st_query = "UPDATE
                            tbl_contato
                        SET
                            con_st_nome = '$this->st_nome',
                            con_st_email = '$this->st_email'
                        WHERE
                            con_in_id = $this->in_id";
        try
        {
             
            if($this->o_db->exec($st_query) > 0)
                if(is_null($this->in_id))
                {
                   /*
					* verificando se o driver usado é sqlite e pegando o ultimo id inserido
					* por algum motivo, a função nativa do PDO::lastInsertId() não funciona com sqlite
					*/
					if($this->o_db->getAttribute(PDO::ATTR_DRIVER_NAME) === 'sqlite')
					{
						$o_ret = $this->o_db->query('SELECT last_insert_rowid() AS con_in_id')->fetchObject();
						return $o_ret->con_in_id;
					}
					else
						return $this->o_db->lastInsertId();
                }
                else
                    return $this->in_id;
        }
        catch (PDOException $e)
        {
            throw $e;
        }
        return false;               
    }
 
    /**
    * Deleta os dados persistidos na tabela de
    * contato usando como referencia, o id da classe.
    */
    public function delete()
    {
        if(!is_null($this->in_id))
        {
            $st_query = "DELETE FROM
                            tbl_contato
                        WHERE con_in_id = $this->in_id";
            if($this->o_db->exec($st_query) > 0)
                return true;
        }
        return false;
    }
     
    /**
    * Cria tabela para armazernar os dados de contato, caso
    * ela ainda não exista.
    * @throws PDOException
    */
    private function createTableContato()
    {
        /*
		* No caso do Sqlite, o AUTO_INCREMENT é automático na chave primaria da tabela
		* No caso do MySQL, o AUTO_INCREMENT deve ser especificado na criação do campo
		*/
		if($this->o_db->getAttribute(PDO::ATTR_DRIVER_NAME) === 'sqlite')
			$st_auto_increment = '';
		else
			$st_auto_increment = 'AUTO_INCREMENT';
		
		$st_query = "CREATE TABLE IF NOT EXISTS tbl_contato
					(
						con_in_id INTEGER NOT NULL $st_auto_increment,
						con_st_nome CHAR(200),
						con_st_email CHAR(100),
						PRIMARY KEY(con_in_id)
					)";

    	//executando a query;
        try
       {
            $this->o_db->exec($st_query);
        }
        catch(PDOException $e)
        {
            throw $e;
        }   
    }
}
?>

Agora é a vez da classe TelefoneModel ser implementada.

<?php
/**
 * Responsável por gerenciar e persistir os dados de telefones dos
 * Contatos da Agenda Telefonica
 * 
 * @package Exemplo simples com MVC
 * @author DigitalDev
 * @version 0.1.1
 * 
 * Camada - Models ou Modelo
 * Diretório Pai - models
 * Arquivo - TelefoneModel.php
 */
class TelefoneModel extends PersistModelAbstract
{
    private $in_id;
    private $in_ddd;
    private $in_telefone;
    private $in_contato_id;
     
    function __construct()
    {
        parent::__construct();
         
        //executa método de criação da tabela de Telefone
        $this->createTableTelefone();
    }
     
     
    /**
     * Setters e Getters da
     * classe TelefoneModel
     */
     
    public function setId( $in_id )
    {
        $this->in_id = $in_id;
        return $this;
    }
     
    public function getId()
    {
        return $this->in_id;
    }
     
    public function setDDD( $in_ddd )
    {
        $this->in_ddd = $in_ddd;
        return $this;
    }
     
    public function getDDD()
    {
        return $this->in_ddd;
    }
     
    public function setTelefone( $in_telefone )
    {
        $this->in_telefone = $in_telefone;
        return $this;
    }
     
    public function getTelefone()
    {
        return $this->in_telefone;
    }
     
    public function setContatoId( $in_contato_id )
    {
        $this->in_contato_id = $in_contato_id;
        return $this;
    }
     
    public function getContatoId()
    {
        return $this->in_contato_id;
    }
     
     
    /**
    * Retorna um array contendo os telefones
    * de um determinado contato
    * @param integer $in_contato_id
    * @return Array
    */
    public function _list( $in_contato_id )
    {
        $st_query = "SELECT * FROM tbl_telefone WHERE con_in_id = $in_contato_id";
        $v_telefones = array();
        try
        {
            $o_data = $this->o_db->query($st_query);
            while($o_ret = $o_data->fetchObject())
            {
                $o_telefone = new TelefoneModel();
                $o_telefone->setId($o_ret->tel_in_id);
                $o_telefone->setDDD($o_ret->tel_in_ddd);
                $o_telefone->setTelefone($o_ret->tel_in_telefone);
                $o_telefone->setContatoId($o_ret->con_in_id);
                array_push($v_telefones,$o_telefone);
            }
        }
        catch(PDOException $e)
        {}              
        return $v_telefones;
    }
     
    /**
    * Retorna os dados de um telefone referente
    * a um determinado Id
    * @param integer $in_id
    * @return TelefoneModel
    */
    public function loadById( $in_id )
    {
        $v_contatos = array();
        $st_query = "SELECT * FROM tbl_telefone WHERE tel_in_id = $in_id;";
        try
        {
            $o_data = $this->o_db->query($st_query);
            $o_ret = $o_data->fetchObject();
            $this->setId($o_ret->tel_in_id);
            $this->setDDD($o_ret->tel_in_ddd);
            $this->setTelefone($o_ret->tel_in_telefone);
            $this->setContatoId($o_ret->con_in_id);
            return $this;
        }
        catch(PDOException $e)
        {}
        return false;   
    }
     
    /**
    * Salva dados contidos na instancia da classe
    * na tabela de telefone. Se o ID for passado,
    * um UPDATE será executado, caso contrário, um
    * INSERT será executado
    * @throws PDOException
    * @return integer
    */
    public function save()
    {
        if(is_null($this->in_id))
            $st_query = "INSERT INTO tbl_telefone
                        (
                            con_in_id,
                            tel_in_ddd,
                            tel_in_telefone
                        )
                        VALUES
                        (
                            $this->in_contato_id,
                            '$this->in_ddd',
                            '$this->in_telefone'
                        );";
        else
            $st_query = "UPDATE
                            tbl_telefone
                        SET
                            tel_in_ddd = '$this->in_ddd',
                            tel_in_telefone = '$this->in_telefone'
                        WHERE
                            tel_in_id = $this->in_id";
        try
        {
             
            if($this->o_db->exec($st_query) > 0)
                if(is_null($this->in_id))
                {
                   /*
					* verificando se o driver usado é sqlite e pegando o ultimo id inserido
					* por algum motivo, a função nativa do PDO::lastInsertId() não funciona com sqlite
					*/
					if($this->o_db->getAttribute(PDO::ATTR_DRIVER_NAME) === 'sqlite')
					{
						$o_ret = $this->o_db->query('SELECT last_insert_rowid() AS tel_in_id')->fetchObject();
						return $o_ret->tel_in_id;
					}
					else
						return $this->o_db->lastInsertId();
                }
                else
                    return $this->in_id;
        }
        catch (PDOException $e)
        {
            throw $e;
        }
        return false;               
    }
 
    /**
    * Deleta os dados persistidos na tabela de
    * telefone usando como referencia, o id da classe.
    */
    public function delete()
    {
        if(!is_null($this->in_id))
        {
            $st_query = "DELETE FROM
                            tbl_telefone
                        WHERE tel_in_id = $this->in_id";
            if($this->o_db->exec($st_query) > 0)
                return true;
        }
        return false;
    }
     
     
     
    /**
    * Cria tabela para armazernar os dados de telefone, caso
    * ela ainda não exista.
    * @throws PDOException
    */
    private function createTableTelefone()
    {
        /*
		* No caso do Sqlite, o AUTO_INCREMENT é automático na chave primaria da tabela
		* No caso do MySQL, o AUTO_INCREMENT deve ser especificado na criação do campo
		*/
		if($this->o_db->getAttribute(PDO::ATTR_DRIVER_NAME) === 'sqlite')
			$st_auto_increment = '';
		else
			$st_auto_increment = 'AUTO_INCREMENT';
		
		
		$st_query = "CREATE TABLE IF NOT EXISTS tbl_telefone
					(
						tel_in_id INTEGER NOT NULL $st_auto_increment,
						con_in_id INTEGER NOT NULL,
						tel_in_ddd CHAR(5),
						tel_in_telefone CHAR(12),
						PRIMARY KEY(tel_in_id)
					)";
		
        //executando a query;
        try
        {
            $this->o_db->exec($st_query);
        }
        catch(PDOException $e)
        {
            throw $e;
        }   
    }
}
?>

Depois das classes da camada de modelo implementadas, o próximo passo é escrever o código das classes de controle referente ao fluxo de gerenciamento das classes acima. Elas também serão duas, ContatoController e TelefoneController.

<?php
//incluindo classes da camada Model
require_once 'models/ContatoModel.php'; 
 
/**
* Responsável por gerenciar o fluxo de dados entre
* a camada de modelo e a de visualização
*  
* @package Exemplo simples com MVC 
* @author DigitalDev 
* @version 0.1.1 
*
* Camada - Controladores ou Controllers
* Diretório Pai - controllers
* Arquivo - ContatoController.php
*
*/
class ContatoController
{
    /**
    * Efetua a manipulação dos modelos necessários
    * para a aprensentação da lista de contatos
    */
    public function listarContatoAction()
    {
        $o_Contato = new ContatoModel();
         
        //Listando os contatos cadastrados
        $v_contatos = $o_Contato->_list();
         
        //definindo qual o arquivo HTML que será usado para
        //mostrar a lista de contatos
        $o_view = new View('views/listarContatos.phtml');
         
        //Passando os dados do contato para a View
        $o_view->setParams(array('v_contatos' => $v_contatos));
         
        //Imprimindo código HTML
        $o_view->showContents();
    }
     
     
    /**
    * Gerencia a requisiçães de criação
    * e edição dos contatos 
    */
    public function manterContatoAction()
    {
        $o_contato = new ContatoModel();
         
        //verificando se o id do contato foi passado
        if( isset($_REQUEST['in_con']) )
            //verificando se o id passado é valido
            if( DataValidator::isNumeric($_REQUEST['in_con']) )
                //buscando dados do contato
                $o_contato->loadById($_REQUEST['in_con']);
             
        if(count($_POST) > 0)
        {
            $o_contato->setNome(DataFilter::cleanString($_POST['st_nome']));
            $o_contato->setEmail(DataFilter::cleanString($_POST['st_email']));
             
            //salvando dados e redirecionando para a lista de contatos
            if($o_contato->save() > 0)
                Application::redirect('?controle=Contato&acao=listarContato');
        }
             
        $o_view = new View('views/manterContato.phtml');
        $o_view->setParams(array('o_contato' => $o_contato));
        $o_view->showContents();
    }
     
    /**
    * Gerencia a requisições de exclusão dos contatos
    */
    public function apagarContatoAction()
    {
        if( DataValidator::isNumeric($_GET['in_con']) )
        {
            //apagando o contato
            $o_contato = new ContatoModel();
            $o_contato->loadById($_GET['in_con']);
            $o_contato->delete();
             
            //Apagando os telefones do contato
            $o_telefone = new TelefoneModel();
            $v_telefone = $o_telefone->_list($_GET['in_con']);
            foreach($v_telefone AS $o_telefone)
                $o_telefone->delete();
            Application::redirect('?controle=Contato&acao=listarContato');
        }   
    }
}
?>

e por sua vez a classe TelefoneController

<?php
//incluindo classes da camada Model
require_once 'models/TelefoneModel.php';
require_once 'models/ContatoModel.php';

/**
* Responsável por gerenciar o fluxo de dados entre
* a camada de modelo e a de visualização
* 
* @package Exemplo simples com MVC 
* @author DigitalDev 
* @version 0.1.1 
* 
* Camada - Controladores ou Controllers
* Diretório Pai - controllers
* Arquivo - TelefoneController.php
*/
class TelefoneController
{
	
    /**
    * Efetua a manipulação dos modelos necessários
    * para a aprensentação da lista de telefones do contato
    */
	public function listarTelefonesAction()
	{
		if( isset($_REQUEST['in_con']) )
			if( DataValidator::isNumeric($_REQUEST['in_con']) )
			{
				$o_contato = new ContatoModel();
				$o_contato->loadById($_REQUEST['in_con']);
				
				$o_telefone = new TelefoneModel();
				$v_telefones = $o_telefone->_list($_GET['in_con']);
				$o_view = new View('views/listarTelefones.phtml');
				$o_view->setParams(array('o_contato' => $o_contato,'v_telefones' => $v_telefones));
				$o_view->showContents();
			}
	}
	
	/**
    * Gerencia a requisiçães de criação
    * e edição dos telefones do contato 
    */
	public function manterTelefoneAction()
	{
		$o_contato = new ContatoModel();
		$o_telefone = new TelefoneModel();
		
		if( isset($_REQUEST['in_con']) )
			if( DataValidator::isInteger($_REQUEST['in_con']) )
				$o_contato->loadById($_REQUEST['in_con']);
			
		if( isset($_REQUEST['in_tel']) )
			if( DataValidator::isInteger($_REQUEST['in_tel']) )
				$o_telefone->loadById($_REQUEST['in_tel']);
				
		if(count($_POST) > 0)
		{
			$o_telefone->setDDD(DataFilter::numeric($_POST['in_ddd']));
			$o_telefone->setTelefone(DataFilter::numeric($_POST['in_telefone']));
			$o_telefone->setContatoId($o_contato->getId());
			if($o_telefone->save() > 0)
				Application::redirect('?controle=Telefone&acao=listarTelefones&in_con='.$o_contato->getId());
		}
			
		$o_view = new View('views/manterTelefone.phtml');
		$o_view->setParams(array('o_contato' => $o_contato,'o_telefone' => $o_telefone));
		$o_view->showContents();
	}
	
	/**
    * Gerencia a requisições de exclusão de telefones do contato
    */
	public function apagarTelefoneAction()
	{
		if( isset($_GET['in_tel']) )
			if( DataValidator::isInteger($_GET['in_tel']))
			{
				$o_telefone = new TelefoneModel();
				$o_telefone->loadById($_GET['in_tel']);
				$o_telefone->delete();
				Application::redirect('?controle=Telefone&acao=listarTelefones&in_con='.$_GET['in_con']);
			}	
	}
}
?>	

Arquivos de visualização ou views

As classes implementadas acima estão fazendo referência à arquivos HTML contidos na camada de visualização, para esse programa funcionar, será preciso implementá-los. Isso é o que irei fazer agora.

Arquivo – listarContatos.phtml
Diretório Pai – views

<?php
$v_params = $this->getParams();
$v_contatos = $v_params['v_contatos'];
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>
        Agenda Telef&ocirc;nica - Exemplo de MVC com PHP
    </title>
    <link rel="stylesheet" type="text/css" href="template/css/default.css" />
</head>
<body>
    <div align="center">
        <table width="80%" border="1">
            <tr>
                <th>
                    ID
                </th>
                <th>
                    Nome
                </th>
                <th>
                    E-mail
                </th>
                <th colspan="3">
                    A&ccedil;&otilde;es
                </th>
            </tr>
            <?php
            foreach($v_contatos AS $o_contato)
            {
                ?>
                <tr>
                    <td>
                        <?php echo $o_contato->getId()?>
                    </td>
                    <td>
                        <?php echo $o_contato->getNome()?>
                    </td>
                    <td>
                        <?php echo $o_contato->getEmail()?>
                    </td>
                    <td align="center">
                        <a href='?controle=Telefone&acao=listarTelefones&in_con=<?php echo $o_contato->getId()?>'>Telefones</a>
                    </td>
                    <td align="center">
                        <a href='?controle=Contato&acao=manterContato&in_con=<?php echo $o_contato->getId()?>'>Editar</a>
                    </td>
                    <td align="center">
                        <a href='?controle=Contato&acao=apagarContato&in_con=<?php echo $o_contato->getId()?>'>Apagar</a>
                    </td>
                </tr>
                <?php
            }
            ?>
        </table>
        <br />
        <a href='?controle=Contato&acao=manterContato'>Novo Contato</a>
    </div>
</body>
</html>

Arquivo – listarTelefones.phtml
Diretório Pai – views

<?php
$v_params = $this->getParams();
$o_contato = $v_params['o_contato'];
$v_telefones = $v_params['v_telefones'];
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>
        Agenda Tele&ocirc;nica - Exemplo de MVC com PHP
    </title>
    <link rel="stylesheet" type="text/css" href="template/css/default.css" />
</head>
<body>
    <div align="center">
        <table width="80%" border="1">
            <tr>
                <th colspan='3'>
                    Contato
                </th>
            </tr>
            <tr>
                <th>
                    ID
                </th>
                <th>
                    Nome
                </th>
                <th>
                    E-mail
                </th>
            </tr>
            <tr>
                <td>
                    <?php echo $o_contato->getId()?>
                </td>
                <td>
                    <?php echo $o_contato->getNome()?>
                </td>
                <td>
                    <?php echo $o_contato->getEmail()?>
                </td>
            </tr>
        </table>
        <br />
        <table width="50%" border="1">
            <tr>
                <th colspan="5">
                    Telefones
                </th>
            </tr>
            <tr>
                <th>
                    ID
                </th>
                <th>
                    DDD
                </th>
                <th>
                    Telefone
                </th>
                <th colspan="2">
                    A&ccedil;&otilde;es
                </th>
            </tr>
            <?php
            foreach($v_telefones AS $o_telefone)
            { 
                ?>
                <tr>
                    <td align="center">
                        <?php echo $o_telefone->getId()?>
                    </td>
                    <td align="center">
                        <?php echo $o_telefone->getDDD()?>
                    </td>
                    <td align="center">
                        <?php echo $o_telefone->getTelefone()?>
                    </td>
                    <td align="center">
                        <a href='?controle=Telefone&acao=apagarTelefone&in_con=<?php echo $o_contato->getId()?>&in_tel=<?php echo $o_telefone->getId()?>'>Apagar</a>
                    </td>
                    <td align="center">
                        <a href='?controle=Telefone&acao=manterTelefone&in_con=<?php echo $o_contato->getId()?>&in_tel=<?php echo $o_telefone->getId()?>'>Editar</a>
                    </td>
                </tr>
                <?php
            } 
            ?>
        </table>
        <br />
        <a href='?controle=Index'>Voltar</a>
        <a href='?controle=Telefone&acao=manterTelefone&in_con=<?php echo $o_contato->getId()?>'>Novo Telefone</a>
    </div>
</body>
</html>

Arquivo – manterContato.phtml
Diretório Pai – views

<?php
$v_params = $this->getParams();
$o_contato = $v_params['o_contato'];
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>
        Agenda Telef&ocirc;nica - Exemplo de MVC com PHP
    </title>
    <link rel="stylesheet" type="text/css" href="template/css/default.css" />
</head>
<body>
    <div align="center">
        <form method='post'>
        <table width="300" border="1">
            <tr>
                <th>
                    Nome
                </th>
                <th>
                    E-mail
                </th>
                <th colspan="2">
                    A&ccedil;&otilde;es
                </th>
            </tr>
            <tr>
                <td>
                    <input type='text' name='st_nome' value='<?php echo $o_contato->getNome()?>'> 
                </td>
                <td>
                    <input type='text' name='st_email' value='<?php echo $o_contato->getEmail()?>'>
                </td>             
                <td align="center">
                    <a href='?controle=Contato&acao=listarContato'>Cancelar</a>
                </td>
                <td align="center">
                    <input type='hidden' name='controle' value='Contato'>
                    <input type='hidden' name='acao' value='manterContato'>
                    <input type='hidden' name='in_con' value='<?php echo $o_contato->getId()?>'>
                    <button type='submit'>Salvar</button>
                </td>
            </tr>         
        </table>
        </form>
    </div>
</body>
</html>

Arquivo – manterTelefone.phtml
Diretório Pai – views

<?php
$v_params = $this->getParams();
$o_contato = $v_params['o_contato'];
$o_telefone = $v_params['o_telefone'];
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>
        Agenda Telef&ocirc;nica - Exemplo de MVC com PHP
    </title>
    <link rel="stylesheet" type="text/css" href="template/css/default.css" />
</head>
<body>
    <div align="center">
        <table width="80%" border="1">
            <tr>
                <th colspan='3'>
                    Contato
                </th>
            </tr>
            <tr>
                <th>
                    ID
                </th>
                <th>
                    Nome
                </th>
                <th>
                    E-mail
                </th>
            </tr>
            <tr>
                <td>
                    <?php echo $o_contato->getId()?>
                </td>
                <td>
                    <?php echo $o_contato->getNome()?>
                </td>
                <td>
                    <?php echo $o_contato->getEmail()?>
                </td>
            </tr>
        </table>
        <br />
        <form method='post'>
        <table width="40%" border="1">
            <tr>
                <th colspan="4">
                    Telefones
                </th>
            </tr>
            <tr>
                <th>
                    DDD
                </th>
                <th>
                    Telefone
                </th>
                <th>
                    A&ccedil;&otilde;es
                </th>
            </tr>
            <tr>
                <td align="center">
                    <input type='text' name='in_ddd' value='<?php echo $o_telefone->getDDD()?>' size="5" maxlength="3">
                </td>
                <td align="center">
                    <input type='text' name='in_telefone' value='<?php echo $o_telefone->getTelefone()?>'>
                </td>
                <td align="center">
                    <input type='hidden' name='controle' value='Telefone'>
                    <input type='hidden' name='acao' value='manterTelefone'>
                    <input type='hidden' name='in_con' value='<?php echo $o_contato->getId()?>'>
                    <input type='hidden' name='in_tel' value='<?php echo $o_telefone->getId()?>'>
                    <button type='submit'>Salvar</button>
                </td>
            </tr>
        </table>
        </form>
        <br />
        <a href='?controle=Telefone&acao=listarTelefones&in_con=<?php echo $o_contato->getId()?>'>Cancelar</a>
    </div>
</body>
</html>

Cada método (Action) da classe de controle (Controller) faz referência à um arquivo HTML (View). Assim se cumpre a profecia de intermédio entre a camada de controle e a camada de visualização. Onde o controle faz consultas à camada de modelo e repassa a resposta para a camada de visualização.

Após o código implementado, para selecionar o Controlador e a Ação que quer executar, basta envia-las via GET ou POST, como por exemplo ?controle=Contato&acao=listarContato. Com esses parâmetros, o método listarContatoAction da classe ContatoController será executado.

Para entender um pouco melhor o funcionamento desse programa, visualize o Diagrama de Sequência da funcionalidade Listar Contatos mostrado na Imagem 6 abaixo. Ele será executado por padrão quando nenhum outro for requisitado pelo usuário.

Listar Contatos


Imagem 6 – Diagrama de Sequência do Caso de Uso Listar Contatos

Quer ver isso funcionando? Acesse o link http://digitaldev.com.br/exemploMVC/

Quer fazer download do código para estudar? Acesse o link http://digitaldev.com.br/downloads/exemploMVC.zip

Conectando a agenda com o MySQL

Caso você queira conectar a aplicação desenvolvida com o MySQL, basta editar o arquivo lib/PersistModelAbstract.php e deixá-lo como abaixo, preenchendo apenas os dados da sua conexão e credenciais referentes.

<?php
 /**
 * Classe Abstrata responsável por centralizar a conexão
 * com o banco de dados
 * 
 * @package Exemplo simples com MVC
 * @author DigitalDev
 * @version 0.1.1
 *  
 * Diretório Pai - lib
 * Arquivo - PersistModelAbstract.php
 */
abstract class PersistModelAbstract
{
    /**
    * Variável responsável por guardar dados da conexão do banco
    * @var resource
    */
    protected $o_db;
     
    function __construct()
    {
        
    	/*
    	// Inicio de conexão com SQLite     
        $this->o_db = new PDO("sqlite:./databases/db.sq3");
        $this->o_db->setAttribute ( PDO::ATTR_ERRMODE , PDO::ERRMODE_EXCEPTION );
        // Fim de conexão com SQLite
        */
         
        //Inicio de conexão com MySQL 
		$st_host = 'ip ou host';
		$st_banco = 'bancodedados';
		$st_usuario = 'usuario';
		$st_senha = 'senha';
		
		 
		$st_dsn = "mysql:host=$st_host;dbname=$st_banco"; 
		$this->o_db = new PDO
		(
			$st_dsn,
			$st_usuario,
			$st_senha
		);
		//Fim de conexão com MySQL
	}
}
?>

Mais sobre MVC com PHP

Há um tempo atrás, encontrei um material em vídeo muito bom sobre como montar um Mini-Framework no padrão MVC com PHP. Para acessar esse videos, clique em Criando um Mini Framework PHP 5 com MVC

Mais sobre Padrões de Projeto

O MVC é um dos Padrões de Projetos mais importantes hoje em dia, mas não está sozinho. Existem vários outros padrões por ai, eles nos ajudam a organizar o código, resolver problemas que costumamos enfrentar no dia-dia da Programação Orientada a Objeto. Sendo assim, vale muito a pena estudar sobre eles, pois um bom programador não é aquele que apenas codifica soluções excepcionais, mas também o que consegue organizar com clareza seu código. Veja uma lista de alguns padrões organizados por suas categorias abaixo, já é um pontapé inicial para se estudar. Espero ter ajudado.

Padrões de criação

Abstract Factory, Builder, Factory Method, Prototype, Singleton

Padrões estruturais

Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy

Padrões comportamentais

Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor

73 comentários

1 menção

Pular para o formulário de comentário

    • Isaac Sampaio em 25 de janeiro de 2013 às 0:45
    • Responder

    Parabéns amigo!! Muito bom
    artigo!! nota 1000.

    • Jorge Sampaio em 4 de fevereiro de 2013 às 12:58
    • Responder

    Cara!!!! Muito bom mesmo sua abordagem. Estou criando um aplicativo, porém, o código já está muito bagunçado e de difícil assimilação, estou começando a perder o controle do mesmo.
    Observando a forma com que vc lidou com seu exemplo, hummmm, fique mais entusiasmado em refazer o meu apoiado em sua ótima explicação. Fico alegre por vc compartilhar seu conhecimento a favor de outros. Novamente, muito obrigado e até.

    1. Obrigado Jorge. Se tiver alguma dúvida, entre em contato que eu tentarei ajudar.

      • Eduardo em 2 de dezembro de 2013 às 9:38
      • Responder

      Faço das palavras do Jorge Sampaio as minhas. Obrigado por compartilhar tal informação que, muitos dos que estão começando OOP tem muita dificuldade.

  1. Olá… Tarcísio obrigado pelo belo material publicado, parabéns, foi muito esclarecedor e didático.

    Nunca trabalhei com phpoo(tirando os web services) nem mvc, mas tenho uma noção. Mas mesmo depois de ler sua publicação e varias outras que já li, não consigo visualizar eu trabalhando com mvc. Talvez por falta de conhecimento, ou falta daquele estalo que da na nossa mente pra cair a fixa. Mesmo conseguindo compreender o que foi proposto. =/.

    Estou nessa busca de conseguir entender o suficiente pra mudar minha rotina de trabalho sem que caia a produção. Mas talvez, só usando mesmo pra sacar a ideia.

    Teria algo referente a isso pra me dizer/recomendar?

    Mais uma vez, parabéns pelo material e obrigado por compartilhar.
    o/

  2. Parabéns, empaquei no meu projeto, acredito que o estudo desse código irá me auxiliar!

  3. Como tenho MySql e não SQLlite, não consegui rodar aqui :S
    Vou tentar colocar no MySQL

    1. Boa tarde Cassio, eu revisei o código e alterei o mesmo para possibilitar tanto a conexão com MySQL, quanto com SQLite. Se houver mais alguma dúvida, entre em contato.

    • Amarildo Lucas em 7 de abril de 2013 às 13:13
    • Responder

    Olá Tarcísio,
    Tentei refazer o seu exemplo igualzinho mas usando mysql, não me ocorre erro algum, mas ao correr o projecto (http://localhost:8888/mvcExemplo/?controle=Usuario&acao=listarUsuario) o browser fica todo em branco, como se não ocorresse nada. Ainda acredito que o erro está na forma como lidei para tratar os dados com mysql. Pode me ajudar com esta dúvida? Como estaria a minha classe PersistModelAbstract.php usando mysql?

    1. Ok! Já consegui resolver. Obrigado!

      1. Fala Amarildo to tentando usar o MySQL tb e aparece o mxm erro como vc corrigiu ? vlw

        1. Deixa quieto já resolvi tb era besteira uma variável escrita errado.

      2. Achei q tinha resolvido mas continua, minha tela está td branca

    2. Boa tarde Amarildo, eu revisei o código e alterei o mesmo para possibilitar tanto a conexão com MySQL, quanto com SQLite. Se houver mais alguma dúvida, entre em contato.

    • Almir em 16 de abril de 2013 às 16:15
    • Responder

    Boa tarde Tarcísio!

    Acabei de ler o seu artigo e ficou muito bom, parabéns!

    Apenas a classe “TelefoneController” esta com o codigo da “ContatoController”.

    []’s

    Almir

    1. Opa Almir, vou verificar os códigos trocados…Obrigado

    2. Almir, revisei o código e coloquei as classes em seus devidos lugares. Obrigado pela observação. Qualquer outro problema, só falar.

  4. Não estou conseguindo fazer funcionar com o MySQL, na hora de salvar a tela fica td branca e não salva.

    Alguem conseguiu fazer funcionar com MySQL ?

    1. Não ta mostrando mensagem de erro alguma leandro? Você consegue fazer selects ou tá quebrando tudo?

    2. Boa tarde Leandro.
      Revisei e alterei o código, agora ele se conecta tanto com MySQL quanto com SQLite.
      Se houver mais alguma dúvida, entre em contato

  5. Cara. Show de bola a sua explicação. A melhor e mais clara que achei na net. Parabéns.

  6. Parabens , otimo post, conseg converte para outro banco de dados de uma forma extremamente simples, show de boa

  7. Para mim aparece o seguinte erro qd eu aponto para o mysql:

    Erro no servidor
    O site encontrou um erro ao recuperar http://www.polesystem.com.br/mvc/?controle=Telefone&acao=manterTelefone&in_con=0. Ele pode estar em manutenção ou configurado incorretamente.
    Veja algumas sugestões:
    Recarregue esta página da web mais tarde.
    Erro HTTP {500 (Internal Server Error): Ocorreu uma condição inesperada enquanto o servidor tentava completar a solicitação.

    Pode acessar aí o link para ver tb:
    http://www.polesystem.com.br/mvc

    o que eu mudei do arquivos foi somente a function no arquivo PersistModelAbstract.php para :

    function __construct()
    {

    $mysql = “host”;
    $username = “user”;
    $passwd = “senha”;
    $sqlite = “sqlite:./databases/db.sq3″;

    try{

    //Conectando ao banco de dados

    //$this->o_db = new PDO($sqlite);
    //$this->o_db->setAttribute ( PDO::ATTR_ERRMODE , PDO::ERRMODE_EXCEPTION );

    $this->o_db = new PDO($mysql, $username, $passwd) or print (mysql_error());
    $this->o_db->setAttribute ( PDO::ATTR_ERRMODE , PDO::ERRMODE_EXCEPTION );

    } catch (PDOException $e) {
    echo $e->getMessage();
    }

    }

    e mudei tb no arquivo Application.php a function loadRoute() onde estava ” index ” para ” Index ” pq não abria a pagina principal.

    • Edson em 17 de maio de 2013 às 15:17
    • Responder

    Ola, bom artigo,muito esclarecedor. Sou novado em OO e baixei os arquivos e na hora de executar, quando rodo a pagina index.php estou com o seguinte erro:
    Uncaught exception ‘Exception’ with message ‘Arquivo controllers/indexController.php nao encontrado’ in /home/u810533697/public_html/mvc/lib/Application.php:75 Stack trace: #0 /home/u810533697/public_html/mvc/index.php(7): Application->dispatch() #1 {main} thrown in /home/u810533697/public_html/mvc/lib/Application.php on line 75
    Alguém aqui teve esse problema?
    Grato

    1. Edson, o código foi revisado e o bug foi corrigido. Alem disso, adicionei algumas alterações que permitem conectar a aplicação tanto com MySQL quanto com SQLite. Se achar mais alguma coisa errada ai, entre em contato. Obrigado…

  8. Oi, parabéns pelo artigo! Apena uma dúvida… *Model não deveria chamar *DB e a calsse DataBase realizar as operações no banco? Então no *Model eu poderia ter o conceito de transação interessante para algumas aplicações. O que você acha?

    1. Bom dia Zegildo.

      Tentei usar classes mais simples para demonstrar o funcionamento, evitando assim a divisão da Camada Model em mais Subcamadas. Mas, conforme a experiência do programador, é válido a divisão da camada de Model em várias outras. Eu geralmente costumo criar classes com sufixo DAO (Data Access Object), onde as mesmas são responsáveis pela persistência dos dados das classes com sufixo Entity (Entidades como contato, telefone, endereço).

    • Sérgio Novelli em 7 de junho de 2013 às 14:33
    • Responder

    Tarcísio, primeiramente, parabéns pelo código.

    Estou tentando rodar esse código, mas estou tendo o seguinte erro:
    Fatal error: Class ‘PersistModelAbstract’ not found in D:\wamp\www\maisumteste\models\TelefoneModel.php on line 15

    O fato é que não consegue encontrar a classe ‘PersistModelAbstract’, porém ela existe. A única alteração realizada foi fazer a conexão com mysql:

    abstract class PersistModelAbstract {

    protected $o_db;

    function __construct() {

    try{
    $this->o_db = new PDO(‘mysql:host=localhost;dbname=testemvc’, ‘root’, ”, array(PDO::ATTR_PERSISTENT => true)) or print (mysql_error());
    $this->o_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
    }
    catch (PDOException $e) {
    echo $e->getMessage();
    }
    }

    }

    Poderia me dar uma dica do que está acontecendo? O uso da classe PDO está correto?

    Obrigado.

    1. Vou testar o código com o mysql e te dou retorno.

    2. Sergio, eu retestei e revisei o código para corrigir alguns pequenos erros. Esse erro relatado por você foi corrigido. Obrigado.

    • Roberto em 18 de junho de 2013 às 0:03
    • Responder

    Boa noite Tarcísio, fico no aguardo do código repetido. (TelefoneController)… Tentei fazer mas não consegui muito sucesso. Abraços e valeu pelo grande post!

    1. Roberto, revisei o código e alterei o post. Agora a classe está no seu devido lugar.
      Se achar mais alguma inconsistência, entre em contato.

    • Francis Rodrigues em 18 de junho de 2013 às 10:26
    • Responder

    Gostei muito da estrutura MVC que você desenvolveu. 🙂
    Tem algo em mente pra colocar .htaccess nisso pra deixar com query string e url amigável também?

    Esse método de jogar instruções SQL também não gostei, mas não sei uma forma de usar meta queries como o CakePHP faz. Alguma idéia?

    1. Esse código é um exemplo focado em MVC…apenas para que programadores iniciantes entendam como funciona a estrutura. Não é um projeto de um framework ou algo do tipo. O SQL no meio do Model é apenas para diminuir o número de classes e divisões da camada de Modelo, mantendo assim, o foco no entendimento do MVC. Por esse motivo, não implementei url amigável.

      1. Tarcísio programadores iniciantes?
        Essa foi boa hahaha.

        Sua estrutura está até melhor que a de muitos frameworks de ponta que diversas comunidades os usam.

        Precisamos criar um parse no html como templates, para evitar usar tanto php junto com o html, criar rotas de url e deixando as urls amigávels ou não, como o wordpress e joomla tem, acesso a url com query string ou com modo rewrite tudo ativo.

        Estou interessado em continuar essa estrutura em projeto, quem sabe não criamos uma estrutura em que muitos poderão seguir. 😉

        Se estiver interessado, entre em contato.
        Abraço.

        1. Quando digo Iniciantes, quero dizer que tentei deixar a explicação mais clara possível, sem ir muito alem nas estruturas. Porque se eu deixasse o modelo completo, do jeito que eu trabalho, ficaria mais difícil pro pessoal entender.

          1. Entendi.
            Mas podemos subir isso num repositório como projeto.
            Considerando depois como uma arquitetura MVC open de sua autoria. 😀

      • Granhani em 29 de outubro de 2013 às 4:36
      • Responder

      Tarcísio show de bola seu post!! parabéns mesmo!!!
      a questão foi que tive a mesma ideia do Francis Rodrigues
      e dei uma fuçada no seu código!!!

      adicionei o .htaccess pra gerenciar as url amigável, e algumas outras mudanças pra isso funcionar!!!

      se o Francis Rodrigues ainda nao fez essas mudanças
      segue o link para download

      https://copy.com/OBL46UmwxyIC

    • Ernando A. Mendes em 30 de junho de 2013 às 13:11
    • Responder

    Fala Tarcísio, tudo bem? Muito bom esse material, tudo bem comentado, ótimo para mim que sou iniciante em MVC.

  9. Tem um erro aqui:

    Fatal error: Class ‘PersistModelAbstract’ not found in /home/ubuntu/projeto/models/TelefoneModel.php on line 16

    1. Vou verificar

      1. É que faltou incluir require_once(‘lib/PersistModelAbstract.php’);

        e View.php também.

        Vai dar um monte de erro seguindo o exemplo.

        Vou ver se no código do download está tudo normal. 😉

    2. Francis, o post foi corrigido…

      Foi adicionado uma função __autoload no arquivo Application.php, com ela todas as classes da pasta lib serão automaticamente carregadas, inclusive essa em questão.

      Obrigado pela observação.

        • JKC em 28 de abril de 2016 às 11:22
        • Responder

        Tarciso, foste extremamente fundamental para a mimha aprendizagem… abraços!

        1. Obrigado JKC, estou a disposição.

  10. Parabéns pelo artigo, está perfeito!

    • Fabiana Moura em 2 de outubro de 2013 às 2:04
    • Responder

    Curti muito sua explicação!

    • Riguel em 27 de outubro de 2013 às 21:48
    • Responder

    Tarcisio

    Cara sou iniciante e estou com uma duvida pois estou utilizando o seu projeto para estudo contudo não estou conseguindo passar os valores dos campos e resgatalos no lado do server dá um erro dizendo que não existe, estou utilizando $POST[‘xxx’] teria como me dar uma força ou me explicar como realmente funciona o set e get params?? obrigado e realmente muito bom o projeto

    • Augusto em 28 de outubro de 2013 às 14:28
    • Responder

    Gostei muito do artigo!!! gostaria de saber como implementar autenticação usando esse modelo mvc?

  11. Muito bom mesmo, parabéns!
    São de posts assim que muitos programadores PHP estruturais no Brasil precisam para
    crescer nos padrões de projetos atuais. Muitos têm medo, mas com este post vemos que não é bicho de sete cabeças
    Didaticamente seu post está ótimo, se alguém criticar ou disser que esta faltando ajustes é porque está em outro nível de conhecimento
    Este link vai para meu blog

  12. Muito bom o tutorial, o padrão de projeto GRASP MVC está bem definido no php, facilita muito a vida programar orientado a objetos, principalmente web, muitos programadores realmente precisam aprender com soluções simples e práticas, parabens.

  13. muito bom o seu artigo.

    • Helede em 12 de dezembro de 2013 às 15:51
    • Responder

    Muito bom mesmo cara, parabéns. Vou iniciar um novo projeto e vou basear em MVC. Achei sua explicação muito boa.

  14. Opa, muito bom, isso é quase um framework a parte. Estou usando num projeto. E se eu quiser passar parâmetro para uma view redirecionada por exemplo: header(‘Location: ?controle=Contato&acao=listarContato’); Se eu precisar passar parametros para o listarContato, como procedo?

  15. Muito obrigado por compartilhar seu conhecimento. O tutorial é um dos melhores e mais didáticos que eu já li.

  16. Olá e em que camada coloco as exceções personalizadas que criar?

    1. Boa tarde Rafael.

      As exceções personalizadas podem ficar em qualquer camada. Por exemplo, se você quiser lançar uma exceção por um erro de banco de dados, provavelmente isso ficará na camada de modelo, já se você quiser lançar uma exceção porque o cara chamou um comando / método que não existe, essa ficaria na camada de controle. O que você tem que fazer é tratar as exceções camada a camada, ou seja, se você chamar a camada de modelo dentro da camada de controle e a mesma lançar uma exceção personalizada, o certo é tratar com try{}catch( Exception ){} para ignorar a mesma ou até mesmo mostrar uma mensagem de erro ao usuário.

      1. Obrigado pela atenção, outra questão, eu coloco os arquivos/classes de exceção na pasta da camada ou dentro da camada eu coloco uma pasta de exception própria

        tipo
        models/exception/ClasseException.php
        ou
        models/ClasseExceptions.php

        Qual o mais “correto”?

    • Danilo em 24 de fevereiro de 2014 às 18:19
    • Responder

    Olá Tarcísio, tudo bom???

    Seguinte, comecei a pouco numa nova empresa e o pessoal daqui criou nosso sistema inteiramente baseado neste seu exemplo… Porem, ninguém da equipe tinha muita experiência com o modelo MVC… Então alguns problemas surgiram…

    O maior deles para mim agora esta no layout… Todos os demais membros da equipe implementaram seus HTML/CSS em cada uma das views de suas telas… Porem isso é insano!!! Quando precisarmos, e vamos precisar, dar manutenção no layout teremos um ENORME problema!!!

    Eu ainda sou novo no MVC tbm… Mas já trabalhei em outra empresa com um já montado.

    Enfim, minha dúvida é quanto a criação de uma view master com o layout padrão do site. Para que as demais views tenham APENAS o conteúdo interno de cada aplicação.

    Será q vc poderia me dar uma luz em como procedo nisto?? Nem que seja me indicar algum outro site com alguma luz sobre o assunto!

    Desde já agradeço pela força! Seu MVC foi fundamental para nós aqui! 😉

    Grande abraço
    Danilo

    1. Olha é muito simples divida sua view em três arquivos config.php (define constantes com as pastas usadas), header.php( cabeçalho), footer.php(rodapé) então na view independente só chame o conteúdo da view incluindo sempre o config, header, o conteúdo da view atual e footer se necessários.. estou fazendo assim…

        • Danilo em 26 de fevereiro de 2014 às 17:45
        • Responder

        Hmmm,
        Eu já estou fazendo assim nas minhas aplicações e alguns outros membros tbm fizeram isso. Entretanto eles ainda fizeram arquivos (topo, rodape, etc) proprios. Mas com a mesma cara…

        Mas isso não é o ideal. Eu queria mesmo era criar um template master e aplicar as views dentro dele… Invertendo essa logica. Ou seja, ao inves deu colocar o include dos arquivos em cada view, eu incluiria cada view num template.

        Mas enfim, eu ja convenci o pessoal a migrar para um framework MCV mais completo. Pois creio que teremos mais desafios la pra frente…

        Abs

    • Bruno em 19 de junho de 2014 às 16:56
    • Responder

    Olá Tarcísio, tudo bem? Legal sua iniciativa!

    Mas lembrando que MVC não é um padrão de projeto e sim um padrão de arquitetura de software.

    Uma outra dica legal é generalizar os diagramas de casos de uso e de classes para que estes fiquem mais compreensíveis, isto é, ao invés de colocar cada etapa do CRUD (criar, consultar, alterar e excluir), colocar apenas uma que identifique todas (por exemplo, caso de uso “Manter Contato”, método “manterContato()”).

    Parabéns pelo artigo,
    sucesso e
    abraços!

    1. Bruno, o MVC é apresentado em diversas bibliografias como um Padrão de Projeto. Sobre generalizar os diagramas, fiz assim exatamente para que as pessoas que estão lendo o texto não se confundirem, é um texto para iniciante em desenvolvimento. Obrigado pelo seu feedback.

    • Charles Duarte em 30 de junho de 2014 às 10:14
    • Responder

    Para este modelo rodar diretamente no mysql, quais as mudanças necessárias. Um ótima explanação do assunto.

    • Charles Duarte em 30 de junho de 2014 às 10:53
    • Responder

    Erro que esta acontecendo. Ao colocar o MYSQL.

    Coloquei um usuário e ip fictício.

    Fatal error: Uncaught exception ‘PDOException’ with message ‘SQLSTATE[28000] [1045] Access denied for user ‘root’@’1.1.1.1′ (using password: YES)’ in /var/www/amt/agenda/lib/PersistModelAbstract.php:50 Stack trace: #0 /var/www/amt/agenda/lib/PersistModelAbstract.php(50): PDO->__construct(‘mysql:host=1.1…’, ‘root’, ‘######’) #1 /var/www/amt/agenda/models/ContatoModel.php(25): PersistModelAbstract->__construct() #2 /var/www/amt/agenda/controllers/ContatoController.php(26): ContatoModel->__construct() #3 /var/www/amt/agenda/lib/Application.php(89): ContatoController->listarContatoAction() #4 /var/www/amt/agenda/index.php(22): Application->dispatch() #5 {main} thrown in /var/www/amt/agenda/lib/PersistModelAbstract.php on line 50

    • Elenildo em 31 de julho de 2014 às 23:22
    • Responder

    o banco pode ser criando em um mysql normal

    • Jonathas Alves em 28 de novembro de 2015 às 0:25
    • Responder

    Ótimo artigo!

  17. Olá Tarcisio, eu tenho uma duvida. Como uso url amigável nesse exemplo?

    • Elias em 2 de março de 2017 às 20:58
    • Responder

    Estou me sentindo uma ameba, não entendi nada. rsrs.
    Tem um exemplo mais simples?

    1. Oi Elias. Talvez o ideal seja você se aprofundar um pouco mais em PHP e Orientação a Objetos. Esse exemplo é bem simples no que se diz respeito a MVC, com desenhos e tudo mais. Será que o que você está buscando é realmente exemplos de MVC ou tá querendo apenas se aprofundar em PHP?

  18. muito bom mesmo seu artigo. vou usar o modelo para desenvolver o meu.

  19. CARACA!!! Era tudo oq eu precisava para aprender mvc! Obrigado por compartilhar esse material tao interessante. Funcionou perfeitamente no meu localhost com mysql, bastou alterar o arquivo indicado.

    • Philipe Souza em 1 de novembro de 2017 às 13:45
    • Responder

    Amigo so tenho uma duvida como faço relações de N:N com as models para buscar um id um exemplo é uma table de categoria que tem um id que faz referencia a uma outra tabela ex subcategoria.

  1. […] Exemplo de MVC com PHP » […]

Deixe uma dúvida, resposta ou sugestão