PHP Orientado a Objetos traz todos artigos que criamos relacionados a programação com Orientação a Objetos. Aprenda a trabalhar com classes e objetos em PHP.

Lançar (throw) e capturar (try / catch) exceções em PHP é parte essencial da sua programação orientada a objetos. Com elas você pode criar um “erro” (uma exceção) que deverá ser capturado quando a classe ou função for executada.

Utilizamos throw para lançar uma nova exceção da seguinte maneira:

<?php
function mensagem ( $mensagem = null ) {
    // Verifica se a mensagem NÃO foi enviada
    if ( ! $mensagem ) {
        // Lança uma nova exceção
        throw new Exception('Mensagem não enviada!');
    }
    
    // Exibe a mensagem
    echo $mensagem;
}
?>

No código acima, criei uma função que exibirá uma mensagem que deverá (obrigatoriamente) ser enviada para o parâmetro $mensagem da mesma. Se a mensagem não for enviada pelo desenvolvedor, lanço uma exceção detalhando o que ocorreu.

Quando throw é encontrado pelo interpretador do PHP, o código irá ler o que ele diz e não executará o restante daquela função, ou seja, eu não preciso verificar novamente se o parâmetro foi enviado para continuar escrevendo o restante dos códigos daquela função.

Um ponto importante a ser ressaltado, é que agora você não deve executar a função diretamente, como era de costume.

// Executa a função sem o parâmetro mensagem
mensagem();

Isso irá lançar o seguinte erro fatal:

Fatal error: Uncaught exception ‘Exception’ with message ‘Mensagem não enviada!’ in D:hd_antigoProgramasEasyPHPdatalocalwebindex.php:6 Stack trace: #0 D:hd_antigoProgramasEasyPHPdatalocalwebindex.php(14): mensagem() #1 {main} thrown in D:hd_antigoProgramasEasyPHPdatalocalwebindex.php on line 6

O que aconteceu no erro acima foi que o interpretador do PHP encontrou uma exceção lançada com o bloco throw na linha 6 do arquivo index.php, mas não encontrou uma maneira de capturar essa exceção.

Capturar uma exceção significa utilizar o bloco try para tentar executar a função, e pelo menos um bloco catch para capturar e manipular o erro.

A maneira mais simples para resolver o problema acima seria:

// Executa o código
try {
    // Executa a função sem o parâmetro mensagem
    mensagem();
} catch( Exception $e ) {
    // Exibe a exceção (erro)
    echo $e->getMessage();
}

O bloco try/catch em PHP funcionam de maneira similar às estruturas condicionais em PHP, porém, ao invés de imaginar “Se / se não”, imagine “Tente / capture”.

Try sempre será executado enquanto um throw não for encontrado. Se throw for encontrado pelo interpretador do PHP, o código daquela função ou método imediatamente para de ser executado e os comandos do bloco catch serão executados.

Veja mais um exemplo, mas agora com classes PHP:

<?php
// Cria uma classe
class Mensagem
{
    // Um método
    public function exibe( $mensagem = false ) {
        // Verifica se a mensagem NÃO (!) foi enviada como parâmetro
        if ( ! $mensagem ) {
            throw new Exception('Envie a mensagem em $mensagem!');
        }
        
        // Exibe a mensagem
        echo $mensagem;
    }
}

// Executa o código
try {
    // Cria uma instância da classe
    $mensagem = new Mensagem();
    
    // Executa o método exibe
    $mensagem->exibe();
} catch( Exception $e ) {
    // Exibe a exceção (erro)
    echo $e->getMessage();
}
?>

No código acima, o bloco catch será executado, já que tentamos executar o método “exibe” sem enviar o parâmetro $mensagem. Consequentemente, ao invés de exibir qualquer coisa na tela, o usuário verá apenas:

Envie a mensagem em $mensagem!

Para que o bloco try seja executado, deveríamos executar o método “exibe” da seguinte maneira:

$mensagem->exibe("Oi, agora tenho uma mensagem!");

E resolveríamos o problema do parâmetro $mensagem.

Você também pode estender a classe “Exception” para criar suas próprias exceções da seguinte maneira:

<?php
// Nova exceção
class MinhaExcecao Extends Exception { }

Nesse caso nosso código mudaria um pouco, veja:

<?php
// Nova exceção
class MinhaExcecao Extends Exception { }

// Cria uma classe
class Mensagem
{
    // Um método
    public function exibe( $mensagem = false ) {
        // Verifica se a mensagem NÃO (!) foi enviada como parâmetro
        if ( ! $mensagem ) {
            // Lança a nova excessão
            throw new MinhaExcecao('Envie a mensagem em $mensagem!');
        }
        
        // Exibe a mensagem
        echo $mensagem;
    }
}

// Executa o código
try {
    // Cria uma instância da classe
    $mensagem = new Mensagem();
    
    // Executa o método exibe
    $mensagem->exibe();
} catch( MinhaExcecao $e ) {
    // Exibe a exceção (erro)
    echo $e->getMessage();
}
?>

No trecho de código acima, ao invés de lançar e capturar exceções na classe “Exception”, faço o mesmo na minha nova classe de exceções “MinhaExcecao”.

Uma parte importante da classe “Exception” no PHP, é que ela tem vários métodos interessantes, são eles:

  • getMessage () – A mensagem de erro
  • getCode () – O código de erro
  • getFile () – O arquivo de erro
  • getLine () – A linha do erro
  • getTrace () – Informações de contexto do erro
  • getTraceAsString () – Mesmo informação anterior, só que com string

Com isso podemos gerar erros mais precisos, veja:

// Executa o código
try {
    // Cria uma instância da classe
    $mensagem = new Mensagem();
    
    // Executa o método exibe
    $mensagem->exibe();
} catch( MinhaExcecao $e ) {
    echo '<b>Erro: </b>';
    echo $e->getMessage() . '<br>';
    echo ' <b>Na linha: </b>';
    echo $e->getLine() . '<br>';
    echo ' <b>Arquivo: </b>';
    echo $e->getFile() . '<br>';
}

O trecho de código acima deverá lançar um erro da seguinte maneira:

Erro: Envie a mensagem em $mensagem!
Na linha: 13
Arquivo: D:hd_antigoProgramasEasyPHPdatalocalwebindex.php

Bem mais detalhado.

Concluindo

Exceções trazem uma nova maneira de trabalhar com o PHP Orientado a Objetos, deixando o PHP mais próximo de linguagens como Java ou C. Utilize com sabedoria.

Caso queira tirar alguma dúvida, basta deixar um comentário.

Para terminar nossa aplicação com estrutura MVC em PHP, vamos criar um sistema de cadastro de notícias bem simples, com campos de título, data, autor, texto e URL da imagem.

Além disso, criaremos uma área administrativa para gerenciar as notícias, onde poderemos criar, apagar, editar e listar notícias cadastradas.

Este é apenas um exemplo sobre como iremos utilizar nossa aplicação MVC em PHP. A partir desse tutorial você estará apto a criar seus próprios modelos, controladores e views simplesmente seguindo o mesmo modelo.

Download

Antes de qualquer coisa, faça o download da aplicação e acompanhe em seu computador. Segue o link para download:

Caso queira contribuir com o projeto, acesso no Github:

Se tiver perdido alguma parte desse tutorial, deixo os links para facilitar sua vida:

Então vamos codificar um pouco…

Criando o controller para as notícias

Primeiramente vamos criar o nosso controller, portando acesse a pasta “controllers” e crie um arquivo chamado “noticias-controller.php”.

Neste controller teremos que ter duas ações:

  • index (pública) – Lista as notícias na página e não precisa de usuário e senha para o acesso;
  • adm (restrita) – Gerenciamento das notícias – CRUD com PDO

Veja o código do controller:

<?php
/**
 * NoticiasController - Controller de exemplo
 *
 * @package TutsupMVC
 * @since 0.1
 */
class NoticiasController extends MainController
{

   /**
    * $login_required
    *
    * Se a página precisa de login
    *
    * @access public
    */
    public $login_required = false;

   /**
    * $permission_required
    *
    * Permissão necessária
    *
    * @access public
    */
    public $permission_required;

   /**
    * INDEX
    *
    * Carrega a página "/views/noticias/index.php"
    */
    public function index() {
	// Título da página
	$this->title = 'Notícias';
	
	// Carrega o modelo para este view
        $modelo = $this->load_model('noticias/noticias-adm-model');
				
	/** Carrega os arquivos do view **/
		
	// /views/_includes/header.php
        require ABSPATH . '/views/_includes/header.php';
		
	// /views/_includes/menu.php
        require ABSPATH . '/views/_includes/menu.php';
		
	// /views/noticias/index.php
        require ABSPATH . '/views/noticias/noticias-view.php';
		
	// /views/_includes/footer.php
        require ABSPATH . '/views/_includes/footer.php';
		
    } // index
	
   /**
    * ADM
    *
    * Carrega a página "/views/noticias/noticias-adm-view.php"
    */
    public function adm() {
		// Page title
		$this->title = 'Gerenciar notícias';
		$this->permission_required = 'gerenciar-noticias';
		
		// Verifica se o usuário está logado
		if ( ! $this->logged_in ) {
		
			// Se não; garante o logout
			$this->logout();
			
			// Redireciona para a página de login
			$this->goto_login();
			
			// Garante que o script não vai passar daqui
			return;
		
		}
		
		// Verifica se o usuário tem a permissão para acessar essa página
		if (!$this->check_permissions($this->permission_required, $this->userdata['user_permissions'])) {
		
			// Exibe uma mensagem
			echo 'Você não tem permissões para acessar essa página.';
			
			// Finaliza aqui
			return;
		}
	
	// Carrega o modelo para este view
        $modelo = $this->load_model('noticias/noticias-adm-model');
		
	/** Carrega os arquivos do view **/
		
	// /views/_includes/header.php
        require ABSPATH . '/views/_includes/header.php';
		
	// /views/_includes/menu.php
        require ABSPATH . '/views/_includes/menu.php';
		
	// /views/noticias/index.php
        require ABSPATH . '/views/noticias/noticias-adm-view.php';
		
	// /views/_includes/footer.php
        require ABSPATH . '/views/_includes/footer.php';
		
    } // adm
	
} // class NoticiasController

Veja que temos duas ações agora, index e adm. Ambas serão acessadas pelas URLs:

  • index: dominio.com/noticias/
  • adm: dominio.com/noticias/adm/

Agora vamos criar o modelo para as notícias.

Criando o model para as notícias

Nosso model será compartilhado pelos dois views que teremos, um para a ação “adm” outro para “index”.

Lembre-se: Modelos ficam dentro da pasta models separados por pastas. Nosso model estará dentro da pasta noticias com o nome de noticias-adm-model.php.

Veja seu código:

<?php 
/**
 * Modelo para gerenciar notícias
 *
 * @package TutsupMVC
 * @since 0.1
 */
class NoticiasAdmModel extends MainModel
{

	/**
	 * $posts_per_page
	 *
	 * Receberá o número de posts por página para configurar a listagem de 
	 * notícias. Também utilizada na paginação. 
	 *
	 * @access public
	 */
	public $posts_por_pagina = 5;
	
	/**
	 * 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;
	}
	
	/**
	 * Lista notícias
	 *
	 * @since 0.1
	 * @access public
	 * @return array Os dados da base de dados
	 */
	public function listar_noticias () {
	
		// Configura as variáveis que vamos utilizar
		$id = $where = $query_limit = null;
		
		// Verifica se um parâmetro foi enviado para carregar uma notícia
		if ( is_numeric( chk_array( $this->parametros, 0 ) ) ) {
			
			// Configura o ID para enviar para a consulta
			$id = array ( chk_array( $this->parametros, 0 ) );
			
			// Configura a cláusula where da consulta
			$where = " WHERE noticia_id = ? ";
		}
		
		// Configura a página a ser exibida
		$pagina = ! empty( $this->parametros[1] ) ? $this->parametros[1] : 1;
		
		// A paginação inicia do 0
		$pagina--;
		
		// Configura o número de posts por página
		$posts_por_pagina = $this->posts_por_pagina;
		
		// O offset dos posts da consulta
		$offset = $pagina * $posts_por_pagina;
		
		/* 
		Esta propriedade foi configurada no noticias-adm-model.php para
		prevenir limite ou paginação na administração.
		*/
		if ( empty ( $this->sem_limite ) ) {
		
			// Configura o limite da consulta
			$query_limit = " LIMIT $offset,$posts_por_pagina ";
		
		}
		
		// Faz a consulta
		$query = $this->db->query(
			'SELECT * FROM noticias ' . $where . ' ORDER BY noticia_id DESC' . $query_limit,
			$id
		);
		
		// Retorna
		return $query->fetchAll();
	} // listar_noticias
	
	/**
	 * Obtém a notícia e atualiza os dados se algo for postado
	 *
	 * Obtém apenas uma notícia da base de dados para preencher o formulário de
	 * edição.
	 * Configura a propriedade $this->form_data.
	 *
	 * @since 0.1
	 * @access public
	 */
	public function obtem_noticia () {
		
		// Verifica se o primeiro parâmetro é "edit"
		if ( chk_array( $this->parametros, 0 ) != 'edit' ) {
			return;
		}
		
		// Verifica se o segundo parâmetro é um número
		if ( ! is_numeric( chk_array( $this->parametros, 1 ) ) ) {
			return;
		}
		
		// Configura o ID da notícia
		$noticia_id = chk_array( $this->parametros, 1 );
		
		/* 
		Verifica se algo foi postado e se está vindo do form que tem o campo
		insere_noticia.
		
		Se verdadeiro, atualiza os dados conforme a requisição.
		*/
		if ( 'POST' == $_SERVER['REQUEST_METHOD'] && ! empty( $_POST['insere_noticia'] ) ) {
		
			// Remove o campo insere_notica para não gerar problema com o PDO
			unset($_POST['insere_noticia']);
			
			// Verifica se a data foi enviada
			$data = chk_array( $_POST, 'noticia_data' );
			
			/*
			Inverte a data para os formatos dd-mm-aaaa hh:mm:ss
			ou aaaa-mm-dd hh:mm:ss
			*/
			$nova_data = $this->inverte_data( $data );
			
			// Adiciona a data no $_POST		
			$_POST['noticia_data'] = $nova_data;
			
			// Tenta enviar a imagem
			$imagem = $this->upload_imagem();
			
			// Verifica se a imagem foi enviada
			if ( $imagem ) {
				// Adiciona a imagem no $_POST
				$_POST['noticia_imagem'] = $imagem;
			}
			
			// Atualiza os dados
			$query = $this->db->update('noticias', 'noticia_id', $noticia_id, $_POST);
			
			// Verifica a consulta
			if ( $query ) {
				// Retorna uma mensagem
				$this->form_msg = '<p class="success">Notícia atualizada com sucesso!</p>';
			}
			
		}
		
		// Faz a consulta para obter o valor
		$query = $this->db->query(
			'SELECT * FROM noticias WHERE noticia_id = ? LIMIT 1',
			array( $noticia_id )
		);
		
		// Obtém os dados
		$fetch_data = $query->fetch();
		
		// Se os dados estiverem nulos, não faz nada
		if ( empty( $fetch_data ) ) {
			return;
		}
		
		// Configura os dados do formulário
		$this->form_data = $fetch_data;
		
	} // obtem_noticia
	
