Outra parte muito importante da nossa aplicação com estrutura MVC em PHP é a base de dados. A maioria dos sistemas que iremos criar terá acesso ao banco de dados, tanto para salvar dados relevantes como para manipular os dados de usuários.

Para nosso tutorial, criei uma classe que gerencia tudo o que é relacionado à base de dados, como conexão e manipulação de dados.

A classe responsável por manter a base de dados é a “TutsupDB”, que está na pasta “classes”, com o nome “class-TutsupDB.php”.

Mais adiante neste artigo vamos analisar seu código, bem como demais classes que formam nosso formato de MVC em PHP.

Download

Como estamos criando uma aplicação inteira, são vários arquivos que se encaixam uns nos outros. Se você quiser baixar os dados e ir acompanhando com eles em seu computador, segue o link abaixo:

Caso queira contribuir com o projeto, acesso no Github:

No download acima, a aplicação está praticamente completa. Pode ser que algo seja alterado conforme nossa necessidade, mas vou detalhar tudo no artigo.

classes/class-TutsupDB.php

A classe TutsupDB faz a conexão PDO e gerencia dados da base de dados, veja seu código:

<?php
/**
 * TutsupDB - Classe para gerenciamento da base de dados
 *
 * @package TutsupMVC
 * @since 0.1
 */
class TutsupDB
{
	/** DB properties */
	public $host      = 'localhost', // Host da base de dados 
	       $db_name   = 'tutsup',    // Nome do banco de dados
	       $password  = '',          // Senha do usuário da base de dados
	       $user      = 'root',      // Usuário da base de dados
	       $charset   = 'utf8',      // Charset da base de dados
	       $pdo       = null,        // Nossa conexão com o BD
	       $error     = null,        // Configura o erro
	       $debug     = false,       // Mostra todos os erros 
	       $last_id   = null;        // Último ID inserido
	
	/**
	 * Construtor da classe
	 *
	 * @since 0.1
	 * @access public
	 * @param string $host     
	 * @param string $db_name
	 * @param string $password
	 * @param string $user
	 * @param string $charset
	 * @param string $debug
	 */
	public function __construct(
		$host     = null,
		$db_name  = null,
		$password = null,
		$user     = null,
		$charset  = null,
		$debug    = null
	) {
	
		// Configura as propriedades novamente.
		// Se você fez isso no início dessa classe, as constantes não serão
		// necessárias. Você escolhe...
		$this->host     = defined( 'HOSTNAME'    ) ? HOSTNAME    : $this->host;
		$this->db_name  = defined( 'DB_NAME'     ) ? DB_NAME     : $this->db_name;
		$this->password = defined( 'DB_PASSWORD' ) ? DB_PASSWORD : $this->password;
		$this->user     = defined( 'DB_USER'     ) ? DB_USER     : $this->user;
		$this->charset  = defined( 'DB_CHARSET'  ) ? DB_CHARSET  : $this->charset;
		$this->debug    = defined( 'DEBUG'       ) ? DEBUG       : $this->debug;
	
		// Conecta
		$this->connect();
		
	} // __construct
	
	/**
	 * Cria a conexão PDO
	 *
	 * @since 0.1
	 * @final
	 * @access protected
	 */
	final protected function connect() {
	
		/* Os detalhes da nossa conexão PDO */
		$pdo_details  = "mysql:host={$this->host};";
		$pdo_details .= "dbname={$this->db_name};";
		$pdo_details .= "charset={$this->charset};";
		 
		// Tenta conectar
		try {
		
			$this->pdo = new PDO($pdo_details, $this->user, $this->password);
			
			// Verifica se devemos debugar
			if ( $this->debug === true ) {
			
				// Configura o PDO ERROR MODE
				$this->pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
				
			}
			
			// Não precisamos mais dessas propriedades
			unset( $this->host     );
			unset( $this->db_name  );
			unset( $this->password );
			unset( $this->user     );
			unset( $this->charset  );
		
		} catch (PDOException $e) {
			
			// Verifica se devemos debugar
			if ( $this->debug === true ) {
			
				// Mostra a mensagem de erro
				echo "Erro: " . $e->getMessage();
				
			}
			
			// Kills the script
			die();
		} // catch
	} // connect
	
