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.