	/**
	 * Insere notícias
	 *
	 * @since 0.1
	 * @access public
	 */
	public function insere_noticia() {
	
		/* 
		Verifica se algo foi postado e se está vindo do form que tem o campo
		insere_noticia.
		*/
		if ( 'POST' != $_SERVER['REQUEST_METHOD'] || empty( $_POST['insere_noticia'] ) ) {
			return;
		}
		
		/*
		Para evitar conflitos apenas inserimos valores se o parâmetro edit
		não estiver configurado.
		*/
		if ( chk_array( $this->parametros, 0 ) == 'edit' ) {
			return;
		}
		
		// Só pra garantir que não estamos atualizando nada
		if ( is_numeric( chk_array( $this->parametros, 1 ) ) ) {
			return;
		}
			
		// Tenta enviar a imagem
		$imagem = $this->upload_imagem();
		
		// Verifica se a imagem foi enviada
		if ( ! $imagem ) {
			return;		
		}
		
		// Remove o campo insere_notica para não gerar problema com o PDO
		unset($_POST['insere_noticia']);
		
		// Insere a imagem em $_POST
		$_POST['noticia_imagem'] = $imagem;
		
		// Configura a data
		$data = chk_array( $_POST, 'noticia_data' );
		$nova_data = $this->inverte_data( $data );
					
		// Adiciona a data no POST
		$_POST['noticia_data'] = $nova_data;
		
		// Insere os dados na base de dados
		$query = $this->db->insert( 'noticias', $_POST );
		
		// Verifica a consulta
		if ( $query ) {
		
			// Retorna uma mensagem
			$this->form_msg = '<p class="success">Notícia atualizada com sucesso!</p>';
			return;
			
		} 
		
		// :(
		$this->form_msg = '<p class="error">Erro ao enviar dados!</p>';

	} // insere_noticia
	
	/**
	 * Apaga a notícia
	 *
	 * @since 0.1
	 * @access public
	 */
	public function apaga_noticia () {
		
		// O parâmetro del deverá ser enviado
		if ( chk_array( $this->parametros, 0 ) != 'del' ) {
			return;
		}
		
		// O segundo parâmetro deverá ser um ID numérico
		if ( ! is_numeric( chk_array( $this->parametros, 1 ) ) ) {
			return;
		}
		
		// Para excluir, o terceiro parâmetro deverá ser "confirma"
		if ( chk_array( $this->parametros, 2 ) != 'confirma' ) {
		
			// Configura uma mensagem de confirmação para o usuário
			$mensagem  = '<p class="alert">Tem certeza que deseja apgar a notícia?</p>';
			$mensagem .= '<p><a href="' . $_SERVER['REQUEST_URI'] . '/confirma/">Sim</a> | ';
			$mensagem .= '<a href="' . HOME_URI . '/noticias/adm/">Não</a></p>';
			
			// Retorna a mensagem e não excluir
			return $mensagem;
		}
		
		// Configura o ID da notícia
		$noticia_id = (int)chk_array( $this->parametros, 1 );
		
		// Executa a consulta
		$query = $this->db->delete( 'noticias', 'noticia_id', $noticia_id );
		
		// Redireciona para a página de administração de notícias
		echo '<meta http-equiv="Refresh" content="0; url=' . HOME_URI . '/noticias/adm/">';
		echo '<script type="text/javascript">window.location.href = "' . HOME_URI . '/noticias/adm/";</script>';
		
	} // apaga_noticia
	
	/**
	 * Envia a imagem
	 *
	 * @since 0.1
	 * @access public
	 */
	public function upload_imagem() {
	
		// Verifica se o arquivo da imagem existe
		if ( empty( $_FILES['noticia_imagem'] ) ) {
			return;
		}
		
		// Configura os dados da imagem
		$imagem         = $_FILES['noticia_imagem'];
		
		// Nome e extensão
		$nome_imagem    = strtolower( $imagem['name'] );
		$ext_imagem     = explode( '.', $nome_imagem );
		$ext_imagem     = end( $ext_imagem );
		$nome_imagem    = preg_replace( '/[^a-zA-Z0-9]/', '', $nome_imagem);
		$nome_imagem   .= '_' . mt_rand() . '.' . $ext_imagem;
		
		// Tipo, nome temporário, erro e tamanho
		$tipo_imagem    = $imagem['type'];
		$tmp_imagem     = $imagem['tmp_name'];
		$erro_imagem    = $imagem['error'];
		$tamanho_imagem = $imagem['size'];
		
		// Os mime types permitidos
		$permitir_tipos  = array(
			'image/bmp',
			'image/x-windows-bmp',
			'image/gif',
			'image/jpeg',
			'image/pjpeg',
			'image/png',
		);
		
		// Verifica se o mimetype enviado é permitido
		if ( ! in_array( $tipo_imagem, $permitir_tipos ) ) {
			// Retorna uma mensagem
			$this->form_msg = '<p class="error">Você deve enviar uma imagem.</p>';
			return;
		}
		
		// Tenta mover o arquivo enviado
		if ( ! move_uploaded_file( $tmp_imagem, UP_ABSPATH . '/' . $nome_imagem ) ) {
			// Retorna uma mensagem
			$this->form_msg = '<p class="error">Erro ao enviar imagem.</p>';
			return;
		}
		
		// Retorna o nome da imagem
		return $nome_imagem;
		
	} // upload_imagem
	
	/**
	 * Paginação
	 *
	 * @since 0.1
	 * @access public
	 */
	public function paginacao () {
	
		/* 
		Verifica se o primeiro parâmetro não é um número. Se for é um single
		e não precisa de paginação.
		*/
		if ( is_numeric( chk_array( $this->parametros, 0) ) ) {	
			return;
		}
		
		// Obtém o número total de notícias da base de dados
		$query = $this->db->query(
			'SELECT COUNT(*) as total FROM noticias '
		);
		$total = $query->fetch();
		$total = $total['total'];
		
		// Configura o caminho para a paginação
		$caminho_noticias = HOME_URI . '/noticias/index/page/';
		
		// Itens por página
		$posts_per_page = $this->posts_por_pagina;
		
		// Obtém a última página possível
		$last = ceil($total/$posts_per_page);
		
		// Configura a primeira página
		$first = 1;
		
		// Configura os offsets
		$offset1 = 3;
		$offset2 = 6;
		
		// Página atual
		$current = $this->parametros[1] ? $this->parametros[1] : 1;
		
		// Exibe a primeira página e reticências no início
		if ( $current > 4 ) {
			echo "<a href='$caminho_noticias$first'>$first</a> ... ";
		}
		
		// O primeiro loop toma conta da parte esquerda dos números
		for ( $i = ( $current - $offset1 ); $i < $current; $i++ ) {
			if ( $i > 0 ) {
				echo "<a href='$caminho_noticias$i'>$i</a>";
				
				// Diminiu o offset do segundo loop
				$offset2--;
			}
		}
		
		// O segundo loop toma conta da parte direita dos números
		// Obs.: A primeira expressão realmente não é necessária
		for ( ; $i < $current + $offset2; $i++ ) {
			if ( $i <= $last ) {
				echo "<a href='$caminho_noticias$i'>$i</a>";
			}
		}
		
		// Exibe reticências e a última página no final
		if ( $current <= ( $last - $offset1 ) ) {
			echo " ... <a href='$caminho_noticias$last'>$last</a>";
		}

	} // paginacao
	
} // NoticiasAdmModel

Este é um model bem grande, mas ele fará tudo o que precisamos.

Seus métodos fazem o seguinte:

  • listar_noticias() – Retorna um array com todas as notícias;
  • obtem_noticia() – Obtém uma única notícia. Também atualiza os dados caso algo seja enviado;
  • insere_noticia() – Insere uma notícia na base de dados;
  • apaga_noticia() – Apaga uma notícia da base de dados;
  • upload_imagem() – Faz o upload de imagens se alguma for enviada;
  • paginacao() – Cria uma paginação para apresentar as notícias na página inicial de notícias;

Os métodos estão bem comentados, portanto, leia os comentários e se ficar com dúvidas, basta questionar.

Agora vamos criar os views.

Criando um view para mostrar notícias

Agora vamos criar um dos views que precisamos, o para a área administrativa.

Lembre-se: Views ficam dentro da pasta views separados por pastas. Nosso view estará dentro da pasta noticias com o nome de noticias-adm-view.php e noticias-view.php.

Veja seu o código do arquivo noticias-adm-view.php.

/views/noticias/noticias-adm-view.php

<?php 
// Evita acesso direto a este arquivo
if ( ! defined('ABSPATH')) exit;

// Configura as URLs
$adm_uri = HOME_URI . '/noticias/adm/';
$edit_uri = $adm_uri . 'edit/';
$delete_uri = $adm_uri . 'del/';

		
// Carrega o método para obter uma notícia
$modelo->obtem_noticia();

// Carrega o método para inserir uma notícia
$modelo->insere_noticia();

// Carrega o método para apagar a notícia
$modelo->form_confirma = $modelo->apaga_noticia();

// Remove o limite de valores da lista de notícias
$modelo->sem_limite = true;
?>

<div class="wrap">

	<?php 
	// Mensagem de configuração caso o usuário tente apagar algo
	echo $modelo->form_confirma;
	?>

	<!-- Formulário de edição das notícias -->
	<form method="post" action="" enctype="multipart/form-data">
		<table class="form-table">
			<tr>
				<td>
					Título: <br>
					<input type="text" name="noticia_titulo" value="<?php 
					echo htmlentities( chk_array( $modelo->form_data, 'noticia_titulo') );
					?>" />
				</td>
			</tr>
			<tr>
				<td>
					Imagem: <br>
					<input type="file" name="noticia_imagem" value="" />
				</td>
			</tr>
			<tr>
				<td>
					Data: <br>
					<input type="text" name="noticia_data" value="<?php 
					$data = chk_array( $modelo->form_data, 'noticia_data');
					if ( $data && $data != '0000-00-00 00:00:00' )
					echo date('d-m-Y H:i:s', strtotime( $data ) );
					?>" />
				</td>
			</tr>
			<tr>
				<td>
					Autor: <br>
					<input type="text" name="noticia_autor" value="<?php 
					echo htmlentities( $_SESSION['userdata']['user_name'] );
					?>" />
				</td>
			</tr>
			<tr>
				<td>
					Texto da notícia: <br>
					<textarea name="noticia_texto"><?php
					echo htmlentities( chk_array( $modelo->form_data, 'noticia_texto') );
					?></textarea>
				</td>
			</tr>
			<tr>
				<td colspan="2">
					<?php 
					// Mensagem de feedback para o usuário
					echo $modelo->form_msg;
					?>
					<input type="submit" value="Save" />
				</td>
			</tr>
		</table>
		
		<input type="hidden" name="insere_noticia" value="1" />
	</form>
	
	<!-- LISTA AS NOTICIAS -->
	<?php $lista = $modelo->listar_noticias(); ?>

	<table class="list-table">

		<?php foreach( $lista as $noticia ):?>
			
			<tr>
				<td><?php echo $noticia['noticia_titulo']?></td>
				<td>
					<a href="<?php echo $edit_uri . $noticia['noticia_id']?>">
						Editar
					</a> 
					
					<a href="<?php echo $delete_uri . $noticia['noticia_id']?>">
						Apagar
					</a>
				</td>
			</tr>
			
		<?php endforeach; ?>

	</table>

</div> <!-- .wrap -->

Este view faz todo o sistema de CRUD das notícias utilizando as ações do nosso model.

/views/noticias/noticias-view.php

Veja agora como apresentaremos os dados para o usuário final (este é o view da ação index):

<?php 
// Evita acesso direto a este arquivo
if ( ! defined('ABSPATH')) exit; 
?>

<div class="wrap">

<?php
// Número de posts por página
$modelo->posts_por_pagina = 10;

// Lista notícias
$lista = $modelo->listar_noticias(); 
?>

<?php foreach( $lista as $noticia ):?>
	
	<!-- Título -->
	<h1>
		<a href="<?php echo HOME_URI?>/noticias/index/<?php echo $noticia['noticia_id']?>">
			<?php echo $noticia['noticia_titulo']?>
		</a>
	</h1>

	<?php 
	// Verifica se estamos visualizando uma única notícia
	if ( is_numeric( chk_array( $modelo->parametros, 0 ) ) ): // single
	?>
	
		<p>
		<?php echo $modelo->inverte_data( $noticia['noticia_data'] );?> | 
		<?php echo $noticia['noticia_autor'];?> 
		</p>
		
		<p>
			<img src="<?php 
			echo HOME_URI . '/views/_uploads/' . $noticia['noticia_imagem']; ?>">
		</p>
		
		<?php echo $noticia['noticia_texto'];?>
		
	<?php endif;  // single ?>
	
<?php endforeach; ?>

<?php $modelo->paginacao();?>

</div> <!-- .wrap -->

Isso deverá gerar uma lista com as notícias, links para a página de uma notícia única e uma paginação em baixo.

Dentro da notícia outros campos serão apresentados, como imagem, autor, data e texto.

Veja um exemplo:

Cadastro de notícias

Cadastro de notícias

Campos preenchidos

Campos preenchidos

Notícia sendo visualizada

Notícia sendo visualizada

Lembre-se, isto é apenas um exemplo da funcionalidade do sistema, você deve criar seus próprios modelos, controles e views.

Download e outras partes do artigo

Segue o link para download:

Caso queira contribuir com o projeto, acesso no Github:

Se tiver perdido alguma parte desse tutorial, deixo os links para facilitar sua vida:

Em caso de dúvidas, questione!

Até agora, criamos as partes mais simples do sistema, tais como criar um controller, model e view e exibir o conteúdo na tela, mas e se eu precisar de uma área restrita?

Nossa estrutura MVC foi criada pensando nisso, ou seja, além de você ter a possibilidade de criar partes restritas no seu sistema, também há a possibilidade de criar permissões de acesso para determinados M, V e C.

Além disso, você também pode restringir apenas pedaços de uma página, ou bloquear o acesso completo, fica a seu critério.

Neste artigo você vai aprender a criar o sistema de login, sistema de registro de usuários e MVCs com acesso restrito.

Artigos anteriores

Antes que você continue a ler, é necessário que você tenha visto os artigos anteriores, assim você entenderá melhor o conteúdo que verá aqui:

 

Nos artigos acima eu guio você por toda a estrutura do sistema, detalhando como cada área funciona e interage uma com a outra.

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.

Criando o controller de login

Vamos seguir o mesmo modelo da nossa última aula para criar o controller do nosso sistema de login. Felizmente, precisaremos apenas de um controller e um view, já que nossa classe “UserLogin” fará o resto pra gente.

controllers/login-controller.php

Para o controller do nosso login teremos o seguinte:

<?php
/**
 * LoginController - Controller de exemplo
 *
 * @package TutsupMVC
 * @since 0.1
 */
class LoginController extends MainController
{

    /**
    * Carrega a página "/views/login/index.php"
    */
    public function index() {
	// Título da página
	$this->title = 'Login';
		
	// Parametros da função
	$parametros = ( func_num_args() >= 1 ) ? func_get_arg(0) : array();
	
	// Login não tem Model
		
	/** Carrega os arquivos do view **/
		
	// /views/_includes/header.php
        require ABSPATH . '/views/_includes/header.php';
		
	// /views/_includes/menu.php
        require ABSPATH . '/views/_includes/menu.php';
		
	// /views/home/login-view.php
        require ABSPATH . '/views/login/login-view.php';
		
	// /views/_includes/footer.php
        require ABSPATH . '/views/_includes/footer.php';
		
    } // index
	
} // class LoginController

Este controller tem apenas uma ação – index – que será carregada automaticamente quando acessarmos a URL /login/ do nosso sistema. Dentro dessa ação, simplesmente incluímos os arquivos dos views que exibem HTML.

São eles:

/views/_includes/header.php

Este é um cabeçalho HTML comum, veja seu código:

<?php if ( ! defined('ABSPATH')) exit; ?>

<!DOCTYPE html>
<!--[if IE 7]>
<html class="ie ie7" lang="pt-BR">
<![endif]-->
<!--[if IE 8]>
<html class="ie ie8" lang="pt-BR">
<![endif]-->
<!--[if !(IE 7) & !(IE 8)]><!-->
<html lang="pt-BR">
<!--<![endif]-->

