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!