	/**
	 * query - Consulta PDO
	 *
	 * @since 0.1
	 * @access public
	 * @return object|bool Retorna a consulta ou falso
	 */
	public function query( $stmt, $data_array = null ) {
		
		// Prepara e executa
		$query      = $this->pdo->prepare( $stmt );
		$check_exec = $query->execute( $data_array );
		
		// Verifica se a consulta aconteceu
		if ( $check_exec ) {
			
			// Retorna a consulta
			return $query;
			
		} else {
		
			// Configura o erro
			$error       = $query->errorInfo();
			$this->error = $error[2];
			
			// Retorna falso
			return false;
			
		}
	}
	
	/**
	 * insert - Insere valores
	 *
	 * Insere os valores e tenta retornar o último id enviado
	 *
	 * @since 0.1
	 * @access public
	 * @param string $table O nome da tabela
	 * @param array ... Ilimitado número de arrays com chaves e valores
	 * @return object|bool Retorna a consulta ou falso
	 */
	public function insert( $table ) {
		// Configura o array de colunas
		$cols = array();
		
		// Configura o valor inicial do modelo
		$place_holders = '(';
		
		// Configura o array de valores
		$values = array();
		
		// O $j will assegura que colunas serão configuradas apenas uma vez
		$j = 1;
		
		// Obtém os argumentos enviados
		$data = func_get_args();
		
		// É preciso enviar pelo menos um array de chaves e valores
		if ( ! isset( $data[1] ) || ! is_array( $data[1] ) ) {
			return;
		}
		
		// Faz um laço nos argumentos
		for ( $i = 1; $i < count( $data ); $i++ ) {
		
			// Obtém as chaves como colunas e valores como valores
			foreach ( $data[$i] as $col => $val ) {
			
				// A primeira volta do laço configura as colunas
				if ( $i === 1 ) {
					$cols[] = "`$col`";
				}
				
				if ( $j <> $i ) {
					// Configura os divisores
					$place_holders .= '), (';
				}
				
				// Configura os place holders do PDO
				$place_holders .= '?, ';
				
				// Configura os valores que vamos enviar
				$values[] = $val;
				
				$j = $i;
			}
			
			// Remove os caracteres extra dos place holders
			$place_holders = substr( $place_holders, 0, strlen( $place_holders ) - 2 );
		}
		
		// Separa as colunas por vírgula
		$cols = implode(', ', $cols);
		
		// Cria a declaração para enviar ao PDO
		$stmt = "INSERT INTO `$table` ( $cols ) VALUES $place_holders) ";
		
		// Insere os valores
		$insert = $this->query( $stmt, $values );
		
		// Verifica se a consulta foi realizada com sucesso
		if ( $insert ) {
			
			// Verifica se temos o último ID enviado
			if ( method_exists( $this->pdo, 'lastInsertId' ) 
				&& $this->pdo->lastInsertId() 
			) {
				// Configura o último ID
				$this->last_id = $this->pdo->lastInsertId();
			}
			
			// Retorna a consulta
			return $insert;
		}
		
		// The end :)
		return;
	} // insert
	
	/**
	 * Update simples
	 *
	 * Atualiza uma linha da tabela baseada em um campo
	 *
	 * @since 0.1
	 * @access protected
	 * @param string $table Nome da tabela
	 * @param string $where_field WHERE $where_field = $where_field_value
	 * @param string $where_field_value WHERE $where_field = $where_field_value
	 * @param array $values Um array com os novos valores
	 * @return object|bool Retorna a consulta ou falso
	 */
	public function update( $table, $where_field, $where_field_value, $values ) {
		// Você tem que enviar todos os parâmetros
		if ( empty($table) || empty($where_field) || empty($where_field_value)  ) {
			return;
		}
		
		// Começa a declaração
		$stmt = " UPDATE `$table` SET ";
		
		// Configura o array de valores
		$set = array();
		
		// Configura a declaração do WHERE campo=valor
		$where = " WHERE `$where_field` = ? ";
		
		// Você precisa enviar um array com valores
		if ( ! is_array( $values ) ) {
			return;
		}
		
		// Configura as colunas a atualizar
		foreach ( $values as $column => $value ) {
			$set[] = " `$column` = ?";
		}
		
		// Separa as colunas por vírgula
		$set = implode(', ', $set);
		
		// Concatena a declaração
		$stmt .= $set . $where;
		
		// Configura o valor do campo que vamos buscar
		$values[] = $where_field_value;
		
		// Garante apenas números nas chaves do array
		$values = array_values($values);
				
		// Atualiza
		$update = $this->query( $stmt, $values );
		
		// Verifica se a consulta está OK
		if ( $update ) {
			// Retorna a consulta
			return $update;
		}
		
		// The end :)
		return;
	} // update