<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width">

	<link rel="stylesheet" href="<?php echo HOME_URI;?>/views/_css/style.css">

	<!--[if lt IE 9]>
	<script src="<?php echo HOME_URI;?>/views/_js/scripts.js"></script>
	<![endif]-->

	<title><?php echo $this->title; ?></title>
</head>
<body>

<div class="main-page">

 /views/_includes/menu.php

Outro arquivo HTML comum:

<?php if ( ! defined('ABSPATH')) exit; ?>

<?php if ( $this->login_required && ! $this->logged_in ) return; ?>

<nav class="menu clearfix">
	<ul>
		<li><a href="<?php echo HOME_URI;?>">Home</a></li>
		<li><a href="<?php echo HOME_URI;?>/login/">Login</a></li>
		<li><a href="<?php echo HOME_URI;?>/user-register/">User Register</a></li>
		<li><a href="<?php echo HOME_URI;?>/noticias/">Notícias</a></li>
		<li><a href="<?php echo HOME_URI;?>/noticias/adm/">Notícias Admin</a></li>
		<li><a href="<?php echo HOME_URI;?>/exemplo/">Exemplo básico</a></li>
	</ul>
</nav>

/views/_includes/footer.php

Este tem puramente HTML:

<?php if ( ! defined('ABSPATH')) exit; ?>

</div> <!-- .main-page (header.php) -->

</body>
</html>

 Restringindo acesso direto aos arquivos

É importante que você tenha em mente que usuários mais entendidos sobre PHP e HTML, poderão entender como o sistema funciona e tentar executar arquivos diretamente. Para restringir o acesso a direto a qualquer conteúdo do seu sistema de MVC, adicione a seguinte linha no cabeçalho do seu código:

<?php if ( ! defined('ABSPATH')) exit; ?>

 Criando o view do login

O view do nosso login terá apenas um formulário HTML, veja:

views/login/login-view.php

<?php if ( ! defined('ABSPATH')) exit; ?>

<div class="wrap">

	<form method="post">
		<table class="form-table">
			<tr>
				<td>User</td> 
				<td><input name="userdata[user]"></td>
			</tr>
			<tr>
				<td>Password </td>
				<td><input type="password" name="userdata[user_password]"></td>
			</tr>
			<tr>
				<td colspan="2">
					<input type="submit" value="Enter"> 
				</td>
			</tr>
		</table>
	</form>
	
</div> <!-- .wrap -->

Só isso já é suficiente para que o usuário faça login no sistema e seja redirecionado para a URL que tentou acessar antes do login. O problema é que não temos feedback para o usuário, nenhuma mensagem é apresentada na tela. Para resolver isto, podemos utilizar as propriedades retornadas pela classe UserLogin, veja:

<?php if ( ! defined('ABSPATH')) exit; ?>

<div class="wrap">

<?php
if ( $this->logged_in ) {
	echo '<p class="alert">Logado</p>';
}
?>

	<form method="post">
		<table class="form-table">
			<tr>
				<td>User</td> 
				<td><input name="userdata[user]"></td>
			</tr>
			<tr>
				<td>Password </td>
				<td><input type="password" name="userdata[user_password]"></td>
			</tr>
			<?php
			if ( $this->login_error ) {
				echo '<tr><td colspan="2" class="error">' . $this->login_error . '</td></tr>';
			}
			?>
			<tr>
				<td colspan="2">
					<input type="submit" value="Enter"> 
				</td>
			</tr>
		</table>
	</form>
	
</div> <!-- .wrap -->

Agora estamos verificando as propriedades $this->login_error para ver se existe algum erro no login, e $this->logged_in, para saber se o usuário está logado.

O formulário acima deverá exibir uma mensagem caso a senha do usuário estiver incorreta, se o usuário não existir, se ele está logado e coisas do tipo.

Formulário de login

Formulário de login

As mensagens estão em inglês porque a classe UserLogin era de outro sistema que eu havia criado anteriormente. Você pode alterar conforme preferir.

Criando o controller para registro de usuários

O controle de usuários é essencial para nossa aplicação, sem ele não poderíamos bloquear ou permitir nada.

Este controller só irá permitir que o view seja acessado por usuários logados. Além disso, ele também só irá permitir que um usuário visualize seu conteúdo se o mesmo tiver a permissão necessária.

Como funcionam as permissões?

Na verdade, você é quem escolhe como funcionam as permissões.

O formulário de cadastro terá um campo onde você separa as permissões por vírgula, por exemplo:

  • acessar-home, gerenciar-noticias, abrir-modelo-adm, e assim por diante…

Em seguida, é só verificar no controller se o usuário tem aquela permissão, por exemplo:

// Verifica se o usuário tem a permissão para acessar essa página
if (!$this->check_permissions('permissao-necessaria', $this->userdata['user_permissions'])) {

	// Exibe uma mensagem
	echo 'Você não tem permissões para acessar essa página.';
	
	// Finaliza aqui
	return;
}

Simples assim!

controllers/user-register-controller.php

Vejamos agora o código do nosso controller para registro de usuários:

<?php
/**
 * UserRegisterController - Controller de exemplo
 *
 * @package TutsupMVC
 * @since 0.1
 */
class UserRegisterController extends MainController
{

   /**
    * $login_required
    *
    * Se a página precisa de login
    *
    * @access public
    */
    public $login_required = true;

    /**
     * $permission_required
     *
     * Permissão necessária
     *
     * @access public
     */
    public $permission_required = 'user-register';

    /**
     * Carrega a página "/views/user-register/index.php"
     */
    public function index() {
		// Page title
		$this->title = 'User Register';
		
		// Verifica se o usuário está logado
		if ( ! $this->logged_in ) {
		
			// Se não; garante o logout
			$this->logout();
			
			// Redireciona para a página de login
			$this->goto_login();
			
			// Garante que o script não vai passar daqui
			return;
		
		}
		
		// Verifica se o usuário tem a permissão para acessar essa página
		if (!$this->check_permissions($this->permission_required, $this->userdata['user_permissions'])) {
		
			// Exibe uma mensagem
			echo 'Você não tem permissões para acessar essa página.';
			
			// Finaliza aqui
			return;
		}
	
		// Parametros da função
		$parametros = ( func_num_args() >= 1 ) ? func_get_arg(0) : array();
	
	// Carrega o modelo para este view
        $modelo = $this->load_model('user-register/user-register-model');
				
	/** Carrega os arquivos do view **/
		
	// /views/_includes/header.php
        require ABSPATH . '/views/_includes/header.php';
		
	// /views/_includes/menu.php
        require ABSPATH . '/views/_includes/menu.php';
		
	// /views/user-register/index.php
        require ABSPATH . '/views/user-register/user-register-view.php';
		
	// /views/_includes/footer.php
        require ABSPATH . '/views/_includes/footer.php';
		
    } // index
	
} // class UserRegisterController

Este controle verifica se o usuário está logado e se ele tem permissões para acessar tal página, veja o trecho:

public $permission_required = 'user-register';

/* ... */
		
// Verifica se o usuário está logado
if ( ! $this->logged_in ) {
	
	// Se não; garante o logout
	$this->logout();
	
	// Redireciona para a página de login
	$this->goto_login();
			
	// Garante que o script não vai passar daqui
	return;
		
}
		
// Verifica se o usuário tem a permissão para acessar essa página
if (!$this->check_permissions($this->permission_required, $this->userdata['user_permissions'])) {
	
	// Exibe uma mensagem
	echo 'Você não tem permissões para acessar essa página.';
		
	// Finaliza aqui
	return;
}

Depois dessa verificação, incluímos o model e os views.

Criando o model para registro de usuários

Vamos analisar o código do model.

models/user-register/user-register-model.php

Este é um modelo bem grande, pois já inclui todo o sistema de CRUD (create, read, update and delete). Mas não se assuste com o tamanho, pois ele fará praticamente a mesma coisa que todos os seus modelos irão fazer:

<?php 
/**
 * Classe para registros de usuários
 *
 * @package TutsupMVC
 * @since 0.1
 */

class UserRegisterModel
{

	/**
	 * $form_data
	 *
	 * Os dados do formulário de envio.
	 *
	 * @access public
	 */	
	public $form_data;

	/**
	 * $form_msg
	 *
	 * As mensagens de feedback para o usuário.
	 *
	 * @access public
	 */	
	public $form_msg;

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

	/**
	 * Construtor
	 * 
	 * Carrega  o DB.
	 *
	 * @since 0.1
	 * @access public
	 */
	public function __construct( $db = false ) {
                // Carrega o BD
		$this->db = $db;
	}

	/**
	 * Valida o formulário de envio
	 * 
	 * Este método pode inserir ou atualizar dados dependendo do campo de
	 * usuário.
	 *
	 * @since 0.1
	 * @access public
	 */
	public function validate_register_form () {
	
		// Configura os dados do formulário
		$this->form_data = array();
		
		// Verifica se algo foi postado
		if ( 'POST' == $_SERVER['REQUEST_METHOD'] && ! empty ( $_POST ) ) {
		
			// Faz o loop dos dados do post
			foreach ( $_POST as $key => $value ) {
			
				// Configura os dados do post para a propriedade $form_data
				$this->form_data[$key] = $value;
				
				// Nós não permitiremos nenhum campos em branco
				if ( empty( $value ) ) {
					
					// Configura a mensagem
					$this->form_msg = '<p class="form_error">There are empty fields. Data has not been sent.</p>';
					
					// Termina
					return;
					
				}			
			
			}
		
		} else {
		
			// Termina se nada foi enviado
			return;
			
		}
		
		// Verifica se a propriedade $form_data foi preenchida
		if( empty( $this->form_data ) ) {
			return;
		}
		
		// Verifica se o usuário existe
		$db_check_user = $this->db->query (
			'SELECT * FROM `users` WHERE `user` = ?', 
			array( 
				chk_array( $this->form_data, 'user')		
			) 
		);
		
		// Verifica se a consulta foi realizada com sucesso
		if ( ! $db_check_user ) {
			$this->form_msg = '<p class="form_error">Internal error.</p>';
			return;
		}
		
		// Obtém os dados da base de dados MySQL
		$fetch_user = $db_check_user->fetch();
		
		// Configura o ID do usuário
		$user_id = $fetch_user['user_id'];
		
		// Precisaremos de uma instância da classe Phpass
		// veja http://www.openwall.com/phpass/
		$password_hash = new PasswordHash(8, FALSE);
		
		// Cria o hash da senha
		$password = $password_hash->HashPassword( $this->form_data['user_password'] );
		
		// Verifica se as permissões tem algum valor inválido: 
		// 0 a 9, A a Z e , . - _
		if ( preg_match( '/[^0-9A-Za-z,.-_s ]/is', $this->form_data['user_permissions'] ) ) {
			$this->form_msg = '<p class="form_error">Use just letters, numbers and a comma for permissions.</p>';
			return;
		}		
		
		// Faz um trim nas permissões
		$permissions = array_map('trim', explode(',', $this->form_data['user_permissions']));
		
		// Remove permissões duplicadas
		$permissions = array_unique( $permissions );
		
		// Remove valores em branco
		$permissions = array_filter( $permissions );
		
		// Serializa as permissões
		$permissions = serialize( $permissions );
		
		
		// Se o ID do usuário não estiver vazio, atualiza os dados
		if ( ! empty( $user_id ) ) {

			$query = $this->db->update('users', 'user_id', $user_id, array(
				'user_password' => $password, 
				'user_name' => chk_array( $this->form_data, 'user_name'), 
				'user_session_id' => md5(time()), 
				'user_permissions' => $permissions, 
			));
			
			// Verifica se a consulta está OK e configura a mensagem
			if ( ! $query ) {
				$this->form_msg = '<p class="form_error">Internal error. Data has not been sent.</p>';
				
				// Termina
				return;
			} else {
				$this->form_msg = '<p class="form_success">User successfully updated.</p>';
				
				// Termina
				return;
			}
		// Se o ID do usuário estiver vazio, insere os dados
		} else {
		
			// Executa a consulta 
			$query = $this->db->insert('users', array(
				'user' => chk_array( $this->form_data, 'user'), 
				'user_password' => $password, 
				'user_name' => chk_array( $this->form_data, 'user_name'), 
				'user_session_id' => md5(time()), 
				'user_permissions' => $permissions, 
			));
			
			// Verifica se a consulta está OK e configura a mensagem
			if ( ! $query ) {
				$this->form_msg = '<p class="form_error">Internal error. Data has not been sent.</p>';
				
				// Termina
				return;
			} else {
				$this->form_msg = '<p class="form_success">User successfully registered.</p>';
				
				// Termina
				return;
			}
		}
	} // validate_register_form
	
	/**
	 * Obtém os dados do formulário
	 * 
	 * Obtém os dados para usuários registrados
	 *
	 * @since 0.1
	 * @access public
	 */
	public function get_register_form ( $user_id = false ) {
	
		// O ID de usuário que vamos pesquisar
		$s_user_id = false;
		
		// Verifica se você enviou algum ID para o método
		if ( ! empty( $user_id ) ) {
			$s_user_id = (int)$user_id;
		}
		
		// Verifica se existe um ID de usuário
		if ( empty( $s_user_id ) ) {
			return;
		}
		
		// Verifica na base de dados
		$query = $this->db->query('SELECT * FROM `users` WHERE `user_id` = ?', array( $s_user_id ));
		
		// Verifica a consulta
		if ( ! $query ) {
			$this->form_msg = '<p class="form_error">Usuário não existe.</p>';
			return;
		}
		
		// Obtém os dados da consulta
		$fetch_userdata = $query->fetch();
		
		// Verifica se os dados da consulta estão vazios
		if ( empty( $fetch_userdata ) ) {
			$this->form_msg = '<p class="form_error">User do not exists.</p>';
			return;
		}
		
		// Configura os dados do formulário
		foreach ( $fetch_userdata as $key => $value ) {
			$this->form_data[$key] = $value;
		}
		
		// Por questões de segurança, a senha só poderá ser atualizada
		$this->form_data['user_password'] = null;
		
		// Remove a serialização das permissões
		$this->form_data['user_permissions'] = unserialize($this->form_data['user_permissions']);
		
		// Separa as permissões por vírgula
		$this->form_data['user_permissions'] = implode(',', $this->form_data['user_permissions']);
	} // get_register_form
	
	/**
	 * Apaga usuários
	 * 
	 * @since 0.1
	 * @access public
	 */
	public function del_user ( $parametros = array() ) {

		// O ID do usuário
		$user_id = null;
		
		// Verifica se existe o parâmetro "del" na URL
		if ( chk_array( $parametros, 0 ) == 'del' ) {

			// Mostra uma mensagem de confirmação
			echo '<p class="alert">Tem certeza que deseja apagar este valor?</p>';
			echo '<p><a href="' . $_SERVER['REQUEST_URI'] . '/confirma">Sim</a> | 
			<a href="' . HOME_URI . '/user-register">Não</a> </p>';
			
			// Verifica se o valor do parâmetro é um número
			if ( 
				is_numeric( chk_array( $parametros, 1 ) )
				&& chk_array( $parametros, 2 ) == 'confirma' 
			) {
				// Configura o ID do usuário a ser apagado
				$user_id = chk_array( $parametros, 1 );
			}
		}
		
		// Verifica se o ID não está vazio
		if ( !empty( $user_id ) ) {
		
			// O ID precisa ser inteiro
			$user_id = (int)$user_id;
			
			// Deleta o usuário
			$query = $this->db->delete('users', 'user_id', $user_id);
			
			// Redireciona para a página de registros
			echo '<meta http-equiv="Refresh" content="0; url=' . HOME_URI . '/user-register/">';
			echo '<script type="text/javascript">window.location.href = "' . HOME_URI . '/user-register/";</script>';
			return;
		}
	} // del_user
	
