Testando um framework PHP

Quando resolvi fazer a palestra sobre PHPUnit para o FLISOL, tive a ideia de também escrever os testes unitários em português para o Planeta Framework. Assim poderia esclarecer algumas dúvidas que tinha e ainda divulgar um bom material sobre testes.

Então escrevi todos os testes unitários. O interessante quando se escreve testes é que acabamos mudando a estrutura do código, deixando-a mais clara e mais fácil de ser testada. Isso é muito bom para planejamento de futuras arquiteturas e projetos.

Quando pensei que tinha acabado, resolvi colocar algumas ferramentas de validação, métrica e documentação. Peguei alguns exemplos do Sebastian Bergmann e coloquei as ferramentas para serem executadas por meio do Ant. É muito legal ver o resultado disso tudo rodando.

Para finalizar com estilo livre, quando subi para o Github, lembrei que poderia colocá-lo para rodar no servidor de integração contínua: Travis (travis-ci.org). Após dolorosas 14 tentativas, ele ficou verdinho. Nada como uma bolinha verde para o dia ficar feliz!

Todos os arquivos de configuração utilizados e todos os testes já estão disponíveis no Github: https://github.com/abdala/Planeta-Framework.

Estude com carinho! E se tiver alguma sugestão de aperfeiçoamento da nossa ferramenta de estudo: fork, fork, fork!

Abraço!

Construindo um framework PHP – Modelo

Quando falamos do “M” do MVC, a camada de modelo, sempre existem várias opiniões do seu papel. Acho que essa é a parte mais filosófica de todo o padrão.

Na maioria das frameworks, a parte de modelo também é a parte que faz acesso ao banco de dados. No nosso framework também vamos fazer desta maneira, mas lembre-se, isso não é uma regra. A camada de modelo não precisa obrigatoriamente fazer acesso ou ser a forma de acesso ao banco de dados. Ela pode ser para conter as nossas regras de negócio e os dados podem ser obtidos de outras fontes. Ex: arquivos e webservices.

Dica: Cuidado para não confundir validação de dados, com validação(regra) de negócio. Isso acontece com bastante frequência.

Validação de dados = verificar se um número é inteiro.

Validação de negócio = se o número for maior que 10, dividir ele por 2.

Entendido isso vamos para o que interessa. Vamos construir nossas classes de abstração de dados.Show me the code:

biblioteca/Planeta/Banco.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Planeta_Banco
{
	private $conexao;
	private static $instancia;
 
	public static function pegarInstancia()
	{
		if (!self::$instancia) {
			self::$instancia = new Planeta_Banco();
		}
 
		return self::$instancia;
	}
 
	public function pegarConexao()
	{
		return $this->conexao;
	}
 
	private function __construct() 
	{
		$this->conexao = new PDO('mysql:dbname=blog;host=127.0.0.1', 
                                  'root', 
                                  '123456', 
                                  array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'"));
	}
 
	private function __clone() 
	{
		throw Exception('Nao pode');
	}
}

É interessante verificar a utilização do padrão de projeto Singleton. Ele garante que só haverá uma única instância do objeto banco criado. Para conseguir isso, declaramos o método construtor como privado, assim, não é possível criar um novo objeto publicamente com o operador new. Para se criar esse objeto então, fazemos uma chamada estática(que não precisa de instância) e essa chamada vai fazer o controle de criação deste objeto.

biblioteca/Planeta/Banco/Tabela.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
abstract class Planeta_Banco_Tabela
{
	protected $nome;
	protected $chave;
 
	public function pegarNome()
	{
		return $this->nome;
	}
 
    /**
     *
     * @return Planeta_Banco
     */
	public function pegarBanco()
	{
		return Planeta_Banco::pegarInstancia();
	}
 
	public function buscarTodos()
	{
		$sql = sprintf("SELECT * FROM %s", $this->nome);
		return $this->pegarBanco()->pegarConexao()->query($sql)->fetchAll();
	}
 
	public function buscar($id)
	{
		$sql = sprintf("SELECT * FROM %s WHERE %s = %d", $this->nome, $this->chave, $id);
		return $this->pegarBanco()->pegarConexao()->query($sql)->fetch();
	}
 
	public function excluir($id)
	{
		$sql = sprintf("DELETE FROM %s WHERE %s = %d", $this->nome, $this->chave, $id);
		return $this->pegarBanco()->pegarConexao()->exec($sql);
	}
 
	public function inserir($dados)
	{
		unset($dados[$this->chave]);
		$campos  = array_keys($dados);
		$sql     = "INSERT INTO %s(%s) VALUES(:%s)";
		$sql     = sprintf($sql, $this->nome, \implode(",", $campos), \implode(",:", $campos));
 
		$confirmacao = $this->pegarBanco()->pegarConexao()->prepare($sql);
		return $confirmacao->execute($dados);
	}
 
	public function atualizar($dados)
	{
		$set = "";
		$id  = $dados[$this->chave];  
 
		unset($dados[$this->chave]);
 
        $campos = array_keys($dados);
 
		foreach ($campos as $campo) {
			$set .= sprintf("%s = :%s, ", $campo, $campo);
		}
 
		$set = substr($set, 0, strlen($set)-2);
		$sql = "UPDATE %s SET %s WHERE %s = %d";
		$sql = sprintf($sql, $this->nome, $set, $this->chave, $id);
 
        $confirmacao = $this->pegarBanco()->pegarConexao()->prepare($sql);
		return $confirmacao->execute($dados);
	}
}

Pronto! Agora vem a parte mais simples. Criar o Modelo/Tabela.

aplicacao/modelo/Postagem.php

1
2
3
4
5
class Postagem extends Planeta_Banco_Tabela
{
	protected $nome  = "postagem";
	protected $chave = "id";
}

E vamos modificar o nosso controlador e nossa visao também:

aplicacao/controlador/ControladorInicial.php

1
2
3
4
5
6
7
8
9
class ControladorInicial extends Planeta_Mvc_Controlador
{
    public function acaoInicial() 
    {
        $postagem = new Postagem();
        $this->visao->dados = $postagem->buscarTodos();
        $this->renderizar();
    }
}

aplicacao/visao/inicial/inicial.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta http-equiv="keywords" content="html css php planeta programacao">
    <meta http-equiv="description" content="Arquivo de demonstração">
    <title>Planeta Framework</title>
</head>
<body>
    <h1>Inicial do blog</h1>
    <p>Página com conteúdo dinâmico</p>
    <br />
    <?php foreach($this->dados as $dados):?>
        <div class="post-item">
            <h3><?php echo $dados['nome']?></h3>
            <p><?php echo $dados['descricao']?></p>
        </div>
        <br />
    <?php endforeach;?>
</body>
</html>

Agora é só escrever o restante das operações(inserir, atualizar e excluir). Espero que tenha gostado!

Construindo um framework PHP – Visão

Neste post vamos ver como construir e utilizar a parte de visão do nosso framework.

A visão é responsável por retornar alguma informação para o usuário(cliente). Essa informação normalmente é retornada em formato HTML, mas ela pode retornar em outros formatos também, como: JSON, XML, CSS etc.

A inclusão da visão nada mais é do que um include do arquivo. Vamos padronizar o local do arquivo(nome do nosso controlador) e nome(nome da ação) para facilitar a inclusão dele o código vai ficar assim:

aplicacao/controlador/ControladorInicial.php

1
2
3
4
5
6
7
class ControladorInicial
{
    public function acaoInicial() 
    {
        require "../aplicacao/visao/inicial/inicial.php";
    }
}

Simples, não? Para passar os dados para a nossa visão vamos criar variáveis no controlador e acessa-lás no arquivo de visão. Desta forma:

aplicacao/controlador/ControladorInicial.php

1
2
3
4
5
6
7
8
class ControladorInicial
{
    public function acaoInicial() 
    {
        $titulo = "Blog Planeta Framework";
        require "../aplicacao/visao/inicial/inicial.php";
    }
}

aplicacao/visao/inicial/inicial.php

1
<h1><?php echo $titulo?></h1>

Mas, pense comigo, se tivermos muitos controladores e muitas ações isso vai ficando cada vez mais complexo. Então, o que vamos fazer agora e organizar tudo isso. Vamos modificar a nossa função de autoload, construir uma classe de controlador, de visão e uma MVC para cuidar de tudo isso. Você vai ver como vai ficar bem mais fácil e organizado.

Nossa classes ficarão assim:

bibiloteca/Planeta/Mvc/Visao.php

1
2
3
4
5
6
7
8
class Planeta_Mvc_Visao
{
	public function renderizar($diretorio, $arquivo)
	{
            $local  = '../aplicacao/visao/';
	    require $local . $diretorio . '/' . $arquivo;
	}
}

bibiloteca/Planeta/Mvc/Controlador.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Planeta_Mvc_Controlador
{
	protected $visao;
 
	public function __construct() 
	{
		$this->visao = new Planeta_Mcv_Visao();
	}
 
	public function renderizar()
	{
		$diretorio = strtolower(Planeta_Mcv_Visao::pegarInstancia()->pegarControlador());
		$arquivo   = strtolower(Planeta_Mcv_Visao::pegarInstancia()->pegarAcao()) . ".php";
 
		$this->visao->renderizar($diretorio, $arquivo);
	}
}

bibiloteca/Planeta/Mvc.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class Planeta_Mvc
{
    /**
     * Nome do controlador
     * 
     * @var string
     */
    protected $controlador;
 
    /**
     * Nome do controlador
     * 
     * @var string
     */
    protected $acao;
 
    /**
     * Instancia única do objeto Planeta_Mvc
     * 
     * @var Planeta_Mvc
     */
	private static $instancia;
 
    /**
     * Implementação do Singleton
     *
     * @return Planeta_Mvc 
     */
	public static function pegarInstancia()
	{
                //verifica se a instância existe
		if (!self::$instancia) {
			self::$instancia = new Planeta_Mvc();
		}
 
		return self::$instancia;
	}
 
    /**
     * Construtor privado para forçar o Singleton
     * 
     * @return void
     */
	private function __construct() 
	{}
 
    /**
     * Pega o controlador da requisição atual
     * 
     * @return string  
     */
    public function pegarControlador() 
    {
        return $this->controlador;
    }
 
    /**
     * Pega a ação da requisição atual
     * 
     * @return string  
     */
    public function pegarAcao() 
    {
        return $this->acao;
    }
 
         public function rodar()
         {
		//pega o modulo, controlador e acao 
		$controlador = isset($_GET['c']) ? $_GET['c'] : 'inicial';
		$acao		 = isset($_GET['a']) ? $_GET['a'] : 'inicial';
 
		//padronizacao de nomes
		$this->controlador = ucfirst(strtolower($controlador));
		$this->acao 	   = ucfirst(strtolower($acao));
 
                $nomeClasseControlador = 'Controlador' . $this->controlador;
	        $nomeAcao              = 'acao' . $this->acao;
 
                //verifica se a classe existe
		if (class_exists($nomeClasseControlador)) {
			$controladorObjeto = new $nomeClasseControlador;
 
			//verifica se o metodo existe
			if (method_exists($controladorObjeto, $nomeAcao)) {
				$controladorObjeto->$nomeAcao();
				return true;
			}
			throw new Exception('Acao nao existente.');
		}
		throw new Exception('Controlador nao existente.');
	}
 
        private function __clone() 
	{
		throw Exception('Nao pode');
	}
}

bibiloteca/Planeta/CarregadorAutomatico.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Planeta_CarregadorAutomatico
{
    /**
     * Carrega a classe se ea existir
     *
     * @param string $nomeDaClasse
     * @return boolean
     */
	public static function carregar($nomeClasse)
	{
                //lista de diretorios que as classes serão pesquisadas
		$diretorios = array('../aplicacao/controlador', 
						    '../biblioteca');
 
                //transforma parte do nome da classe para diretorio
		$nomeClasse = str_replace(array('_', '\\'), '/', $nomeClasse);
 
                //procura as classes nos diretorios definidos 
		foreach ($diretorios as $diretorio) {
			//pega o caminho real do diretorio e junta com o nome da classe
                        $localClasse = realpath($diretorio) . '/' . $nomeClasse . ".php";
 
                        //checa se o arquivo existe
			if (file_exists($localClasse)) {
                                //inclue o arquivo
				require($localClasse);
                                //returna verdadeiro quando achou e para o loop
				return true;
			}
		}
		return false;
	}
 
    /**
     * Resitra o método para ser usado no autoload
     */
	public static function registrar()
	{
		spl_autoload_register('Planeta_CarregadorAutomatico::carregar');
	}
}

publico/index.php

1
2
3
4
require '../biblioteca/Planeta/CarregadorAutomatico.php';
 
Planeta_CarregadorAutomatico::registrar();
Planeta_Mvc::pegarInstancia()->rodar();

Agora para fazer referência a nossas variáveis de Visão vamos adicionar atributos na classe de Visão. E como o include agora está sendo feito dentro da classe de visão, no arquivo de visão iremos referenciar os atributos utilizando o $this. Assim:

aplicacao/controlador/ControladorInicial.php

1
2
3
4
5
6
7
8
class ControladorInicial extends Planeta_Mvc_Controlador
{
    public function acaoInicial() 
    {
        $this->visao->titulo = "Blog Planeta Framework";
        $this->renderizar();
    }
}

aplicacao/visao/inicial/inicial.php

1
<h1><?php echo $this->titulo?></h1>

Ufa! Acho que por hoje já está bom. Nos vemos no próximo post.

Um grande abraço.

Espero que tenha te ajudado.

Construindo um framework PHP – Controlador

Olá! Nesse post o nosso objetivo principal vai ser: conseguir chamar um controlador e sua ação.

A primeira coisa que vamos fazer é definir a estrutura de diretório da nossa apliação. Vamos construir uma estrutura que seja compatível com o padrão MVC. Ela ficará assim:

Aplicacao: arquivos da nossa aplicação. O que de fato vamos desenvolver.

Biblioteca: arquivos auxiliares externos. Desenvolvidos por outras pessoas/empresas e que vamos utilizar na construção da nossa aplicação. Ex: Frameworks, classes de geração de PDF, geração de imagens, envio de e-mail etc.

Público: arquivos que vão estar na pasta pública do nosso servidor(www, public_html). É importante que nossos arquivos PHP fiquem em uma pasta que não seja pública, assim, podemos definir uma única porta de entrada para nossa aplicação, o index.php, e deixar os outros arquivos protegidos.

Agora vamos criar o conteúdo do arquivo index.php. Como ele vai ser a porta de entrada da aplicação, ele terá de ser capaz de, por meio de parâmetros, conseguir chamar os controladores. Então ficará assim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//define a função mágica de carregamento automático de classes. 
//Essa função é chamada sempre que criamos um objeto, assim, não precisamos dar o require manualmente.
function __autoload($nomeClasse) {
	$dir = '../aplicacao/controlador';
    $localClasse = realpath($dir) . '/' . $nomeClasse . '.php';
 
    if (file_exists($localClasse)) {
        require($localClasse);
        return true;
    }
	return false;
}
 
//define o nome padrão para o controlador e a acao
$nomeControlador = "inicial";
$nomeAcao        = "inicial";
 
//verifica se existe parametro "controlador" e se ele tem valor
if (isset($_GET['controlador']) && $_GET['controlador']) {
    $nomeControlador = $_GET['controlador'];
}
 
//verifica se existe parametro "acao" e se ele tem valor
if (isset($_GET['acao']) && $_GET['acao']) {
    $nomeAcao = $_GET['acao'];
}
 
//padronizacao de nome
$nomeControlador = 'Controlador' . ucfirst(strtolower($nomeControlador));
$nomeAcao 	 = 'acao' . ucfirst(strtolower($nomeAcao));
 
//chamada da classe(controlador) e metodo(acao)
if (class_exists($nomeControlador)) {
	$controlador = new $nomeControlador;
 
	if (method_exists($controlador, $nomeAcao)) {
	    $controlador->$nomeAcao();
	} else {
            echo "Acao nao encontrada.";
        }
} else {
    echo "Controlador nao encontrado.";
}

Só falta construir o nosso controlador. Ele será bem simples e ficará dentro da pasta aplicacao/controlador. Segue o código:

1
2
3
4
5
6
7
class ControladorInicial
{
    public function acaoInicial()
    {
        echo "Sou a acao inicial";
    }
}

Agora é chamar no browser para ver se está tudo funcionando. Acesse: http://localhost/planeta/primeiro/publico/index.php.

No meu repositório https://github.com/abdala/Planeta-Framework coloquei mais um controlador de exemplo.

Espero que te ajude. Abraço!

Construindo um framework PHP

Há alguns anos, em minhas turmas de PHP, venho dando exemplos de padrões de projetos e utilização de orientação a objetos, fazendo os alunos criarem o seu próprio framework PHP. Com isso consigo atingir dois pontos principais: a utilização e a criação de um framework, duas coisas completamente diferentes.

Então vou construir o passo-a-passo e, em cada novo post, uma nova funcionalidade será acrescentada ao nosso framework. Seu nome será Planeta. Ele foi inspirado no desenho Capitão Planeta: quando tudo está perdido, os meninos juntam seus poderes e o Capitão Planeta resolve tudo. O Planeta Framework tem o mesmo papel: quando você achar que tudo está perdido, que não vai ter futuro, ele vai te mostrar que “Programação não tem segredo. É um IF ou um INCLUDE”.

Para facilitar o entendimento, começaremos com estruturas bem simples – quando digo simples, não estou brincando! -, aperfeiçoando até chegar nas últimas funcionalidades disponibilizadas no PHP.

Todos os códigos estarão disponíveis em meu repositório: https://github.com/abdala/Construindo-um-framework-PHP.

Espero que isso te ajude de alguma forma.

Grande abraço!

 

Por que PHP?

Quando comecei o meu contato com internet navegava por páginas bonitas que terminavam em “.php” e achava que tinha que aprender aquilo. Não por conta das informações dinâmicas, mas sim pelo visual e comportamento que, na maioria das vezes, era exatamente o que eu admirava.

Na época sempre me deparava com páginas terminando em “.asp”, “.php” e “.html”. Aprendi a programar com aquilo que tive contato. Um menu saltando na minha cara…logo abria o código-fonte para ver como aquilo era feito. Hoje tenho total esclarecimento das coisas. Sei bem que linguagem nenhuma faz um programa. Poderia programar em qualquer linguagem de programação? Sim. Poderia mudar para outra que, talvez, fosse melhor? Sim, claro! Por que não faço isso?

Lendo o manual do PHP me identifiquei com o seguinte trecho:

What can PHP do?

Anything. PHP is mainly focused on server-side scripting, so you can do anything any other CGI program can do, such as collect form data, generate dynamic page content, or send and receive cookies. But PHP can do much more.

Sinto pelo PHP a mesma coisa que sinto pelo Brasil. Foi onde comecei, foi quem me acolheu. É onde eu vivo e é quem me sustenta. Posso fazer alguma viagem internacional de vez em quando, morar em outro país por um tempo, mas a raiz não tem como mudar.

 

 

PHP’n Rio

Queria que todos do DF estivessem presentes no PHP’n Rio. O evento contou com forte participação da comunidade e deixou bem claro que o PHP pode ser utilizado em vários cenários. Mobile, robótica, escalabilidade, SEO, performance etc.

O evento foi muito bem organizado e a estrutura do local estava do tamanho que a comunidade merece. Todas as salas que entrei estavam até a tampa!

Gostei muito do espaço de Desconferência. Nunca tinha visto nada igual em um evento de tecnologia. Foi disponibilizado projetor e microfone para qualquer pessoa falar sobre qualquer assunto. Sem controle, sem censura, sem mimimi. Pluga e se diverte.

Parabéns a todos do @phprio que organizaram o evento. É sempre legal conhecer pessoas que estão realmente interessadas em fazer.