	/**
	 * Delete
	 *
	 * Deleta uma linha da tabela
	 *
	 * @since 0.1
	 * @access protected
	 * @param string $table Nome da tabela
	 * @param string $where_field WHERE $where_field = $where_field_value
	 * @param string $where_field_value WHERE $where_field = $where_field_value
	 * @return object|bool Retorna a consulta ou falso
	 */
	public function delete( $table, $where_field, $where_field_value ) {
		// Você precisa enviar todos os parâmetros
		if ( empty($table) || empty($where_field) || empty($where_field_value)  ) {
			return;
		}
		
		// Inicia a declaração
		$stmt = " DELETE FROM `$table` ";

		// Configura a declaração WHERE campo=valor
		$where = " WHERE `$where_field` = ? ";
		
		// Concatena tudo
		$stmt .= $where;
		
		// O valor que vamos buscar para apagar
		$values = array( $where_field_value );

		// Apaga
		$delete = $this->query( $stmt, $values );
		
		// Verifica se a consulta está OK
		if ( $delete ) {
			// Retorna a consulta
			return $delete;
		}
		
		// The end :)
		return;
	} // delete
	
} // Class TutsupDB

Ela utiliza os dados das constantes presentes no arquivo config.php para realizar a conexão com a base de dados.

Os exemplos de utilização seriam mais ou menos assim:

<?php
// Objeto
$db = new TutsupDB();

// Insere
$db->insert(
	'tabela', 
	
	// Insere uma linha
	array('campo_tabela' => 'valor', 'outro_campo'  => 'outro_valor'),
	
	// Insere outra linha
	array('campo_tabela' => 'valor', 'outro_campo'  => 'outro_valor'),
	
	// Insere outra linha
	array('campo_tabela' => 'valor', 'outro_campo'  => 'outro_valor')
);

// Atualiza
$db->update(
	'tabela', 'campo_where', 'valor_where',
	
	// Atualiza a linha
	array('campo_tabela' => 'valor', 'outro_campo'  => 'outro_valor')
);

// Apaga
$db->delete(
	'tabela', 'campo_where', 'valor_where'
);

// Seleciona
$db->query(
	'SELECT * FROM tabela WHERE campo = ? AND outro_campo = ?',
	array( 'valor', 'valor' )
);

Como estamos inserindo os dados da conexão em uma propriedade do nosso modelo, a utilização ficaria assim:

<?php
// Objeto
$modelo->db = new TutsupDB();

// Insere
$modelo->db->insert(
	'tabela', 
	
	// Insere uma linha
	array('campo_tabela' => 'valor', 'outro_campo'  => 'outro_valor'),
	
	// Insere outra linha
	array('campo_tabela' => 'valor', 'outro_campo'  => 'outro_valor'),
	
	// Insere outra linha
	array('campo_tabela' => 'valor', 'outro_campo'  => 'outro_valor')
);

// Atualiza
$modelo->db->update(
	'tabela', 'campo_where', 'valor_where',
	
	// Atualiza a linha
	array('campo_tabela' => 'valor', 'outro_campo'  => 'outro_valor')
);

// Apaga
$modelo->db->delete(
	'tabela', 'campo_where', 'valor_where'
);

// Seleciona
$modelo->db->query(
	'SELECT * FROM tabela WHERE campo = ? AND outro_campo = ?',
	array( 'valor', 'valor' )
);

Vamos analisar agora nossa classe de modelo MainModel.

classes/class-MainModel.php