	/**
	 * Obtém a lista de usuários
	 * 
	 * @since 0.1
	 * @access public
	 */
	public function get_user_list() {
	
		// Simplesmente seleciona os dados na base de dados 
		$query = $this->db->query('SELECT * FROM `users` ORDER BY user_id DESC');
		
		// Verifica se a consulta está OK
		if ( ! $query ) {
			return array();
		}
		// Preenche a tabela com os dados do usuário
		return $query->fetchAll();
	} // get_user_list
}

Como eu disse, isso é o que estamos acostumados a fazer com aplicações. Conectamos à base de dados e gerenciamos os dados.

É importante lembrar, que tudo o que está descrito no model acima, será acionado pelo view.

Por falar em view, vamos analisar como ele irá funcionar.

Criando um view para registro de usuários

O view é simplesmente um formulário HTML, mas também vamos executar as ações do model que precisamos, veja seu código:

views/user-register/user-register-view.php

<?php if ( ! defined('ABSPATH')) exit; ?>

<div class="wrap">

<?php
// Carrega todos os métodos do modelo
$modelo->validate_register_form();
$modelo->get_register_form( chk_array( $parametros, 1 ) );
$modelo->del_user( $parametros );
?>

<form method="post" action="">
	<table class="form-table">
		<tr>
			<td>Name: </td>
			<td> <input type="text" name="user_name" value="<?php 
				echo htmlentities( chk_array( $modelo->form_data, 'user_name') );
				?>" /></td>
		</tr>
		<tr>
			<td>User: </td>
			<td> <input type="text" name="user" value="<?php
				echo htmlentities( chk_array( $modelo->form_data, 'user') );
			?>" /></td>
		</tr>
		<tr>
			<td>Password: </td>
			<td> <input type="password" name="user_password" value="<?php
			echo htmlentities( chk_array( $modelo->form_data, 'user_password') );
			?>" /></td>
		</tr>
		<tr>
			<td>Permissions <br><small>(Separate permissions using commas)</small>: </td>
			<td> <input type="text" name="user_permissions" value="<?php
			echo htmlentities( chk_array( $modelo->form_data, 'user_permissions') );
			?>" /></td>
		</tr>
		<tr>
			<td colspan="2">
				<?php echo $modelo->form_msg;?>
				<input type="submit" value="Save" />
				<a href="<?php echo HOME_URI . '/user-register';?>">New user</a>
			</td>
		</tr>
	</table>
</form>

<?php 
// Lista os usuários
$lista = $modelo->get_user_list(); 
?>


<table class="list-table">
	<thead>
		<tr>
			<th>ID</th>
			<th>Usuário</th>
			<th>Name</th>
			<th>Permissões</th>
			<th>Edição</th>
		</tr>
	</thead>
			
	<tbody>
			
		<?php foreach ($lista as $fetch_userdata): ?>

			<tr>
			
				<td> <?php echo $fetch_userdata['user_id'] ?> </td>
				<td> <?php echo $fetch_userdata['user'] ?> </td>
				<td> <?php echo $fetch_userdata['user_name'] ?> </td>
				<td> <?php echo implode( ',', unserialize( $fetch_userdata['user_permissions'] ) ) ?> </td>
				
				<td> 
					<a href="<?php echo HOME_URI ?>/user-register/index/edit/<?php echo $fetch_userdata['user_id'] ?>">Edit</a>
					<a href="<?php echo HOME_URI ?>/user-register/index/del/<?php echo $fetch_userdata['user_id'] ?>">Delete</a>
				</td>

			</tr>
			
		<?php endforeach;?>
			
	</tbody>
</table>

</div> <!-- .wrap -->

Acessamos as ações que queremos do nosso model através da variável $modelo, criada no nosso controller.

Apenas com este view, temos a capacidade de criar, apagar, editar e visualizar todos os usuários.

Lembre-se que utilizamos um hash de senha bem seguro, portanto, não podemos obter os dados de senha dos usuários, somente modificar a mesma.

Formulário de registro

Formulário de registro

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.

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!

 

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!

 

 

Continuando com o desenvolvimento da nossa aplicação com estrutura MVC em PHP, hoje você vai entender como funciona a classe TutsupMVC, que está presente na pasta classes, em um arquivo chamado class-TutsupMVC.php.

É uma classe relativamente pequena, porém muito importante. Ela carrega o controlador e executa a ação que irá incluir o model e view, formando assim a estrutura MVC. Se ela não funcionar corretamente, toda nossa aplicação irá ter problemas, por isso é muito importante que você saiba como ela funciona.

Além disso, também vamos falar sobre todas as classes que TutsupMVC carrega, gerando um desencadeamento de classes que serão carregadas até gerar um view para o usuário.

Lembre-se que este artigo é parte de uma série de artigos sobre MVC em PHP, não deixe de ler a aula anterior:

Então vamos lá!

Como a classe TutsupMVC funciona?

Antes de vermos códigos, vamos analisar um desenho simples mostrando como a classe funciona:

 

Classe TutsupMVC e o fluxo da informação

Classe TutsupMVC e o fluxo da informação

Na imagem acima, as classes que estão em amarelo são parte da estrutura. A classe TutsupMVC desencadeia uma série de outras classes que fazem parte de um quebra-cabeça para fazer a aplicação funcionar conforme pretendemos.

O controlador personalizado terá todos os métodos e propriedades presentes em todas essas classes, bem como as funcionalidades, como verificação se o usuário está ou não logado, permissões, conexão PDO, métodos para criar, apagar, atualizar e editar dados da base de dados, e qualquer coisa que criarmos posteriormente.

Lembra da imagem que apresentei no último artigo sobre MVC em PHP? Veja agora uma junção de ambas:

Modelo MVC em PHP

Modelo MVC em PHP

Com os modelos acima em mente, veja alguns detalhes importantes sobre as classes:

  1. TutsupMVC só vai tentar carregar seu controller se isso for especificado na URL. Por exemplo: exemplo.com/seu-controller/. Caso contrário, o controlador “home” será incluído e a ação index() será executada;
  2. Se nenhuma ação for especificada na URL (exemplo.com/seu-controller/acao/), TutsupMVC vai tentar carregar a ação index(); se a ação index() não existir, a página de erro 404.php é incluída;
  3. Seu Controller deverá estender a classe “MainController”;
  4. Seu Model deverá estender a classe “MainModel”;
  5. Seu Model estará disponível dentro do seu view por um objeto que você mesmo vai criar;

Agora vamos ver um pouco de código PHP.

classes/class-TutsupMVC.php

Então vamos ver o que realmente interessa, o código.

Caso queira baixar o que já criamos até agora para acompanhar, segue o link:

A classe encontra-se em: classes/class-TutsupMVC.php e contém o seguinte:

<?php
/**
 * TutsupMVC - Gerencia Models, Controllers e Views
 *
 * @package TutsupMVC
 * @since 0.1
 */
class TutsupMVC
{

	/**
	 * $controlador
	 *
	 * Receberá o valor do controlador (Vindo da URL).
	 * exemplo.com/controlador/
	 *
	 * @access private
	 */
	private $controlador;
	
	/**
	 * $acao
	 *
	 * Receberá o valor da ação (Também vem da URL):
	 * exemplo.com/controlador/acao
	 *
	 * @access private
	 */
	private $acao;
	
	/**
	 * $parametros
	 *
	 * Receberá um array dos parâmetros (Também vem da URL):
	 * exemplo.com/controlador/acao/param1/param2/param50
	 *
	 * @access private
	 */
	private $parametros;
	
	/**
	 * $not_found
	 *
	 * Caminho da página não encontrada
	 *
	 * @access private
	 */
	private $not_found = '/includes/404.php';
	
	/**
	 * Construtor para essa classe
	 *
	 * Obtém os valores do controlador, ação e parâmetros. Configura 
	 * o controlado e a ação (método).
	 */
	public function __construct () {
		
		// Obtém os valores do controlador, ação e parâmetros da URL.
		// E configura as propriedades da classe.
		$this->get_url_data();
		
		/**
		 * Verifica se o controlador existe. Caso contrário, adiciona o
		 * controlador padrão (controllers/home-controller.php) e chama o método index().
		 */
		if ( ! $this->controlador ) {
			
			// Adiciona o controlador padrão
			require_once ABSPATH . '/controllers/home-controller.php';
			
			// Cria o objeto do controlador "home-controller.php"
			// Este controlador deverá ter uma classe chamada HomeController
			$this->controlador = new HomeController();
			
			// Executa o método index()
			$this->controlador->index();
			
			// FIM :)
			return;
		
		}
		
		// Se o arquivo do controlador não existir, não faremos nada
		if ( ! file_exists( ABSPATH . '/controllers/' . $this->controlador . '.php' ) ) {
			// Página não encontrada
			require_once ABSPATH . $this->not_found;
			
			// FIM :)
			return;
		}
		
		// Inclui o arquivo do controlador
		require_once ABSPATH . '/controllers/' . $this->controlador . '.php';
		
		// Remove caracteres inválidos do nome do controlador para gerar o nome
		// da classe. Se o arquivo chamar "news-controller.php", a classe deverá
		// se chamar NewsController.
		$this->controlador = preg_replace( '/[^a-zA-Z]/i', '', $this->controlador );
		
		// Se a classe do controlador indicado não existir, não faremos nada
		if ( ! class_exists( $this->controlador ) ) {
			// Página não encontrada
			require_once ABSPATH . $this->not_found;

			// FIM :)
			return;
		} // class_exists
		
		// Cria o objeto da classe do controlador e envia os parâmetros
		$this->controlador = new $this->controlador( $this->parametros );

		// Se o método indicado existir, executa o método e envia os parâmetros
		if ( method_exists( $this->controlador, $this->acao ) ) {
			$this->controlador->{$this->acao}( $this->parametros );
			
			// FIM :)
			return;
		} // method_exists
		
		// Sem ação, chamamos o método index
		if ( ! $this->acao && method_exists( $this->controlador, 'index' ) ) {
			$this->controlador->index( $this->parametros );		
			
			// FIM :)
			return;
		} // ! $this->acao 
		
		// Página não encontrada
		require_once ABSPATH . $this->not_found;
		
		// FIM :)
		return;
	} // __construct
	
	/**
	 * Obtém parâmetros de $_GET['path']
	 *
	 * Obtém os parâmetros de $_GET['path'] e configura as propriedades 
	 * $this->controlador, $this->acao e $this->parametros
	 *
	 * A URL deverá ter o seguinte formato:
	 * http://www.example.com/controlador/acao/parametro1/parametro2/etc...
	 */
	public function get_url_data () {
		
		// Verifica se o parâmetro path foi enviado
		if ( isset( $_GET['path'] ) ) {
	
			// Captura o valor de $_GET['path']
			$path = $_GET['path'];
			
			// Limpa os dados
                        $path = rtrim($path, '/');
                        $path = filter_var($path, FILTER_SANITIZE_URL);
            
			// Cria um array de parâmetros
			$path = explode('/', $path);
			
			// Configura as propriedades
			$this->controlador  = chk_array( $path, 0 );
			$this->controlador .= '-controller';
			$this->acao         = chk_array( $path, 1 );
			
			// Configura os parâmetros
			if ( chk_array( $path, 2 ) ) {
				unset( $path[0] );
				unset( $path[1] );
				
				// Os parâmetros sempre virão após a ação
				$this->parametros = array_values( $path );
			}
			
			
			// DEBUG
			//
			// echo $this->controlador . '<br>';
			// echo $this->acao        . '<br>';
			// echo '<pre>';
			// print_r( $this->parametros );
			// echo '</pre>';
		}
	
	} // get_url_data
	
} // class TutsupMVC

É bastante código, mas vamos ver o que cada parte faz:

  1. O construtor da classe carrega o método get_url_data;
  2. get_url_data obtém os parâmetros necessários da URL;
  3. O primeiro parâmetro é o controlador que você deverá criar. Os controladores “home”, “login”, “user-register” e “noticias” já existem e vêm embutidos no pacote que você baixou. Eles servem apenas como exemplo, já que você poderá criar seus próprios models, controllers e views; Se você não indicar nenhum controlador, a ação index() do controlador “home” será executada;
  4. O segundo parâmetro é a ação. Por ação, me refiro a um método que existe dentro do controller para carregar o(s) model(s) e view(s). Se você não indicar uma ação, a classe irá buscar a ação index() dentro do seu controlador; Se essa ação não existir, a página de erro 404 é incluída (página não encontrada). Você pode personalizar essa página conforme preferir;
  5. Do terceiro parâmetro em diante, a classe irá considerar os valores como parâmetros, e enviará os dados para o seu controlador. Você pode manipular os dados conforme preferir;
  6. Tudo isso é executado no construtor da nossa classe.

Vamos ver um exemplo do controlador “home-controller.php”, que está dentro da pasta controllers.

controllers/home-controller.php

Este é o controlador padrão que é carregado quando nenhum controller é enviado para a URL. Por padrão, ele não tem nenhum model, apenas views.

Veja seu código:

<?php
/**
 * home - Controller de exemplo
 *
 * @package TutsupMVC
 * @since 0.1
 */
class HomeController extends MainController
{

	/**
	 * Carrega a página "/views/home/home-view.php"
	 */
    public function index() {
		// Título da página
		$this->title = 'Home';
		
		// Parametros da função
		$parametros = ( func_num_args() >= 1 ) ? func_get_arg(0) : array();
	
		// Essa página não precisa de modelo (model)
		
		/** Carrega os arquivos do view **/
		
		// /views/_includes/header.php
                require ABSPATH . '/views/_includes/header.php';
		
		// /views/_includes/menu.php
                require ABSPATH . '/views/_includes/menu.php';
		
		// /views/home/home-view.php
                require ABSPATH . '/views/home/home-view.php';
		
		// /views/_includes/footer.php
                require ABSPATH . '/views/_includes/footer.php';
		
    } // index
	
} // class HomeController

Perceba que ele tem apenas uma ação (um método), chamado de index().

Este método apenas inclui os views que preciso para exibir o conteúdo na tela, como:

  • /views/_includes/header.php – Cabeçalho HTML
  • /views/_includes/menu.php – Menu HTML
  • /views/home/home-view.php – Conteúdo HTML
  • /views/_includes/footer.php – Rodapé HTML

Você também pode incluir esses arquivos diretamente no seu view, neste caso você só precisaria incluir o view padrão (home-view.php) e o restante seria feito tudo dentro desse arquivo.

Existem mais coisas que você pode configurar, por exemplo:

// Título da página
$this->title = 'Home';
		
// Parametros da função
$parametros = ( func_num_args() >= 1 ) ? func_get_arg(0) : array();

No trecho acima, estamos configurando o título da página e recebendo os parâmetros que a classe TutsupMVC nos enviou.

Ainda vamos falar sobre controllers mais avançados, com permissão de usuário, e coisas do tipo, mas vamos deixar assim por enquanto.

Como a nossa classe HomeController estende a classe MainController, vamos ver o conteúdo dessa classe.

classes/class-MainController.php

Este é nosso controller padrão, todos os outros controllers irão estender essa classe. Veja seu código:

<?php
/**
 * MainController - Todos os controllers deverão estender essa classe
 *
 * @package TutsupMVC
 * @since 0.1
 */
class MainController extends UserLogin
{

	/**
	 * $db
	 *
	 * Nossa conexão com a base de dados. Manterá o objeto PDO
	 *
	 * @access public
	 */
	public $db;

	/**
	 * $phpass
	 *
	 * Classe phpass 
	 *
	 * @see http://www.openwall.com/phpass/
	 * @access public
	 */
	public $phpass;

