«

»

jan 22

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

Sobre o autor

Tarcísio

Bacharel em Engenharia da Computação, programador C / C++ , especialista em PHP, MySQL, PostgreSQL.

70 comentários

1 menção

Pular para o formulário de comentário

  1. Isaac Sampaio

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

  2. Jorge Sampaio

    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. Tarcísio

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

    2. Eduardo

      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.

  3. Jack Makiyama

    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/

  4. cassioroxássio

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

  5. cassiorox

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

    1. Tarcísio

      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.

  6. Amarildo Lucas

    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. Amarildo Lucas

      Ok! Já consegui resolver. Obrigado!

      1. leandrofb

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

        1. leandrofb

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

      2. leandrofb

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

    2. Tarcísio

      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.

  7. Almir

    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. Tarcísio

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

    2. Tarcísio

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

  8. leandrofb

    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. Tarcísio

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

    2. Tarcísio

      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

  9. Leonardo

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

  10. Victhor

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

  11. leandrofb

    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.

  12. Edson

    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. Tarcísio

      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…

  13. zegildo

    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. Tarcísio

      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).

  14. Sérgio Novelli

    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. Tarcísio

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

    2. Tarcísio

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

  15. Roberto

    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. Tarcísio

      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.

  16. Francis Rodrigues

    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. Tarcísio

      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. Francis Rodrigues

        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. Tarcísio

          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. Francis Rodrigues

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

    2. Granhani

      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

  17. Ernando A. Mendes

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

  18. Francis Rodrigues

    Tem um erro aqui:

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

    1. Tarcísio

      Vou verificar

      1. Francis Rodrigues

        É 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. Tarcísio

      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.

      1. JKC

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

        1. Tarcísio

          Obrigado JKC, estou a disposição.

  19. Cristiano Moura

    Parabéns pelo artigo, está perfeito!

  20. Fabiana Moura

    Curti muito sua explicação!

  21. Riguel

    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

  22. Augusto

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

  23. Dominus Farib

    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

  24. Carlos Anders

    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.

  25. r23k

    muito bom o seu artigo.

  26. Helede

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

  27. adrmasterCarlos Adriano

    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?

  28. batistafic

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

  29. RafaelFerreira

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

    1. Tarcísio

      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. RafaelFerreira

        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”?

  30. Danilo

    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. RafaelFerreira

      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…

      1. Danilo

        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

  31. Bruno

    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. Tarcísio

      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.

  32. Charles Duarte

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

  33. Charles Duarte

    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

  34. Elenildo

    o banco pode ser criando em um mysql normal

  35. Jonathas Alves

    Ótimo artigo!

  36. wallison carlos

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

  37. Elias

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

    1. Tarcísio

      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?

  1. Entendendo o MVC (Model-View-Controller) | DigitalDev

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

Deixe uma dúvida, resposta ou sugestão