Essa classe servirá para manter os métodos que poderão ser utilizados em todos os modelos, ou seja, ela o ajuda a manter a reutilização de código sempre ativa. Ela não tem muitos métodos, já que eles vão depender da sua aplicação.

Adicionei um método simples nela apenas para inverter a data das notícias que iremos cadastrar.

Uma parte importante da classe MainModel, é que ela mantém muitas propriedades que utilizamos constantemente em nossos views e models, portanto, todos os outros modelos deverão estender essa classe.

Veja seu código:

<?php
/**
 * MainModel - Modelo geral
 *
 * 
 *
 * @package TutsupMVC
 * @since 0.1
 */
class MainModel
{
	/**
	 * $form_data
	 *
	 * Os dados de formulários de envio.
	 *
	 * @access public
	 */	
	public $form_data;

	/**
	 * $form_msg
	 *
	 * As mensagens de feedback para formulários.
	 *
	 * @access public
	 */	
	public $form_msg;

	/**
	 * $form_confirma
	 *
	 * Mensagem de confirmação para apagar dados de formulários
	 *
	 * @access public
	 */
	public $form_confirma;

	/**
	 * $db
	 *
	 * O objeto da nossa conexão PDO
	 *
	 * @access public
	 */
	public $db;

	/**
	 * $controller
	 *
	 * O controller que gerou esse modelo
	 *
	 * @access public
	 */
	public $controller;

	/**
	 * $parametros
	 *
	 * Parâmetros da URL
	 *
	 * @access public
	 */
	public $parametros;

	/**
	 * $userdata
	 *
	 * Dados do usuário
	 *
	 * @access public
	 */
	public $userdata;
	
	/**
	 * Inverte datas 
	 *
	 * Obtém a data e inverte seu valor.
	 * De: d-m-Y H:i:s para Y-m-d H:i:s ou vice-versa.
	 *
	 * @since 0.1
	 * @access public
	 * @param string $data A data
	 */
	public function inverte_data( $data = null ) {
	
		// Configura uma variável para receber a nova data
		$nova_data = null;
		
		// Se a data for enviada
		if ( $data ) {
		
			// Explode a data por -, /, : ou espaço
			$data = preg_split('/-|/|s|:/', $data);
			
			// Remove os espaços do começo e do fim dos valores
			$data = array_map( 'trim', $data );
			
			// Cria a data invertida
			$nova_data .= chk_array( $data, 2 ) . '-';
			$nova_data .= chk_array( $data, 1 ) . '-';
			$nova_data .= chk_array( $data, 0 );
			
			// Configura a hora
			if ( chk_array( $data, 3 ) ) {
				$nova_data .= ' ' . chk_array( $data, 3 );
			}
			
			// Configura os minutos
			if ( chk_array( $data, 4 ) ) {
				$nova_data .= ':' . chk_array( $data, 4 );
			}
			
			// Configura os segundos
			if ( chk_array( $data, 5 ) ) {
				$nova_data .= ':' . chk_array( $data, 5 );
			}
		}
		
		// Retorna a nova data
		return $nova_data;
	
	} // inverte_data

} // MainModel

Conforme descrevi, ela não tem muita coisa, apenas algo que seja necessário para vários modelos.

Criando meu primeiro MVC – Model – Controller – View

Agora que já falamos das partes mais importantes do nosso modelo, vamos criar nosso primeiro MVC em PHP.

Nas verdade a ordem correta para a nossa descrição seria CMV (apenas exemplo), já que essa é a ordem que nosso programa procura os arquivos.

Criando um controller

Os controllers devem ser inseridos na pasta “controllers” com o seguinte formato de nome:

  • exemplo-controller.php

Onde “exemplo” é o nome do seu controller. O final -controller.php é obrigatório.

Tive que fazer isso, porque eu me perdia facilmente no nome dos arquivos quando estava desenvolvendo.

Isso acontece apenas com os controladores, mas para facilitar minha vida resolvi adaptar o mesmo modelo para tudo, models, controllers e views (você vai perceber isso posteriormente no artigo).

Apenas para nosso exemplo, crie um arquivo chamado “exemplo-controller.php” e vamos adicionar uma classe com o mesmo nome do arquivo, porém sem nenhum traço.

Veja:

<?php
class ExemploController extends MainController
{
   // Aqui vem nossas ações
}

Observação: A classe deverá ter o mesmo nome do arquivo (incluindo a palavra controller).

Apenas criando a nossa classe não fará a aplicação funcionar, temos que ter pelo menos uma ação (método), veja:

<?php
class ExemploController extends MainController
{
	// URL: dominio.com/exemplo/
	public function index() {
	
		// Carrega o modelo
		$modelo = $this->load_model('exemplo/exemplo-model');
		
		// Carrega o view
		require_once ABSPATH . '/views/exemplo/exemplo-view.php';
	}
}

Veja que criamos uma ação chamada index. Essa ação carrega o modelo e view que precisaremos.

Você pode optar por não utilizar um model, apenas o view, isso fica a seu critério.

Vamos ver os arquivos de model e view.

Criando um model

Os modelos ficam dentro da pasta “models”.

Apenas por convenção de nomes, sempre crio meus modelos com o seguinte formato:

  • modelo-model.php

Sempre com o mesmo nome do seu controller.

Para nosso exemplo, meu modelo ficou com o seguinte note:

  • exemplo-model.php

Outra coisa que faço para deixar a estrutura de arquivos mais organizada é separar os modelos por pastas. Para todos os modelos de “exemplo“, crio uma pasta exemplo e salvo tudo que é referente a exemplos lá dentro.

Crie um arquivo chamado “exemplo-model.php” dentro da pasta “exemplo” (que será criada dentro da pasta models) e vamos configurar o código deste arquivo.

<?php
class ExemploModel extends MainModel
{
	/**
	 * Construtor para essa classe
	 *
	 * Configura o DB, o controlador, os parâmetros e dados do usuário.
	 *
	 * @since 0.1
	 * @access public
	 * @param object $db Objeto da nossa conexão PDO
	 * @param object $controller Objeto do controlador
	 */
	public function __construct( $db = false, $controller = null ) {
		// Configura o DB (PDO)
		$this->db = $db;
		
		// Configura o controlador
		$this->controller = $controller;

		// Configura os parâmetros
		$this->parametros = $this->controller->parametros;

		// Configura os dados do usuário
		$this->userdata = $this->controller->userdata;
		
		echo 'Modelo carregado... <br>';
	}
	
	// Crie seus próprios métodos daqui em diante
}

Perceba que meu modelo de exemplo é uma classe com o mesmo nome do arquivo (sem caracteres especiais).

Por exemplo, para o arquivo compras-model.php, sua classe deverá se chamar ComprasModel.

No construtor da nossa classe, configuramos todas as variáveis que vamos precisar, como base de dados, dados de usuário e assim por diante.

Nosso controlador vai enviar seu objeto para o seu modelo no segundo parâmetro, você poderá utilizar todos os métodos já criados anteriormente utilizando tal objeto.

Vamos criar nosso view e exibir os dados desse modelo.

Criando um view

Agora que já temos um controlador e um modelo, precisamos de um view para exibir os dados.

Os views ficam na pasta views. Normalmente separados em suas próprias pastas.

Crie uma pasta chamada “exemplo“, e um arquivo chamado “exemplo-view.php“.

Neste arquivo você terá todas as propriedades e métodos do controlador e do modelo, veja:

<?php
echo '<h2>Dados do modelo.</h2>';
echo '<pre>';
print_r( $modelo );
echo '</pre>';
?>

<h2>Pronto</h2>

<p>Inclua seu site ou dados neste view...</p>

Isso deverá gerar o seguinte na tela:

Exemplo

Exemplo

Faça o download e o teste. Veja se tudo corre conforme descrito.

Download

Caso queira baixar os arquivos para testes ou utilização, segue abaixo:

Caso queira contribuir com o projeto, acesso no Github:

No download acima, a aplicação está praticamente completa. Pode ser que algo seja alterado conforme nossa necessidade, mas vou detalhar tudo no artigo.

Outras partes do artigo

Este artigo está dividido em várias partes, pois seu conteúdo é muito extenso. Também existe uma categoria exclusiva para todo seu conteúdo, chamada de MVC em PHP.

Veja todas as suas partes já publicas nos links abaixo:

Em caso de dúvidas, comente-a!