	/**
	 * $title
	 *
	 * Título das páginas 
	 *
	 * @access public
	 */
	public $title;

	/**
	 * $login_required
	 *
	 * Se a página precisa de login
	 *
	 * @access public
	 */
	public $login_required = false;

	/**
	 * $permission_required
	 *
	 * Permissão necessária
	 *
	 * @access public
	 */
	public $permission_required = 'any';

	/**
	 * $parametros
	 *
	 * @access public
	 */
	public $parametros = array();
	
	/**
	 * Construtor da classe
	 *
	 * Configura as propriedades e métodos da classe.
	 *
	 * @since 0.1
	 * @access public
	 */
	public function __construct ( $parametros = array() ) {
	
		// Instancia do DB
		$this->db = new TutsupDB();
		
		// Phpass
		$this->phpass = new PasswordHash(8, false);
		
		// Parâmetros
		$this->parametros = $parametros;
		
		// Verifica o login
		$this->check_userlogin();
		
	} // __construct
	
	/**
	 * Load model
	 *
	 * Carrega os modelos presentes na pasta /models/.
	 *
	 * @since 0.1
	 * @access public
	 */
	public function load_model( $model_name = false ) {
	
		// Um arquivo deverá ser enviado
		if ( ! $model_name ) return;
		
		// Garante que o nome do modelo tenha letras minúsculas
		$model_name =  strtolower( $model_name );
		
		// Inclui o arquivo
		$model_path = ABSPATH . '/models/' . $model_name . '.php';
		
		// Verifica se o arquivo existe
		if ( file_exists( $model_path ) ) {
		
			// Inclui o arquivo
			require_once $model_path;
			
			// Remove os caminhos do arquivo (se tiver algum)
			$model_name = explode('/', $model_name);
			
			// Pega só o nome final do caminho
			$model_name = end( $model_name );
			
			// Remove caracteres inválidos do nome do arquivo
			$model_name = preg_replace( '/[^a-zA-Z0-9]/is', '', $model_name );
			
			// Verifica se a classe existe
			if ( class_exists( $model_name ) ) {
			
				// Retorna um objeto da classe
				return new $model_name( $this->db, $this );
			
			}
			
			// The end :)
			return;
			
		} // load_model
		
	} // load_model

} // class MainController

Nosso controlador principal configura tudo o que precisamos em seu construtor, veja essa parte do código:

// Instancia do DB
$this->db = new TutsupDB();
		
// Phpass
$this->phpass = new PasswordHash(8, false);
		
// Parâmetros
$this->parametros = $parametros;
		
// Verifica o login
$this->check_userlogin();

Aqui configuramos nossa conexão com a base de dados, criamos o objeto da classe Phpass, configuramos os parâmetros e acionamos o método que verifica se o usuário estão ou não logado.

Além disso, temos um outro método que carrega os modelos. Vamos falar dele posteriormente.

Perceba que nossa classe MainController estende outra classe chamada de UserLogin, que é responsável por verificar tudo do usuário, como fazer login, logout, verificar se ele está logado e coisas do tipo.

Vamos ver o conteúdo da classe UserLogin.

classes/class-UserLogin.php

A classe UserLogin é responsável por tudo o que é relacionado ao usuário em si, veja:

<?php
/**
 * UserLogin - Manipula os dados de usuários
 *
 * Manipula os dados de usuários, faz login e logout, verifica permissões e 
 * redireciona página para usuários logados.
 *
 * @package TutsupMVC
 * @since 0.1
 */
class UserLogin
{
	/**
	 * Usuário logado ou não
	 *
	 * Verdadeiro se ele estiver logado.
	 *
	 * @public
	 * @access public
	 * @var bol
	 */
	public $logged_in;
	
	/**
	 * Dados do usuário
	 *
	 * @public
	 * @access public
	 * @var array
	 */
	public $userdata;
	
	/**
	 * Mensagem de erro para o formulário de login
	 *
	 * @public
	 * @access public
	 * @var string
	 */
	public $login_error;
	
	/**
	 * Verifica o login
	 *
	 * Configura as propriedades $logged_in e $login_error. Também
	 * configura o array do usuário em $userdata
	 */
	public function check_userlogin () {
	
		// Verifica se existe uma sessão com a chave userdata
		// Tem que ser um array e não pode ser HTTP POST
		if ( isset( $_SESSION['userdata'] )
			 && ! empty( $_SESSION['userdata'] )
			 && is_array( $_SESSION['userdata'] ) 
			 && ! isset( $_POST['userdata'] )
			) { 
			// Configura os dados do usuário
			$userdata = $_SESSION['userdata'];
			
			// Garante que não é HTTP POST
			$userdata['post'] = false;
		}
		
		// Verifica se existe um $_POST com a chave userdata
		// Tem que ser um array
		if ( isset( $_POST['userdata'] )
			 && ! empty( $_POST['userdata'] )
			 && is_array( $_POST['userdata'] ) 
			) {
			// Configura os dados do usuário
			$userdata = $_POST['userdata'];
			
			// Garante que é HTTP POST
			$userdata['post'] = true;
		}

		// Verifica se existe algum dado de usuário para conferir
		if ( ! isset( $userdata ) || ! is_array( $userdata ) ) {
		
			// Remove qualquer sessão que possa existir sobre o usuário
			$this->logout();
		
			return;
		}

		// Passa os dados do post para uma variável
		if ( $userdata['post'] === true ) {
			$post = true;
		} else {
			$post = false;
		}
		
		// Remove a chave post do array userdata
		unset( $userdata['post'] );
		
		// Verifica se existe algo a conferir
		if ( empty( $userdata ) ) {
			$this->logged_in = false;
			$this->login_error = null;
		
			// Remove qualquer sessão que possa existir sobre o usuário
			$this->logout();
		
			return;
		}
		
		// Extrai variáveis dos dados do usuário
		extract( $userdata );
		
		// Verifica se existe um usuário e senha
		if ( ! isset( $user ) || ! isset( $user_password ) ) {
			$this->logged_in = false;
			$this->login_error = null;
		
			// Remove qualquer sessão que possa existir sobre o usuário
			$this->logout();
		
			return;
		}
		
		// Verifica se o usuário existe na base de dados
		$query = $this->db->query( 
			'SELECT * FROM users WHERE user = ? LIMIT 1', 
			array( $user ) 
		);
		
		// Verifica a consulta
		if ( ! $query ) {
			$this->logged_in = false;
			$this->login_error = 'Internal error.';
		
			// Remove qualquer sessão que possa existir sobre o usuário
			$this->logout();
		
			return;
		}
		
		// Obtém os dados da base de usuário
		$fetch = $query->fetch(PDO::FETCH_ASSOC);
		
		// Obtém o ID do usuário
		$user_id = (int) $fetch['user_id'];
		
		// Verifica se o ID existe
		if ( empty( $user_id ) ){
			$this->logged_in = false;
			$this->login_error = 'User do not exists.';
		
			// Remove qualquer sessão que possa existir sobre o usuário
			$this->logout();
		
			return;
		}
		
		// Confere se a senha enviada pelo usuário bate com o hash do BD
		if ( $this->phpass->CheckPassword( $user_password, $fetch['user_password'] ) ) {
			
			// Se for uma sessão, verifica se a sessão bate com a sessão do BD
			if ( session_id() != $fetch['user_session_id'] && ! $post ) { 
				$this->logged_in = false;
				$this->login_error = 'Wrong session ID.';
				
				// Remove qualquer sessão que possa existir sobre o usuário
				$this->logout();
			
				return;
			}
			
			// Se for um post
			if ( $post ) {
				// Recria o ID da sessão
				session_regenerate_id();
				$session_id = session_id();
				
				// Envia os dados de usuário para a sessão
				$_SESSION['userdata'] = $fetch;
				
				// Atualiza a senha
				$_SESSION['userdata']['user_password'] = $user_password;
				
				// Atualiza o ID da sessão
				$_SESSION['userdata']['user_session_id'] = $session_id;
				
				// Atualiza o ID da sessão na base de dados
				$query = $this->db->query(
					'UPDATE users SET user_session_id = ? WHERE user_id = ?',
					array( $session_id, $user_id )
				);
			}
				
			// Obtém um array com as permissões de usuário
			$_SESSION['userdata']['user_permissions'] = unserialize( $fetch['user_permissions'] );

			// Configura a propriedade dizendo que o usuário está logado
			$this->logged_in = true;
			
			// Configura os dados do usuário para $this->userdata
			$this->userdata = $_SESSION['userdata'];
			
			// Verifica se existe uma URL para redirecionar o usuário
			if ( isset( $_SESSION['goto_url'] ) ) {
				// Passa a URL para uma variável
				$goto_url = urldecode( $_SESSION['goto_url'] );
				
				// Remove a sessão com a URL
				unset( $_SESSION['goto_url'] );
				
				// Redireciona para a página
				echo '<meta http-equiv="Refresh" content="0; url=' . $goto_url . '">';
				echo '<script type="text/javascript">window.location.href = "' . $goto_url . '";</script>';
				//header( 'location: ' . $goto_url );
			}
			
			return;
		} else {
			// O usuário não está logado
			$this->logged_in = false;
			
			// A senha não bateu
			$this->login_error = 'Password does not match.';
		
			// Remove tudo
			$this->logout();
		
			return;
		}
	}
	
	/**
	 * Logout
	 *
	 * Remove tudo do usuário.
	 *
	 * @param bool $redirect Se verdadeiro, redireciona para a página de login
	 * @final
	 */
	protected function logout( $redirect = false ) {
		// Remove all data from $_SESSION['userdata']
		$_SESSION['userdata'] = array();
		
		// Only to make sure (it isn't really needed)
		unset( $_SESSION['userdata'] );
		
		// Regenerates the session ID
		session_regenerate_id();
		
		if ( $redirect === true ) {
			// Send the user to the login page
			$this->goto_login();
		}
	}
	
	/**
	 * Vai para a página de login
	 */
	protected function goto_login() {
		// Verifica se a URL da HOME está configurada
		if ( defined( 'HOME_URI' ) ) {
			// Configura a URL de login
			$login_uri  = HOME_URI . '/login/';
			
			// A página em que o usuário estava
			$_SESSION['goto_url'] = urlencode( $_SERVER['REQUEST_URI'] );
			
			// Redireciona
			echo '<meta http-equiv="Refresh" content="0; url=' . $login_uri . '">';
			echo '<script type="text/javascript">window.location.href = "' . $login_uri . '";</script>';
			// header('location: ' . $login_uri);
		}
		
		return;
	}
	
	/**
	 * Envia para uma página qualquer
	 *
	 * @final
	 */
	final protected function goto_page( $page_uri = null ) {
		if ( isset( $_GET['url'] ) && ! empty( $_GET['url'] ) && ! $page_uri ) {
			// Configura a URL
			$page_uri  = urldecode( $_GET['url'] );
		}
		
		if ( $page_uri ) { 
			// Redireciona
			echo '<meta http-equiv="Refresh" content="0; url=' . $page_uri . '">';
			echo '<script type="text/javascript">window.location.href = "' . $page_uri . '";</script>';
			//header('location: ' . $page_uri);
			return;
		}
	}
	
	/**
	 * Verifica permissões
	 *
	 * @param string $required A permissão requerida
	 * @param array $user_permissions As permissões do usuário
	 * @final
	 */
	final protected function check_permissions( 
		$required = 'any', 
		$user_permissions = array('any')
	) {
		if ( ! is_array( $user_permissions ) ) {
			return;
		}

		// Se o usuário não tiver permissão
		if ( ! in_array( $required, $user_permissions ) ) {
			// Retorna falso
			return false;
		} else {
			return true;
		}
	}
}

O arquivo global-functions.php que está dentro da pasta functions tem a função de __autoload, que carregará todas as nossas classes automaticamente.

Para isso, a classe deverá estar dentro da pasta classes ter o nome class-NomeDaClasse.php (Com letras maiúsculas e minúsculas).

Download

Se você quiser baixar tudo o que já foi feito até agora, segue o link:

Caso queira contribuir com o projeto, acesso no Github:

Perceba que o arquivo que você baixou acima está muito mais adiantado do que este artigo, porém, ainda vamos chegar lá, basta você acompanhar a série MVC em PHP.

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!

Se você desenvolve há algum tempo em PHP, provavelmente já deve ter pensado em qual seria a melhor forma para organizar seus arquivos. Talvez até tenha encontrado uma solução e já está utilizando sua própria estrutura de pastas e arquivos, ou um framework que faça isso por você para beneficiar o seu sistema.

Nesse artigo vou explicar como criar seu próprio framework com padrão MVC em PHP. Claro que não será um sistema extremamente avançado, mas será uma grande introdução para lhe ajudar a desenvolver aplicativos de maneira mais organizada e utilizando “Programação Orientada a Objetos“.

Então vamos lá!

O que é MVC?

MVC significa Model – View – Controller (Modelo – Visão – Controlador)  e é um modelo da arquitetura de software que tem a função de separar front-end (que o usuário vê) do back-end (que é o motor da aplicação).

A estrutura MVC funciona da seguinte maneira:

  • Model (modelo) – O Model é responsável por tratar de tudo que é relacionado com os dados, como criar, ler, atualizar e excluir valores da base de dados (CRUD), tratar das regras de negócios, da lógica e das funções. Apesar de fazer isso tudo, o Model não apresenta nada na tela e não executa nada por si. Normalmente, um View requisita que determinado Model execute uma ação e a mesma é executada dentro do View.
  • View (Visão) – O View é a parte que o usuário vê na tela, como HTML, JavaScript, CSS, Imagens e assim por diante. O View não tem nenhuma ação, mas requisita que o Model execute qualquer ação e mostra os valores retornados para o usuário. É importante ressaltar que um View não depende de nenhum Model, por exemplo, se você vai apenas exibir dados HTML na tela, e não vai precisar de base de dados, talvez um Model não seja necessário.
  • Controller (Controlador) – O Controller é responsável por resolver se um Model e/ou um View é necessário. Caso positivo; ele incluirá os arquivos e funções necessárias para o sistema funcionar adequadamente.

Veja uma imagem representando como o modelo MVC funciona:

Model-view-controller (MVC)

Model-view-controller (MVC)

Na imagem acima, as linhas sólidas exemplificam partes que têm ligações diretas; as linhas tracejadas mostram ligações indiretas. Isso é maleável e pode variar dependendo da sua aplicação e ação que está sendo executada.

Nosso projeto

Nosso projeto será criar um sistema de notícias com área administrativa, portanto, é obrigatório que tenhamos o seguinte:

  • Sistema de login para os administradores;
  • Sistema de registro de usuários (CRUD);
  • Sistema de permissões;
  • Sistema de cadastro de notícias (CRUD);

Faremos tudo no modelo MVC, mas para atingir nosso objetivo teremos que criar várias outras pastas e arquivos.

Basicamente, nossa estrutura ficará como na imagem abaixo:

Fluxograma da nossa aplicação MVC em PHP

Fluxograma da nossa aplicação MVC em PHP

Na imagem acima temos uma apresentação de como a informação vai passar pelo nosso sistema. Veja uma descrição:

  1. O usuário acessa o site;
  2. O arquivo index.php apenas inclui o arquivo config.php;
  3. O arquivo config.php é responsável por registrar nossas configurações e carregar o arquivo loader.php;
  4. O arquivo loader.php carrega o arquivo global-functions.php, que é responsável por manter todas as funções globais. Na verdade, a função mais importante que temos ali é a _autoload, para carregar classes automaticamente. Ele também é responsável por instanciar a classe “TutsupMVC” que vai controlar todo o início da aplicação.
  5. A classe “TutsupMVC” vai verificar se um controlador foi requisitado (pela URL) e incluir o mesmo. Ela também vai verificar se alguma ação do controlador foi requisitada (ainda pela URL). Caso contrário, a ação “index” do controlador será executada. Sendo assim, todo controlador tem que ter pelo menos uma ação, chamada de “index“.
  6. O arquivo controlador (controller) é responsável por ter todas as ações daquela sessão. Cada ação irá diferenciar os Models e/ou Views que forem requisitados. Às vezes uma ação pode utilizar um View e vários Models, ou vice-versa;
  7. O arquivo modelo (model) terá todas os métodos necessários para executar as ações do View. Este arquivo não é obrigatório;
  8. O arquivo de visão (view) irá simplesmente mostrar tudo ao usuário que requisitou a ação.

