Uma classe PHP-MySQL usando mysqli:__constructor e __destructor
Introdução
Programar é uma tarefa difícil. Difícil e trabalhosa. É uma arte, que exige experiência, dedicação, conhecimento, habilidade, criatividade, etc. O presente texto mostra, aos interessados, como preparar uma classe para manipular tabelas em bases de dados.
Há três recomendações para tornar a difícil arte, menos traumática:
- Refinamentos sucessivos
- Programação estruturada
- Programação Orientada a Objetos
Além das recomendações acima, a disciplina e o conhecimento das técnicas associadas a linguagens específicas são fundamentais. E as ferramentas, finalmente. Para base de dados uso o MySQL Workench e o MySQL Administator. Para editar os programas em PHP, uso o PHPDezigner e o Enterprise Architect para modelar meus projetos de desenvolvimento, em UML (Unified Modeling Language). Uso UML, também, para modelagem de negócios e modelagem de topologias de redes, de longa distância ou complexas, sobre as quais aplica-se os protocolos BGP, OSPF, RIP e associados (MPLS, por exemplo). A UML é ótima para modelar, inclusive, os componentes dinâmicos envolvidos em tais protocolos. Falo sobre tais ferramentas não porque sejam as melhores. As melhores ferramentas são aquelas com as quais você projeta e desenvolve seu estilo, de forma gratificante.
O principal objetivo deste trabalho é o desenvolvimento de programação orientada a objetos, em PHP. Usaremos o método direto, onde a didática tem valor significativo. Não é um texto para programadores muito experientes. É um texto para os iniciantes ou aqueles experientes interessados no aperfeiçoamento pessoal.
Construção da classe
Inicio as classes, usualmente, com três componentes: a propriedade (variável) que irá identificar o número de instâncias abertas, e os métodos construtor e destrutor. O método construtor conta as instâncias. E, um pequeno programa serve para testar cada etapa do processo de refinamento sucessivo. Eis a primeira versão, abaixo:
<?php
// Versão 1
class BancodeDados extends mysqli {
static $idBancodeDados = 0;
/**
* Construtor:
*
* 1. Conta o número de instâncias.
*/
public function __construct() {
self::$idBancodeDados++;
}
public function __destruct() {
--self::$idBancodeDados;
}
}
Eis o teste e respectivo resultado:
<?php
// Versão 1
require_once ('../includes/funcoesgerais.phpm');
require_once ('../includes/db.phpm');
$db_faleok = new db();
echo "
Instâncias abertas: " . BancodeDados::$idBancodeDados;
$db_faleok1 = new db();
echo "
Instâncias abertas: " . BancodeDados::$idBancodeDados;
?>
Instâncias abertas: 1
Instâncias abertas: 2
O interessante a observar acima são os seguintes pontos:
- A propriedade estática $idBancodeDados é incrementada no método __construtor, que como sabemos é executado na criação de uma instância da classe BancodeDados.
- Uma propriedade estática é referenciada externamente como NomedaClasse::$NomedaPropriedade, pois ela pertence à própria classe.
- A classe BancodeDados, herda métodos e funções da classe msqli, do PHP, uma vez que estamos usando extends mysqli, na sua declaração.
- É bom lembrar, que o método __constructor não pode retornar valores. Esta noção será muito útil mais à frente. Entretanto pode-se observar que o exemplo acima ele altera o valor de uma propriedade estática, isto é, $idBancodeDados, que é como se fosse uma variável global.
- O trecho:
/** * Construtor: * * 1. Conta o número de instâncias. */
é a documentação da classe. Como uso o phpdoc esta tarefa torna-se, em particular, muito importante.
Não se pode esquecer do registro de atividades. Foi construída uma classe chamada Mensagens que possui dois métodos. Um método exibe uma mensagem na tela, como acima, e o outro, grava a mensagem em um arquivo de “log”. Embora a classe Mensagens não seja exibida aqui (assim como outras que não a classe BancodeDados), o contexto a deixa claro. Eis como fica a versão 2 e seu resultado:
<?php $db_faleok1 = new BancodeDados(); Mensagem = "Instâncias abertas: " . BancodeDados::$idBancodeDados; $msg->Exibe(); $msg->LogFaleOK(); $db_faleok2 = new BancodeDados(); $msg->Mensagem = "Instâncias abertas: " . BancodeDados::$idBancodeDados; $msg->Exibe(); $msg->LogFaleOK(); ?> 20120121 11:55:43 - Instâncias abertas: 1 20120121 11:55:43 - Instâncias abertas: 2
As observações sobre a Versão 2 do teste são:
- Aperfeiçoamos o nome das instâncias da classe BancodeDados, dando mais consistência visual ($db_faleok1 e $db_faleok2).
- A mensagem é exibida com a data. Fica mais claro o exemplo, além do fato de que elas estão sendo registradas no “log”, para eventuais acessos futuros.
Melhor ficaria, se a saída fosse aperfeiçoada, como a versão 3 do teste:
<?php Mensagem = "Teste da classe db, Versão 3"; $msg->Exibe(); $db_faleok1 = new BancodeDados(); $msg->Mensagem = "Instâncias abertas: " . BancodeDados::$idBancodeDados; $msg->Exibe(); $msg->LogFaleOK(); $db_faleok2 = new BancodeDados(); $msg->Mensagem = "Instâncias abertas: " . BancodeDados::$idBancodeDados; $msg->Exibe(); $msg->LogFaleOK(); ?> 20120121 12:09:30 - Teste da classe db, Versão 3 20120121 12:09:30 - Instâncias abertas: 1 20120121 12:09:30 - Instâncias abertas: 2
O método direto exige algumas anotações sobre a Versão 3 do teste:
- Retiramos o comentário // Teste x do início e deixamos somente na mensagem a ser exibida na tela. Evitamos duas alterações de versões.
- A mensagem da versão só é exibida na tela. Não é registrada no “log”.
- Já percebe-se que o método construtor executa, imediatamente, ao se definir uma instância da classe. Mas, não temos noção do momento no qual o método destrutor é executado. Geralmente é depois que o programa principal termina (no nosso caso, o testegeral.php), ou logo depois da execução de algumas instruções após a última instrução que atua sobre a instância de uma classe.
A fim de fixar bem a questão do destrutor, abaixo está a versão 2, da classe, na qual adicionamos comentários e alteramos o método __destruct incluindo dois comandos. O primeiro, subtraindo 1 da propriedade estática $idInstanciaDB, pois ela representa o número de instâncias. Destruir significa, também, não possuir nenhuma instância! Incluímos uma saída na tela, para imprimir o momento da destruição e somente mostramos os trechos importantes:
<?php
.
.
.
class db extends mysqli {
static $idBancodeDados = 0;
.
.
.
public function __construct() {
self::$idBancodeDados++;
}
public function __destruct() {
--self::$idBancodeDados;
echo '
Executou a destrutora as ' . date("Ymd H:i:s");
}
}
Mensagem = "Teste da classe db, Versão 4";
$msg->Exibe();
$db_faleok1 = new BancodeDados();
$msg->Mensagem = "Instâncias abertas: " . BancodeDados::$idBancodeDados;
$msg->Exibe();
$msg->LogFaleOK();
$db_faleok2 = new BancodeDados();
$msg->Mensagem = "Instâncias abertas: " . BancodeDados::$idBancodeDados;
$msg->Exibe();
$msg->LogFaleOK();
?>
20120121 17:19:01 - Teste da classe db, Versão 4
20120121 17:19:01 - Instâncias abertas: 1
20120121 17:19:01 - Instâncias abertas: 2
Executou a destrutora as 20120121 17:19:01
Executou a destrutora as 20120121 17:19:01
Os comentários sobre o código acima:
- Duas instâncias foram abertas, portanto, duas execuções de destrutores.
- Os métodos destrutores foram executados em algum momento após o término do programa.
A versão final, completa e bem ilustrativa seria:
<?php
/**
* @author JB
* @version 2.0 (última revisão: Janeiro 18, 2012)
* @copyright (c) 2005-2012 Julião Braga
* @license No licence
* @package Banco de dados
*
*
* Classe BandodeDados:
*
*/
class BancodeDados extends mysqli {
static $idBancodeDados = 0;
public $nome_da_base;
/**
* Construtor:
*
*
*/
public function __construct() {
self::$idBancodeDados++;
}
public function __destruct() {
--self::$idBancodeDados;
}
}
?>
Mensagem = "Teste da classe BancodeDados, Versão 4";
$msg->Exibe();
$db_faleok1 = new BancodeDados();
$db_faleok2 = new BancodeDados();
$msg->Mensagem = "1 Instâncias abertas: " . BancodeDados::$idBancodeDados;
$msg->Exibe();
$msg->LogFaleOK();
$db_faleok1 = NULL;
$msg->Mensagem = "2 Instâncias abertas: " . BancodeDados::$idBancodeDados;
$msg->Exibe();
$msg->LogFaleOK();
$db_faleok2 = NULL;
$msg->Mensagem = "3 Instâncias abertas: " . BancodeDados::$idBancodeDados;
$msg->Exibe();
$msg->LogFaleOK();
?>
20120122 14:20:25 - Teste da classe db, Versão 4
20120122 14:20:25 - 1 Instâncias abertas: 2
20120122 14:20:25 - 2 Instâncias abertas: 1
20120122 14:20:25 - 3 Instâncias abertas: 0
Eis as observações sobre o código acima:
- A forma de executar o destrutor, para uma determinada instância da classe é: $nome_da_Instância->NULL;.
- Executar ou não o destrutor é uma decisão do programador. Se o programador gosta de exercer o controle total sobre seu código, então ele mesmo destrói.
O próximo método
Uma questão aparece, imediatamente está relacionada com o velho dilema: “quem veio primeiro? O ovo ou a galinha? No nosso contexto:
- As informações sobre Bases de Dados remotas, geralmente são repassadas e há alguma dificuldade para alterar a senha original. Mas, o desejável, sob o ponto de vista da segurança é que a senha não seja conhecida nem mesmo pelo usuário da base. Em outras palavras, não se deseja ver a senha ou, na melhor das hipóteses, o menor número de pessoas deveriam ver a senha.
- Neste sentido, a senha pode ser complicada e deverá ser gerada automaticamente pela classe e alterada automaticamente pela classe, preferencialmente.
- Na mesma linha de raciocínio, as informações deveriam ser incluídas pela classe, devidamente criptografada, como é o desejo inicial.
Assim, o próximo método a ser criado é o insereDB, cujo objetivo é inserir na base as informações que serão passadas pelo usuário da classe. Eis o cabeçalho de tal método:
public function insereBD($nomeBD, $usuarioBD, $senhaBD, $hostBD, $portaBD, $descricaoBD, $email_criadorBD) {
// corpo do método
}
Os primeiros cinco parâmetros são autoexplicativos. O parâmetro $descricaoDB é uma descrição sucinta da base de dados e o parâmetro $email_criadorBD identifica o e-mail do “dono” da base, que será usado, para contato automático, em oportunidades sensíveis à segurança. O código abaixo é o refinamento preliminar, que antecederia o tratamento dos dados entrantes e a inclusão na tabela:
class BancodeDados extends mysqli {
static $idBancodeDados = 0;
static $BancodeDadosAbertos = array();
private $_cripto;
private $_caracteres;
private $_maximo;
private $_passwd;
private $_nome;
private $_usuario;
private $_senha;
private $_host;
private $_porta;
private $_descricao;
private $_email_criador;
private $_senha_alternativa;
private $_senha_alternativaBD;
private $_bd;
/**
* Construtor:
*
* 1. Verifica se o banco de dados já existe
* 2. Se não existir cria, sem popular
* 3. Se existir, abre
* 4. {@usa: throw new DBConnectException($this->connect_error, $this->connect_errno);}
* para cobrir exceções
*
*/
public function __construct() {
self::$idBancodeDados++;
}
/**
* @author JB
* @version 1.0 (última revisão: Janeiro 18, 2012)
* @copyright (c) 2005 - 2012 Julião Braga
* @license No licence
* @package funcoesgerais.phpn
* @nome criaDB($nomeBD)
* @funcao Cria dados criptografados na tabela `basesdedados` da base faleok
*
* @Colunas:
* id_bd: INT
* nome_bd: VARCHAR
* usuario_bd: VARCHAR
* senha_bd: VARCHAR
* host_bd: VARCHAR
* porta_bd: VARCHAR
* descricao_bd: VARCHAR
* email_criador_bd: VARCHAR
* senha_alternativa: VARCHAR
*
*/
public function insereBD($nomeBD,
$usuarioBD,
$senhaBD,
$hostBD,
$portaBD,
$descricaoBD,
$email_criadorBD) {
$this->_cripto = new cripto;
$this->_caracteres = 'abcdxywzABCDZYWZ0123456789@#*';
$this->_maximo = strlen($this->_caracteres)-1;
$this->passwd = null;
mt_srand(make_seed());
for($i=0; $i passwd .= $this->_caracteres{mt_rand(0, $this->_maximo)};
}
$this->_senha_alternativaBD = $this->passwd;
$this->_nome = $this->_cripto->encrypt($nomeBD);
$this->_usuario = $this->_cripto->encrypt($usuarioBD);
$this->_senha = $this->_cripto->encrypt($senhaBD);
//$this->_senha_alternativaBD = base_convert(mt_rand() , 10 , 16);
$this->_host = $this->_cripto->encrypt($hostBD);
$this->_porta = $this->_cripto->encrypt($portaBD);
$this->_descricao = $this->_cripto->encrypt($descricaoBD);
$this->_email_criador = $this->_cripto->encrypt($email_criadorBD);
$this->_senha_alternativa = $this->_cripto->encrypt($this->_senha_alternativaBD);
// Tratamento dos dados
echo "Aqui entra o código para imprimir o resultado apresentado na figura, logo abaixo";
self::$BancodeDadosAbertos[self::$idBancodeDados] = $this->_cripto->decrypt($this->_nome);
echo '
Indice do vetor de BancodeDadosAbertos: ' . self::$idBancodeDados . "...pre...";
print_r(self::$BancodeDadosAbertos);
echo ".../pre...";
return true;
}
/**
*
* listaBDs()
*/
public function listaBDs() {
return $bds;
}
public function __destruct() {
--self::$idBancodeDados;
//$this->_bd->close();
}
}
function make_seed() {
list($usec, $sec) = explode(' ', microtime());
return (float) $sec + ((float) $usec * 100000);
}
<?php
require_once ($_SERVER['DOCUMENT_ROOT'] . "faleok/includes/funcoesgerais.phpm");
$bd_faleok = new BancodeDados();
$nomeBD = "nomedoBD";
$usuarioBD = "usuarioautorizado";
$senhaBD = "teste";
$hostBD = "127.0.0.1";
$portaBD = "3306";
$descricaoBD = "BD de teste";
$email_criadorBD = 'usuario@exemplo.com.br';
echo '
Retorno do método; ' .
$bd_faleok->insereBD($nomeBD,
$usuarioBD,
$senhaBD,
$hostBD,
$portaBD,
$descricaoBD,
$email_criadorBD);
?>
O resultado do programa acima pode ser visto na figura abaixo:
Os comentários são os seguintes:
- Uma outra propriedade estática foi incluída motivada pelo interesse em saber, para efeitos de gerenciamento e eficiência, quais as bases estão abertas em um determinado instante. É um “array” e sua atribuição é feita dentro do método insereBD:
static $BancodeDadosAbertos = array();
Já é possível antever um método listaDB, lembrado no códito, que informaria quais as bases abertas até o momento.
- Insistentemente, declaramos propriedades. Sempre que possível escondendo informações para outros componentes (encapsulamento), através do uso de private.
- A senha alternativa, isto é, aquela desconhecida de todos é gerada aleatoriamente, e com 15 caracteres, já que o ser humano não a manipula. Os algoritmos para isto estão disponíveis na Internet. Para aumentar a dificuldade, além dos 15 caracteres foi seguida a sugestão de usar um “alimentador” (‘make_seed’), para a função ‘mt_rand’, disponível no sítio do PHP. A função ‘make_seed’ está isolada no programa principal para, oportunamente, definir seu destino.