Estrutura de pastas

Nossas pastas ficarão da seguinte maneira:

│   .htaccess
│   config.php
│   index.php
│   loader.php
│
├───classes
│       class-MainController.php
│       class-MainModel.php
│       class-PasswordHash.php
│       class-TutsupDB.php
│       class-TutsupMVC.php
│       class-UserLogin.php
│
├───controllers
│       home-controller.php
│       login-controller.php
│       noticias-controller.php
│       user-register-controller.php
│
├───functions
│       global-functions.php
│
├───includes
│       404.php
│
├───models
│   ├───noticias
│   │       noticias-adm-model.php
│   │
│   └───user-register
│           user-register-model.php
│
├───views
│   ├───home
│   │       home-view.php
│   │
│   ├───login
│   │       login-view.php
│   │
│   ├───noticias
│   │       noticias-adm-view.php
│   │       noticias-view.php
│   │
│   ├───user-register
│   │       user-register-view.php
│   │
│   ├───_css
│   │       style.css
│   │
│   ├───_images
│   ├───_includes
│   │       footer.php
│   │       header.php
│   │       menu.php
│   │
│   ├───_js
│   │       html5.js
│   │       scripts.js
│   │
│   └───_uploads
└───_utilitarios
        db.sql
        fluxograma.jpg
        mvc.jpg

As pastas que têm um _sublinhado antes do nome, são pastas que incluem arquivos que os views utilizam, mas que não são views. Normalmente são arquivos que não são acessados diretamente, como header.php, footer.php e outros. Eles são incluídos nos arquivos que precisamos.

Ao decorrer desse curso, pode ser que eu adicione ou remova recursos, mas não se preocupe, vou lhe dizer quando algo for alterado.

Modelo da URL e parâmetros

Vamos obter todos os nossos parâmetros por HTTP GET no seguinte formato:

http://www.exemplo.com/index.php?url=controlador/ação/parametro1/parametro2/etc…

Se você perceber, temos apenas um parâmetro na URL acima, o $_GET[‘url’]. Isso porque vamos utilizar o arquivo .htaccess do Apache para reescrever a URL.

A mesma URL acima ficará assim:

http://www.exemplo.com/controlador/ação/parametro1/parametro2/etc…

Nossa classe TutsupMVC vai tratar de separar o controlador, a ação, e enviar o restante dos parâmetros para os métodos dos controladores.

Base de dados do modelo MVC em PHP

Precisaremos de uma base de dados chamada Tutsup:

CREATE DATABASE IF NOT EXISTS `tutsup` CHARACTER SET utf8;

Uma tabela chamada users:

CREATE TABLE IF NOT EXISTS `tutsup`.`users` (
  `user_id` INT(11) NOT NULL AUTO_INCREMENT,
  `user` VARCHAR(255) COLLATE utf8_bin NOT NULL,
  `user_password` VARCHAR(255) COLLATE utf8_bin NOT NULL,
  `user_name` VARCHAR(255) COLLATE utf8_bin DEFAULT NULL,
  `user_session_id` VARCHAR(255) COLLATE utf8_bin DEFAULT NULL,
  `user_permissions` LONGTEXT COLLATE utf8_bin,
  PRIMARY KEY (`user_id`)
) ENGINE=MYISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

Uma tabela chamada noticias:

CREATE TABLE IF NOT EXISTS `tutsup`.`noticias` (
  `noticia_id` INT (11) NOT NULL AUTO_INCREMENT,
  `noticia_data` DATETIME DEFAULT '0000-00-00 00:00:00',
  `noticia_autor` VARCHAR (255),
  `noticia_titulo` VARCHAR (255),
  `noticia_texto` TEXT,
  `noticia_imagem` VARCHAR (255),
  PRIMARY KEY (`noticia_id`)
) ENGINE = MYISAM CHARSET = utf8 ;

Precisaremos inserir um usuário chamado “Admin” na base de dados.

Os dados do usuário inicial serão:

Usuário: Admin
Senha: admin

Nosso sistema faz distinção entre letras maiúsculas e minúsculas.

INSERT INTO `tutsup`.`users` (
  `user_id`,
  `user`,
  `user_password`,
  `user_name`,
  `user_session_id`,
  `user_permissions`
) 
VALUES
  (
    NULL,
    'Admin',
    '$2a$08$2sGQinTFe3GF/YqAYQ66auL9o6HeFCQryHdqUDvuEVN0J1vdhimii',
    'Admin',
    'ljfp99gvqm2hg2bj6jjpu4ol64',
	'a:2:{i:0;s:13:"user-register";i:1;s:18:"gerenciar-noticias";}'
  ) ;

Se você quiser o script completo, basta criar um arquivo com a extensão “.sql“, com o seguinte texto:

CREATE DATABASE IF NOT EXISTS `tutsup` CHARACTER SET utf8;

CREATE TABLE IF NOT EXISTS `tutsup`.`noticias` (
  `noticia_id` INT (11) NOT NULL AUTO_INCREMENT,
  `noticia_data` DATETIME DEFAULT '0000-00-00 00:00:00',
  `noticia_autor` VARCHAR (255),
  `noticia_titulo` VARCHAR (255),
  `noticia_texto` TEXT,
  `noticia_imagem` VARCHAR (255),
  PRIMARY KEY (`noticia_id`)
) ENGINE = MYISAM CHARSET = utf8 ;

CREATE TABLE IF NOT EXISTS `tutsup`.`users` (
  `user_id` INT(11) NOT NULL AUTO_INCREMENT,
  `user` VARCHAR(255) COLLATE utf8_bin NOT NULL,
  `user_password` VARCHAR(255) COLLATE utf8_bin NOT NULL,
  `user_name` VARCHAR(255) COLLATE utf8_bin DEFAULT NULL,
  `user_session_id` VARCHAR(255) COLLATE utf8_bin DEFAULT NULL,
  `user_permissions` LONGTEXT COLLATE utf8_bin,
  PRIMARY KEY (`user_id`)
) ENGINE=MYISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

INSERT INTO `tutsup`.`users` (
  `user_id`,
  `user`,
  `user_password`,
  `user_name`,
  `user_session_id`,
  `user_permissions`
) 
VALUES
  (
    NULL,
    'Admin',
    '$2a$08$2sGQinTFe3GF/YqAYQ66auL9o6HeFCQryHdqUDvuEVN0J1vdhimii',
    'Admin',
    'ljfp99gvqm2hg2bj6jjpu4ol64',
	'a:2:{i:0;s:13:"user-register";i:1;s:18:"gerenciar-noticias";}'
  ) ;

Utilize qualquer programa de gerenciamento de base de dados MySQL para importar este script.

Se quiser utilizar o phpMyAdmin, já criar um tutorial pra você:

Veja uma imagem da minha base de dados já pronta:

Tabela para nossa aplicação

Tabela para nossa aplicação

É bem simples.

Criando o arquivo .htaccess

Crie uma pasta com o nome do nosso aplicativo (no meu caso “crud“), em seguida abra seu editor de textos preferido e crie um arquivo chamado .htaccess:

Nele adicione o seguinte:

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l

RewriteRule ^(.+)$ index.php?path=$1 [QSA,L]

Este arquivo deverá ficar na pasta principal da nossa aplicação. Veja um exemplo do meu sistema (já pronto):

.htaccess

.htaccess

Este arquivo vai permitir que nossas URLs sejam escritas dessa maneira:

http://www.exemplo.com/index.php?url=controlador/ação/parametro1/parametro2/etc…

Para:

http://www.exemplo.com/controlador/ação/parametro1/parametro2/etc…

Se você não quiser utilizar o recurso do Apache, basta seguir o primeiro exemplo da URL.

Veja uma imagem do arquivo .htaccess:

.htaccess

.htaccess

index.php

Nosso arquivo principal, o index.php, terá apenas o seguinte:

<?php
// Config
require_once 'config.php';
?>

Mais nada…

config.php

Nosso arquivo config.php terá as configurações que você pode alterar para cada um de seus projetos, como configurações de URL, base de dados, debug, e assim por diante, veja:

<?php
/**
 * Configuração geral
 */

// Caminho para a raiz
define( 'ABSPATH', dirname( __FILE__ ) );

// Caminho para a pasta de uploads
define( 'UP_ABSPATH', ABSPATH . '/views/_uploads' );

// URL da home
define( 'HOME_URI', 'http://127.0.0.1/Cursos/crud' );

// Nome do host da base de dados
define( 'HOSTNAME', 'localhost' );

// Nome do DB
define( 'DB_NAME', 'tutsup' );

// Usuário do DB
define( 'DB_USER', 'root' );

// Senha do DB
define( 'DB_PASSWORD', '' );

// Charset da conexão PDO
define( 'DB_CHARSET', 'utf8' );

// Se você estiver desenvolvendo, modifique o valor para true
define( 'DEBUG', true );

/**
 * Não edite daqui em diante
 */

// Carrega o loader, que vai carregar a aplicação inteira
require_once ABSPATH . '/loader.php';
?>

Perceba que este arquivo também carrega o arquivo loader.php (veremos seu conteúdo abaixo).

loader.php

O arquivo loader.php inicia a sessão, configura os erros (dependendo da constante DEBUG) e inclui um arquivo com funções globais.

Veja:

<?php
// Evita que usuários acesse este arquivo diretamente
if ( ! defined('ABSPATH')) exit;
 
// Inicia a sessão
session_start();

// Verifica o modo para debugar
if ( ! defined('DEBUG') || DEBUG === false ) {

	// Esconde todos os erros
	error_reporting(0);
	ini_set("display_errors", 0); 
	
} else {

	// Mostra todos os erros
	error_reporting(E_ALL);
	ini_set("display_errors", 1); 
	
}

// Funções globais
require_once ABSPATH . '/functions/global-functions.php';

// Carrega a aplicação
$tutsup_mvc = new TutsupMVC();

Veja que este arquivo também inicia a classe “TutsupMVC“, ela vai procurar o controlador e a ação. Você vai ver seu conteúdo na próxima aula.

O loader.php carrega o arquivo /functions/global-functions.php, vamos ver seu conteúdo.

functions/global-functions.php

Este arquivo carrega duas funções muito importantes para nossa aplicação, veja:

<?php
/**
 * Verifica chaves de arrays
 *
 * Verifica se a chave existe no array e se ela tem algum valor.
 * Obs.: Essa função está no escopo global, pois, vamos precisar muito da mesma.
 *
 * @param array  $array O array
 * @param string $key   A chave do array
 * @return string|null  O valor da chave do array ou nulo
 */
function chk_array ( $array, $key ) {
	// Verifica se a chave existe no array
	if ( isset( $array[ $key ] ) && ! empty( $array[ $key ] ) ) {
		// Retorna o valor da chave
		return $array[ $key ];
	}
	
	// Retorna nulo por padrão
	return null;
} // chk_array

/**
 * Função para carregar automaticamente todas as classes padrão
 * Ver: http://php.net/manual/pt_BR/function.autoload.php.
 * Nossas classes estão na pasta classes/.
 * O nome do arquivo deverá ser class-NomeDaClasse.php.
 * Por exemplo: para a classe TutsupMVC, o arquivo vai chamar class-TutsupMVC.php
 */
function __autoload($class_name) {
	$file = ABSPATH . '/classes/class-' . $class_name . '.php';
	
	if ( ! file_exists( $file ) ) {
		require_once ABSPATH . '/includes/404.php';
		return;
	}
	
	// Inclui o arquivo da classe
    require_once $file;
} // __autoload

As ações das funções estão descritas no código acima.

Download

Se você quiser baixar tudo o que já foi feito até agora, segue o link:

Caso queira contribuir com o projeto, acesso no Github:

Perceba que o arquivo que você baixou acima está muito mais adiantado do que este artigo, porém, ainda vamos chegar lá, basta você acompanhar o Tutsup.

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!

Trabalhar com funções dinamicamente em PHP é extremamente simples, basta utilizar uma das duas funções: call_user_func  ou call_user_func_array .

Ambas fazem a mesma coisa, recebem o nome de uma função como uma string e quantos parâmetros forem necessários, em seguida executam a função.

call_user_func  pode receber apenas strings como parâmetros, sendo o primeiro deles o nome da função, e o restante os argumentos. call_user_func_array  recebe o primeiro parâmetro como uma string e o restante como um array.

Você pode executar tanto funções nativas do PHP quanto funções que você mesmo criou com ambas, call_user_func ou call_user_func_array .

Vamos ver na prática como executar funções de maneira dinâmica em PHP.

call_user_func

Vamos fazer uma utilização básica de call_user_func  com uma função nativa do PHP, veja:

<?php
// Uma frase que precisa ser corrigida
$valor = '       Uma     frase    cheia   de espaços    ';

// Converte todos os espaços em apenas um
$valor = call_user_func('preg_replace', '/s+/', ' ', $valor);

// Retorna: Uma frase cheia de espaços 
echo $valor;
?>

Perceba que o primeiro parâmetro é o nome da função que estou tentando executar (neste caso preg_replace), o restante são todos parâmetros para a função que estou executando (ainda preg_replace).

Você pode estar pensando: “Mas por que não chamar a função preg_replace diretamente?“. Eu também pensei a mesma coisa quando vi pela primeira vez as funções call_user_func  e call_user_func_array , porém, em algum momento você vai precisar chamar uma função dinamicamente no seu script, aí elas farão todo o sentido.

Por exemplo, imagine que eu tenho uma função pronta:

<?php
function exibe ( $valor ) {
	echo $valor;
}
?>

Agora vamos supor que eu queira uma função de callback para tratar o valor enviado antes de retornar qualquer coisa, porém, outro desenvolvedor irá criar a nova função, você só precisa deixar uma opção para ele enviar a nova função como parâmetro para a sua função.

Na verdade, isso é muito simples, veja:

<?php
// Sua função agora não é tão simples assim
function exibe ( $valor, $callback = false ) {

	// O parâmetro $callback não é obrigatório, mas se
	// for enviado, chama a função e envia o $valor como
	// parâmetro.
	if ( $callback ) {
	
		// O valor retornado pela função de callback
		// altera o valor de $valor
		$valor = call_user_func( $callback, $valor );
		
	}
	
	// Exibe o novo valor
	echo $valor;
}

// Nossa função de callback remove tudo
// o que não é letra ou número
function callback ( $valor ) {

	return preg_replace('/[^0-9a-zA-Z]/', '', $valor);

}

// Retorna: Oie
exibe( 'Oie!!!', 'callback' );
?>

Se você está acostumado a desenvolver para WordPress, provavelmente já deve estar acostumado a utilizar funções de callback. É exatamente dessa maneira que isso é realizado.

call_user_func_array

É a mesma coisa da função que acabei de descrever, porém, o segundo parâmetro deve ser um array contendo os parâmetros que você está enviando para a função a ser executada. Veja:

<?php
// Valor que precisa de trim
$valor = '       Legal        ';

// É igual a call_user_func, mas só aceita dois parâmetros,
// sendo o segundo sempre um array com todos os argumentos que deseja,
// mesmo que apenas um (como abaixo).
$valor = call_user_func_array( 'trim', array( $valor ) );

// Retorna: "Legal"
echo '"' . $valor . '"';
?>

É exatamente a mesma coisa!

call_user_func e call_user_func_array em classes

Para chamar qualquer uma das funções acima dentro de uma classe, é necessário utilizar o seguinte método:

array( $nome_da_classe, 'nome_da_funcao');

Veja um exemplo:

<?php
class Tutsup
{
	function __construct() {
		// Basta adicionar array( $nome_da_classe, 'função' )
		call_user_func( array( $this, 'exibe'), 'Estou dentro de uma classe!' );
	}
	
	public function exibe( $valor ) {
		echo $valor;
	}
}

// Exibe: Estou dentro de uma classe!
$tutsup = new Tutsup();
?>

Veja o mesmo exemplo com call_user_func_array:

<?php
class Tutsup
{
	function __construct() {
		// Basta adicionar array( $nome_da_classe, 'função' )
		call_user_func_array( array( $this, 'exibe'), array( 'Estou dentro de uma classe!' ) );
	}
	
	public function exibe( $valor ) {
		echo $valor;
	}
}

// Exibe: Estou dentro de uma classe!
$tutsup = new Tutsup();
?>

Concluindo

Conforme descrevi no artigo, você deve utilizar este método com sabedoria, não faz sentido chamar uma função diretamente utilizando call_user_func e call_user_func_array, apenas dinamicamente.

Não deixe de conferir o manual do PHP para maiores informações:

Caso tenha qualquer dúvida, basta perguntar nos comentários.

Namespaces em PHP são utilizados para encapsular seu código e evitar colisões de nomes de funções, classes, interfaces e constantes.

Neste artigo você vai aprender a criar e utilizar namespaces de maneira adequada.

Então vamos lá.

O que são colisões de nomes?

Se você está iniciando com PHP, provavelmente o termo colisões de nomes deve soar estranho para você. Mas é algo bastante simples, vai por mim.

Quando criamos uma função, classe, constante ou interface, não podemos utilizar o mesmo nome no decorrer de nosso código. Veja um exemplo:

<?php
// Uma função sem ações
function teste () {}

// Mesmo nome: Fatal error: Cannot redeclare teste()
function teste () {}
?>

No exemplo acima, a segunda função com o mesmo nome da primeira, irá gerar um erro fatal.

E o mesmo ocorre para constantes:

<?php
// Uma uma constante
define('constante', true);

// Mesmo nome: Notice: Constant constante already defined
define('constante', true);
?>

Para classes:

<?php
// Uma classe
class teste{}

// Mesmo nome: Fatal error: Cannot redeclare class teste
class teste{}
?>

E para interfaces:

<?php
// Uma interface
interface teste{}

// Mesmo nome: Fatal error: Cannot redeclare class teste
interface teste{}
?>

Quando isso acontece, falamos que nosso código tem uma "colisão de nomes", que simplesmente indica que duas porções importantes do código tem o mesmo nome.

Como funcionam os namespaces em PHP?

Namespaces funcionam de maneira idêntica às pastas do seu computador, ou seja, dentro da pasta fotos, posso ter um arquivo chamado de minha_foto.jpg  e dentro da pasta documentos também posso ter um arquivo chamado minha_foto.jpg.

Mesmo que o arquivo minha_foto.jpg tenha o mesmo nome nas duas pastas, eles podem ser diferentes e seu conteúdo também pode ser diferente.

Em PHP, você pode ter vários namespaces (que seriam como as pastas) diferentes, e dentro desses namespaces, minhas funções, classes, interfaces e constantes. Se os namespaces forem diferentes, os nomes dos elementos dentro deles podem ser iguais a quaisquer outros nomes que existam em outros namespaces.

Porém, a mesma regra da colisão de nomes se aplica dentro de um namespace, ou seja, você não pode ter dois elementos de mesmo nome dentro de um mesmo namespace.

Como criar um namespace em PHP?

Para criar um namespace em PHP, simplesmente utilize a palavra chave {php}namespace{/php}, veja:

<?php
// Define o namespace
namespace MeuEspaco;

// Uma constante dentro do namespace MeuEspaco
define('MinhaConstante', 'Valor da constante');
?>

No trecho de código acima, criei um namespace chamado MeuEspaco e defini uma constante dentro desse namespace.

Repare que o trecho você deve definir o namespace na primeira linha do seu código, antes de qualquer coisa.

Como definir sub-namespaces em PHP?

Assim como as pastas (detalhado anteriormente) também é possível definir sub-namespaces (com pastas dentro de pastas), assim, você poderá organizar ainda mais seu código.

Para definir um sub-namespace, simplesmente utilize uma barra invertida seguida do nome do sub-namespace.

Veja:

<?php
// Define o sub-namespace
namespace MeuEspacoSubEspaco;

// Uma constante dentro do sub-namespace MeuEspacoSubEspaco
define('MinhaConstante', 'Valor da constante');
?>

Isso seria a mesma coisa que uma subpasta, ou seja, agora minha constante só estará acessível dentro do meu sub-namespace.

Criando vários namespaces em um mesmo arquivo

Namespaces são válidos para um arquivo apenas, porém, você pode definir vários namespaces dentro de um mesmo arquivo. Apesar de não ser recomendado fazer isso, segue um exemplo:

<?php
// Define o namespace
namespace MeuEspaco;

// Uma classe dentro do namespace MeuEspaco
class Teste 
{

}

// Define outro namespace
namespace OutroEspaco;

// Uma classe dentro do namespace OutroEspaco
class Teste 
{

}
?>

Você também pode utilizar chaves para definir seus namespaces, veja:

<?php
// Define o namespace
namespace MeuEspaco {

	// Uma classe dentro do namespace MeuEspaco
	class Teste 
	{

	}
	
}

// Define outro namespace
namespace OutroEspaco {

	// Uma classe dentro do namespace OutroEspaco
	class Teste 
	{

	}

}

// Código global
namespace {

	// Uma classe global
	class Teste 
	{

	}

}
?>

Assim você ainda pode misturar o código global, com namespaces diferentes. Perceba que mesmo que eu tenha uma classe declarada três vezes, no trecho acima, são três classes diferentes.

Vamos adicionar ações e executar essas ações para você entender melhor.

<?php
// Define o namespace
namespace MeuEspaco {

	// Uma classe dentro do namespace MeuEspaco
	class Teste 
	{
		public function __construct () {
			echo 'Classe está em ' . __NAMESPACE__ . '<br>';
		}
	}
	
}

// Define outro namespace
namespace OutroEspaco {

	// Uma classe dentro do namespace OutroEspaco
	class Teste 
	{
		public function __construct () {
			echo 'Classe está em ' . __NAMESPACE__ . '<br>';
		}
	}

}

// Código global
namespace {

	// Uma classe global
	class Teste 
	{
		public function __construct () {
			echo 'Classe está em ' . __NAMESPACE__ . '<br>';
		}
	}

	
	// Instanciando as classes
	$classe1 = new MeuEspacoTeste(); // Classe está em MeuEspaco
	$classe2 = new OutroEspacoTeste(); // Classe está em OutroEspaco
	$classe3 = new Teste();	 // Classe está em 
}
?>

Perceba também, que quando estou trabalhando com namespaces, não posso colocar código fora de um namespace qualquer. No trecho acima, defini os objetos das classes dentro do namespace global.

Observação: Em termos de programação, nunca crie namespaces diferentes, várias classes e nunca crie objetos de classes no mesmo arquivo. Um namespace deve valer para um único arquivo apenas, uma classe deve ser definida em seu próprio arquivo e instanciada fora desse arquivo.

A palavra mágica __NAMESPACE__

A palavra mágica __NAMESPACE__ carrega o nome do namespace que você está trabalhando, veja um exemplo:

<?php
// Define o namespace
namespace MeuEspacoSubEspacoQualqueCoisa;

echo __NAMESPACE__; // MeuEspacoSubEspacoQualqueCoisa
?>

Como utilizar namespaces em PHP

Certo, você aprendeu a criar namespaces e sub-namespaces, mas como vou utilizar isso em meu código?

Simples, para meu exemplo, vou criar dois arquivos diferentes, index.php e outroarquivo.php.

Veja o conteúdo do index.php:

<?php
// Define o namespace
namespace Index;

// Cria uma classe
class Classe
{
	// Construtor
	public function __construct () {
		echo 'Estou no ' . __NAMESPACE__ . '<br>';
	}
}

// Inclui o outro arquivo
include('outroarquivo.php');
?>

Perceba que estou incluindo o outroarquivo.php no index.php. Teoricamente, se eu tentasse instanciar a classe "Classe" no outro arquivo, daria certo, porém, como defini o namespace no index.php, terei um erro fatal:

<?php 
// Tento criar a instância: Fatal error: Class 'Classe' not found
$classeIndex = new Classe();
?>

Existem algumas maneiras diferentes para resolver este problema, uma seria definir o namespace Index no outro arquivo; outra seria chamar a classe pelo seu caminho completo, ou seja, namespacenomedaclasse.

Veja ambas as maneiras:

namespacenomedaclasse

<?php 
// Resultado: Estou no Index
$classeIndex = new IndexClasse();
?>

Definindo o namespace no outro arquivo

<?php 
// Define o namespace
namespace Index;

// Resultado: Estou no Index
$classeIndex = new Classe();
?>

Importando namespaces em PHP

Em PHP, você pode "importar" um namespace para outro nome (criar um alias).

Vamos continuar com nossos dois arquivos descritos no subtítulo anterior, não vou mudar o código do index.php, apenas do outroarquivo.php.

<?php
// Criando um aliás (atalho) para o namespace
use Index as a;

// Utilizando o atalho
$classe = new aClasse();
?>

No trecho acima a agora representa meu namespace Index.

Também posso importar quase tudo de outro namespace, menos funções. Veja:

<?php
// Criando um aliás (atalho) para a classe "Classe" dentro do 
// namespace Index
use IndexClasse as a;

// Utilizando novo nome da classe
$classe = new a();

// Resultado: Estou no Index
?>

Só para reforçar, isso só não funciona com funções.

Utilizando o espaço global dentro de um namespace

Dentro de qualquer namespace, é possível sobrescrever as funções nativas do PHP, porém, você ainda terá acesso a elas se utilizar uma barra invertida quando for utilizá-la, veja:

<?php
// Define o namespace
namespace Index;

// Criando uma função chamada trim dentro do namespace
function trim($valor) {
	// Remove espaços do início e do fim da string
	return preg_replace('/^s+|s+$/', '', $valor);
}

// Utilizando a função dentro do namespace
echo "'" . trim('  Teste de string  ') . "'"; // 'Teste de string'

// Utilizando a função nativa do PHP no espaço global
echo "'" . trim('  Teste de string  ') . "'"; // 'Teste de string'
?>

No exemplo acima, criei uma função chama "trim" que sobrescreve a função "trim" nativa do PHP, porém, ainda consegui utilizá-la em meu código, apenas adicionando uma barra invertida.

Isso pode ser feito com qualquer função.

Definindo constantes em namespaces

Se você quiser uma constante em um determinado namespace, simplesmente utilize a palavra chave {php}const{/php}. Veja:

<?php
// Define o namespace
namespace Index;

// Definindo a constante
const Constante = 'Valor';

// Mostra o valor da constante
echo Constante;

// Inclui o outro arquivo
include('outroarquivo.php');
?>

Para acessar o valor da constante fora de um namespace, simplesmente utilize seu nome completo, ou seja, namespacenomedaconstante.

<?php
// Acessando a constante do namespace Index
echo IndexConstante;
?>

Aprenda mais sobre namespaces em PHP

Se você ainda tem dúvidas, não hesite em questionar nos comentários, e sempre leia o manual do PHP, isso fará você ficar fera na linguagem e na programação, existem altas dicas na contribuição dos usuários.

Veja os links que vão te ajudar:

E não deixe de seguir todas as aulas do nosso curso gratuito de PHP e de PHP Orientado a Objetos.

Caso tenha dúvidas, pergunte aí abaixo, faremos questão de ajudar.

Manipular erros em PHP é extremamente importante, já que eles podem ajudar o desenvolvedor a resolver problemas que ele provavelmente não tenha visto no momento da escrita do código. Encontrar um erro é extremamente simples quando estamos falando em PHP, o interpretador da linguagem especifica qual foi o erro, em qual arquivo e qual a linha o mesmo ocorreu, ou seja, você não precisa vasculhar o código inteiro para saber onde um erro ocorreu.

Veja algumas categorias de erros comuns em programação:

  • Erros de Sintaxe: Normalmente ocorrem quando o desenvolvedor escreve algo de maneira incorreta. Em grande maioria dos casos, é um erro de digitação criado involuntariamente.
  • Erros de Semântica: Erros de semântica ocorrem quando o desenvolvedor cria um código que pode ser executado corretamente, porém, por algum motivo não pensado por ele, acontece o erro e está envolvido com seu código.
  • Erros de Lógica: Erros de lógica ocorrem quando o desenvolvedor escreve algo correto, porém, a lógica não é válida. Por exemplo, trocar um operador por outro e esperar que o interpretador compreenda.
  • Erros de ambiente: Erros de ambiente são relacionados à linguagem ou ao servidor em si. Normalmente fogem ao escopo do que o desenvolvedor pode fazer, sobrando para o Administrador do servidor.

Especificamente em PHP, existem vários níveis de erros que podem ser reportados. Vamos analisar melhor abaixo.

Níveis de erros em PHP

Cada erro tem seu nível e severidade, alguns podem apenas apresentar uma mensagem, outros podem fazer com que o código pare de ser executado.

Veja na tabela abaixo os tipos de erros que podemos encontrar em PHP:

Erros e Logs
Valor Constante Descrição
1 E_ERROR (integer) Erros fatais em tempo de execução. Estes indicam erros que não podem ser recuperados, como problemas de alocação de memória. A execução do script é interrompida.
2 E_WARNING (integer) Avisos em tempo de execução (erros não fatais). A execução do script não é interrompida.
4 E_PARSE (integer) Erro em tempo de compilação. Erros gerados pelo interpretador.
8 E_NOTICE (integer) Notícia em tempo de execução. Indica que o script encontrou alguma coisa que pode indicar um erro, mas que também possa acontecer durante a execução normal do script.
16 E_CORE_ERROR (integer) Erro fatal que acontece durante a inicialização do PHP. Este é parecido com E_ERROR, exceto que é gerado pelo núcleo do PHP.
32 E_CORE_WARNING (integer) Avisos (erros não fatais) que aconteçam durante a inicialização do PHP. Este é parecido com E_WARNING, exceto que é gerado pelo núcleo do PHP.
64 E_COMPILE_ERROR (integer) Erro fatal em tempo de compilação. Este é parecido com E_ERROR, exceto que é gerado pelo Zend Scripting Engine.
128 E_COMPILE_WARNING (integer) Aviso em tempo de compilação. Este é parecido com E_WARNING, exceto que é gerado pelo Zend Scripting Engine.
256 E_USER_ERROR (integer) Erro gerado pelo usuário. Este é parecido com E_ERROR, exceto que é gerado pelo código PHP usando a função trigger_error().
512 E_USER_WARNING (integer) Aviso gerado pelo usuário. Este é parecido com E_WARNING, exceto que é gerado pelo código PHP usando a função trigger_error().
1024 E_USER_NOTICE (integer) Notícia gerada pelo usuário. Este é parecido com E_NOTICE, exceto que é gerado pelo código PHP usando a função trigger_error().
2048 E_STRICT (integer) Notícias em tempo de execução. Permite ao PHP sugerir mudanças ao seu código as quais irão assegurar melhor interoperabilidade e compatibilidade futura do seu código.
4096 E_RECOVERABLE_ERROR (integer) Erro fatal capturável. Indica que um erro provavelmente perigoso aconteceu, mas não deixou o Engine em um estado instável. Se o erro não for pego por um manipulador definido pelo usuário (veja também set_error_handler()), a aplicação é abortada como se fosse um E_ERROR.
8192 E_DEPRECATED (integer) Avisos em tempo de execução. Habilite-o para receber avisos sobre código que não funcionará em futuras versões.
16384 E_USER_DEPRECATED (integer) Mensagem de aviso gerado pelo usuário. Este é como um E_DEPRECATED, exceto que é gerado em código PHP usando a função trigger_error().
30719 E_ALL (integer) Todos erros e avisos, como suportado, exceto de nível E_STRICT no PHP < 6.

A tabela acima está disponível no próprio manual do PHP.

Os tipos de erros da tabela acima devem estar ativos para que você possa vê-los. Para ativar, você pode alterar seu arquivo php.ini:

error_reporting = E_ALL

Você também pode utilizar opções diretamente no seu código. Por exemplo:

<?php
// Mostra todos os erros
error_reporting(E_ALL);
ini_set("display_errors", 1); 
?>

O trecho acima mostra todos os erros que seu código tiver. Se quiser ocultar todos os erros, o que não é recomendado que faça em seu ambiente de desenvolvimento, mude para:

<?php
// Oculta todos os erros
 error_reporting(0);
ini_set("display_errors", 0); 
?>

Você também pode criar um arquivo de log e salvar este arquivo no local que preferir. Para isso, é possível fazer o seguinte (também no código):

ini_set("error_log", "nome_do_arquivo.log");

E você vai ver todos os erros do seu código salvos em um arquivo de texto que poderá ser acessado posteriormente para análise.

Criando seus próprios erros em PHP

Se você está seguindo nosso curso de PHP gratuito desde o início, provavelmente já deve ter visto uma mensagem de erro do PHP em seu navegador. Isso acontece a todo o momento, com todos os desenvolvedores.

Porém, e se eu quiser criar meu próprio erro para exibir na tela se algo de errado ocorrer? Simples, utilize trigger_error, veja:

<?php
// Número inicial
$numero = 100;

// Verifica se 101 é maior que 100
if ( 101 > $numero ) {
	trigger_error ( "O número não pode ser maior que $numero", E_USER_ERROR );	
}
?>

O trecho de código acima deverá gerar o seguinte erro:

Fatal error: O número não pode ser maior que 100 in cursosphpaula_42index.php on line 7

Além da função acima, você também pode utilizar set_error_handler, porém, neste caso o PHP passa todo o gerenciamento de erros para suas mãos. Não creio que seja algo totalmente seguro (a não ser que estritamente necessário), pois, você vai ter que gerenciar todos os erros, e, caso necessário, matar (die ou exit) o script.

Utilizando exceções

Uma das maneiras que mais gosto para manipular erros são as exceções. Elas fazem parte da classe {php}Excpetion{/php}, onde você pode lançar erros com {php}throw{/php} e utilizar {php}try{/php} e {php}catch{/php}.

Funciona assim: Quando você quiser lançar um erro, simplesmente utilize o trecho de código abaixo:

throw new Exception("Mensagem de erro");

Para testar, você deve utilizar um bloco de {php}try{/php} e {php}catch{/php}. Dentro do bloco {php}try{/php}, você deve colocar o código que será executado normalmente (sem erros). {php}catch{/php} só será executado se o interpretador do PHP encontrar uma exceção. Veja:

<?php
// Uma função simples
function verifica( $max, $numero ) {
	// Se o máximo for menor que o número
	if ( $max < $numero ) {
		// Lança a exceção
		throw new Exception("Erro: número não pode ser maior que $max");
	} else {
		// Caso contrário mostra o número
		echo $numero;
	}
}

// Bloco try/catch
try {
	// Executa a função com o número maior que o máximo
	verifica( 100, 101 );
} catch (Exception $e) {
	// Se encontrar alguma exceção na função, para e mostra a mensagem
	echo $e->getMessage();
}
?>

É interessante lançar exceções em programação orientada a objetos, veja como fazer o mesmo em classes:

<?php 
class Numero
{
	// Propriedades
	public $numero;
	
	// Função para verificar número
	public function verifica ( $max, $numero ) {
		// Número atual
		$this->numero = $numero;
		
		// Verifica se o número é maior que o máximo
		if ( $numero > $max ) {
			throw new Exception("Erro: Número não pode ser maior que $max.");
		} else {
			echo $numero;
		}
	}
}

// Instância (objeto) de Numero
$numero = new Numero();

// Bloco try/catch
try {
	$numero->verifica( 100, 101 );
} catch (Exception $e) {
	echo $e->getMessage();
}
?>

Porém, lembre-se que todas as exceções lançadas com {php}throw{/php} devem ser capturadas com {php}catch{/php}, caso contrário, o PHP gera um erro fatal e para a execução do seu script.

Com as exceções, você tem os seguinte métodos prontos para serem utilizados:

  • getMessage () – A mensagem de erro
  • getCode () – O código de erro
  • getFile () – O arquivo de erro
  • getLine () – A linha do erro
  • getTrace () – Informações de contexto do erro
  • getTraceAsString () – Mesmo informação anterior, só que com string

Por exemplo:

<?php 
class Numero
{
	// Propriedades
	public $numero;
	
	// Função para verificar número
	public function verifica ( $max, $numero ) {
		// Número atual
		$this->numero = $numero;
		
		// Verifica se o número é maior que o máximo
		if ( $numero > $max ) {
			throw new Exception("Erro: Número não pode ser maior que $max.");
		} else {
			echo $numero;
		}
	}
}

// Instância (objeto) de Numero
$numero = new Numero();

// Bloco try/catch
try {
	$numero->verifica( 100, 101 );
} catch (Exception $e) {
	echo '<b>Erro: </b>';
	echo $e->getMessage();
	echo ' <b>Na linha: </b>';
	echo $e->getLine();
	echo ' <b>Arquivo: </b>';
	echo $e->getFile();
}
?>

Isso deverá gerar:

Erro: Número não pode ser maior que 100. Na linha: 15 Arquivo: E:ProgramasEasyPHPdatalocalwebcursosphpaula_42index.php

Muito interessante!

Concluindo

Claro que existem milhares de maneiras de manipular erros em PHP e cabe a você, ou sua equipe, escolher qual a melhor para seu projeto. Os exemplos do artigo acima dão apenas dicas sobre como é possível manipular erros em PHP, porém, você deve fazer modificações e adaptações para o seu código.

Não deixe de seguir todas as aulas do nosso curso gratuito de PHP e de PHP Orientado a Objetos.

Caso tenha dúvidas, não hesite em comentar.

É possível criar objetos dinâmicos em PHP, quero dizer, criar objetos sem criar uma classe antes. Para isso, podemos utilizar a stdClass (Standard Class).

Você pode até criar um objeto direto pelas propriedades, por exemplo:

$objeto->propriedade = 'valor';

Porém, você vai ver um erro de aviso quando fizer isso.

Se precisar criar objetos diretamente, o mais correto é criar uma instância da stdClass. Veja:

<?php
// Primeiro objeto
$luiz            = new stdClass();
$luiz->nome      = 'Luiz Otávio';
$luiz->sobrenome = 'Miranda';
$luiz->idade     = 27;
$luiz->profissao = 'Desenvolvedor';

// Segundo objeto
$jose            = new stdClass();
$jose->nome      = 'José';
$jose->sobrenome = 'silva';
$jose->idade     = 33;
$jose->profissao = 'Analista de Sistemas';

// Função para percorrer o objeto e mostrar os dados
function mostra_dados ( $objeto ) {
	// Laço
	foreach ( $objeto as $propriedade => $valor ) {
		// Mostra
		echo ucfirst( $propriedade ) . ' = ' . $valor . '<br>';
	}
}

// Mostra primeiro objeto
mostra_dados( $luiz );
echo '<br>';

// Mostra segundo objeto
mostra_dados( $jose );
?>

Você não terá métodos nos objetos criados dinamicamente, apenas propriedades.

Propriedades dinâmicas em classes PHP

Também é possível criar propriedades dinamicamente em qualquer classe PHP, para isso, simplesmente utilize a seguinte forma:

$objeto->propriedade = 'valor';

Veja um exemplo completo:

<?php
// Cria uma classe comum sem propriedades
class Dinamica
{
	// Um método que exibe as propriedades dinâmicas
	public function obter_propriedades () {
		// get_object_vars obtém todas as propriedades da classe
		$propriedades = get_object_vars( $this );
		
		foreach ( $propriedades as $propriedade => $valor ) {
			echo "{$propriedade} = {$valor}<br>";
		}
	}
}

// Instância (objeto)
$dinamica = new Dinamica();

// Cria propriedades
$dinamica->propriedade1 = 'Propriedade 1';
$dinamica->propriedade2 = 'Propriedade 2';
$dinamica->propriedade3 = 'Propriedade 3';

// Exibe as propriedades criadas
$dinamica->obter_propriedades();
?>

Como resultado teremos:

propriedade1 = Propriedade 1
propriedade2 = Propriedade 2
propriedade3 = Propriedade 3

Perceba que utilizei a função get_object_vars para obter as propriedades criadas dinamicamente.

Concluindo

Objetos e propriedades dinâmicas em PHP podem ser bastante úteis dependendo da ação que seu código vai realizar. Utilize sabiamente.

Não deixe de seguir todas as aulas do nosso curso gratuito de PHP e de PHP Orientado a Objetos.

Caso tenha dúvidas, não hesite em comentar.

Em nossa primeira aula sobre OOP em PHP, você aprendeu que devemos criar classes em arquivos separados, onde o arquivo que mantém a classe, deve ter o nome class-nomedaclasse.php. Com isso, podemos utilizar uma função especial do PHP chamada {php}__autoload{/php} para carregar nossos arquivos de classes automaticamente.

Ao invés de utilizar as funções include, include_once, require ou require_once para incluir os arquivos de classe todas as vezes que precisarmos de um novo objeto da mesma, você pode fazer o seguinte.

Suponhamos que eu tenha uma classe simples:

<?php
class Pessoa
{
	// Propriedades da classe
	public $nome;
	public $sobrenome;
	public $idade;
	
	// Construtor - Define os valores das proprieades
	public function __construct ( $nome = null, $sobrenome = null, $idade = 0 ) {
		$this->nome      = $nome;
		$this->sobrenome = $sobrenome;
		$this->idade     = (int) $idade;
	}
	
	// Método para exibir
	public function exibir () {
		echo 'Nome: ';
		echo $this->nome . '<br>';
		echo 'Sobrenome: ';
		echo $this->sobrenome . '<br>';
		echo 'Idade: ';
		echo $this->idade . '<br><br>';
	}
}

Se eu quiser utilizá-la, devo fazer o seguinte:

<?php 
include('classes/class-pessoa.php');

// Instância da classe Pessoa
$pessoa = new Pessoa('Luiz', 'Miranda Figueiredo', 27);
$pessoa->exibir();
?>

Perceba que a função {php}include{/php} está incluindo o arquivo da classe que pretendo utilizar em meu código.

Porém, suponhamos que eu tenha milhares de classes separadas em arquivos distintos (que é o recomendado). Todas as vezes que eu precisasse de determinada classe, teria que incluir o arquivo da mesma para poder utilizá-la.

A função {php}__autoload{/php} faz isso automaticamente por mim. Ela detecta o nome da classe que estou tentando criar, e tenta incluir tal classe em meu código. Veja:

<?php 
// função para carregar classes automaticamente
function __autoload( $class ){
	// Garante apenas letras minúsculas no nome do arquivo de classe
	$class = strtolower( $class );
	
	// Inclui a classe que precisamos
	include_once("classes/class-{$class}.php");
}

// Instância da classe Pessoa
$pessoa = new Pessoa('Luiz', 'Miranda Figueiredo', 27);
$pessoa->exibir();
?>

Agora, independente da classe que eu tentar utilizar, o PHP vai saber que pretendo que ele inclua o arquivo da mesma em meu código. Porém, é necessário que você siga as instruções que descrevi anteriormente, ou seja, o nome do arquivo da classe deve ser "class-nomedaclasse.php", com letras minúsculas.

Isso vai facilitar a sua vida!

Criar clones de objetos em PHP é extremamente simples, você pode utilizar a palavra {php}clone{/php} para criar um objeto exatamente idêntico ao objeto clonado.

Veja um exemplo:

<?php 
class Pessoa
{
	// Propriedades da classe
	public $nome;
	public $sobrenome;
	public $idade;
	
	// Construtor - Define os valores das propriedades
	public function __construct ( $nome = null, $sobrenome = null, $idade = 0 ) {
		$this->nome      = $nome;
		$this->sobrenome = $sobrenome;
		$this->idade     = (int) $idade;
	}
	
	// Método para exibir
	public function exibir () {
		echo 'Nome: ';
		echo $this->nome . '<br>';
		echo 'Sobrenome: ';
		echo $this->sobrenome . '<br>';
		echo 'Idade: ';
		echo $this->idade . '<br><br>';
	}
}

// Instância da classe Pessoa
$pessoa = new Pessoa('Luiz', 'Miranda Figueiredo', 27);

// Aqui está o clone
$pessoa2 = clone $pessoa;
$pessoa2->exibir();
?>

No trecho acima, o objeto {php}$pessoa2{/php} recebe exatamente tudo o que o objeto {php}$pessoa{/php} possui. Como resultado teremos:

Nome: Luiz
Sobrenome: Miranda Figueiredo
Idade: 27

Porém, é meio inútil que você clone um objeto para outro, já que você pode utilizar o objeto criado anteriormente. Por isso, existe um método mágico, que não descrevemos na aula 37 (Métodos mágicos em classes PHP), que faz toda a diferença para os clones em PHP: O método {php}__clone{/php}.

Este método é utilizado no objeto original, que quando clonado, executa as ações de {php}__clone{/php} para realizar as alterações necessárias no novo objeto.

Veja um exemplo de alteração através do método {php}__clone{/php}.

<?php 
class Pessoa
{
	// Propriedades da classe
	public $nome;
	public $sobrenome;
	public $idade;
	
	// Construtor - Define os valores das proprieades
	public function __construct ( $nome = null, $sobrenome = null, $idade = 0 ) {
		$this->nome      = $nome;
		$this->sobrenome = $sobrenome;
		$this->idade     = (int) $idade;
	}
	
	// Método para exibir
	public function exibir () {
		echo 'Nome: ';
		echo $this->nome . '<br>';
		echo 'Sobrenome: ';
		echo $this->sobrenome . '<br>';
		echo 'Idade: ';
		echo $this->idade . '<br><br>';
	}
	
	// Método executado ao criar um clone
	public function __clone () {
		// Se for um clone, adiciona a palavra "Clonado" no valor
		$this->nome      = $this->nome . ' (Clonado)';
		$this->sobrenome = $this->sobrenome . ' (Clonado)';
		$this->idade     = $this->idade . ' (Clonado)';
	}
}

// Instância da classe Pessoa
$pessoa = new Pessoa('Luiz', 'Miranda Figueiredo', 27);

// Aqui está o clone
$pessoa2 = clone $pessoa;
$pessoa2->exibir();
?>

Perceba que estou alterando o valor das variáveis da classe no método {php}__clone{/php}, ou seja, agora tenho um novo resultado vindo do clone.

Nome: Luiz (Clonado)
Sobrenome: Miranda Figueiredo (Clonado)
Idade: 27 (Clonado)

Concluindo

A utilização de clones de objetos em PHP é bastante útil quando utilizamos o método {php}__clone{/php}. Dependendo da sua imaginação, você pode facilitar bastante a sua vida.

Não deixe de seguir todas as aulas do nosso curso gratuito de PHP e de PHP Orientado a Objetos.

Caso tenha dúvidas, não hesite em comentar.