PHP:: PDO – CONEXÃO CONFORME PADRÕES SINGLETON E FACTORY

php_logo2

Não é novidade para ninguém a necessidade de conexão com um banco de dados sempre quando se desenvolvem aplicações com persistência. O problema é que se você estiver trabalhando em um projeto sem frameworks, precisará desenvolver diversas classes, antes de iniciar o trabalho sobre as regras de negócio. Dependendo do seu projeto, nem sempre a opção será trabalhar com um Framework para fazer todo trabalho pesado. Mas é claro que hoje em dia, a maioria dos programadores já adotaram o paradígmica de programação orientado a objetos, como o melhor método de desenvolvimento. Unindo as duas idéias, na minha opinião, você quase sempre terá de possuir ou desenvolver uma classe para se conectar com o banco de dados. Com este objetivo, apresento uma classe que sempre tenho utilizado.

Talvez você sempre tenha de alterar configurações tais como: nome do banco, o endereço do servidor, ou até mesmo o tipo de banco de dados. Então, o quando você precisa editar a classe e alterar estas configurações nela, corre alguns riscos e entre eles, alterar código sensível. Então, um modo interessante é criar um arquivo de configuração .INI. Vamos ver como funciona?

1) na pasta de seu projeto, crie um arquivo chamado configdb.ini e adicione os dados abaixo, conforme refletir seu ambiente:


; Arquivo de configuração de conexão com o banco de dados
; Driver de conexão
sgdb      = mysql
; Dados para conexão
banco     = dbmvc
servidor = localhost
porta     = 3306
; Credenciais de usuário
usuario  = root
senha    = S3nh@

2) agora crie um arquivo com o nome connection.php e insira estes códigos:


final class Connection
{
    private static $connection;

    /**
     * Singleton: Método construtor privado para impedir classe de gerar instâncias
     *
     */
    private function __construct()
    {}

    private function __clone()
    {}

    private function __wakeup()
    {}
}

Até aqui, criamos uma classe, embora ainda não esteja fazendo nada, não é possível ser instanciada. E isso é bem válido porque, qual a necessidade de instanciar uma classe para conexão se o objetivo dela é somente prover um objeto PDO para manipular-mos o banco de dados? Caso você ainda não conheça, seja bem vindo ao padrão de projetos Singleton!

Segundo a Wikipédia:

Singleton é um padrão de projeto de software (do inglês Design Pattern). Este padrão garante a existência de apenas uma instância de uma classe, mantendo um ponto global de acesso ao seu objeto. (https://pt.wikipedia.org/wiki/Singleton)

Mas a Wikipédia menciona uma situação que tem tudo a ver com nosso artigo:

Quando você necessita de somente uma instância da classe, por exemplo, a conexão com banco de dados, vamos supor que você terá que chamar diversas vezes a conexão com o banco de dados em um código na mesma execução, se você instanciar toda vez a classe de banco, haverá grande perda de desempenho, assim usando o padrão singleton, é garantida que nesta execução será instânciada a classe somente uma vez.

Então, como é que utilizamos este padrão?

  • Deixar o construtor privado, pois assim ninguém deve conseguir instanciar a classe, apenas o próprio Singleton.
  • Criar um atributo privado e estático do mesmo tipo da classe (instance). Algumas linguagens não tipadas não irão precisar do tipo, caso do PHP, por exemplo.
  • Método getInstance() é o principal ponto da classe. Ele verifica se a variável instance já foi iniciada, caso não tenha sido, ele faz sua criação pela primeira e única vez.
  • Para fazer a conexão, devemos chamar o getInstance.

Os dois primeiros requisitos, já estão ok! Porque nosso construtor está privado e não permite instanciar:


private function __construct()
{}

Além disso, não queremos permitir que haja nenhum tipo de reconstrução de objetos com base nesta classe de conexão, utilizando unserialize(), por isso deixamos privado os método mágico __wakeup(). Não vamos utilizar serialização proque não queremos transportar classes para serem executadas em um outro lugar ou armazenar o estado de algum objeto construído a base dessa classe em uma string. Além disso, não precisamos clonar a propriedade instance desta classe, e por isso tornamos __clone() também privado. Note também, que esta classe não permite herança, quando utilizamos a definição ‘final class’.

3) Agora adicione outro método estático, privado chamado load. Este método lê o arquivo INI no disco e converte suas linhas para array, utilizando a função parse_ini_file:


    /**
     * Método estático privado que permite o carregamento do arquivo
     * @param $arquivo string
     * @return array
     */
    private static function load(string $arquivo): array
    {
        // $arquivo = 'subsdiretorio/' .$arquivo.'.ini';

        if(file_exists($arquivo)) {
            $dados = parse_ini_file($arquivo);
        } else {
            throw new Exception('Erro: Arquivo não encontrado');
        }
        return $dados;
    }


Você pode escrever este método para aceitar uma string de nome de arquivo de qualquer maneira, ou simplesmente, definir um caminho internamente, como a linha comentada. A opção é sua!

Note que fizemos um teste antes para cerificar da existência do arquivo, e se o mesmo existir seguimos em frente, caso contrário, será lançada uma exceção.

A função parse_ini_file, interpreta um arquivo INI de configuração que carregar a partir do nome que especificarmos como argumento. Então retornará as configurações contidas nele em um array associativo. Sua sintaxe é:

array = parse_ini_file ( string $nome_do_arquivo [, bool $process_sections ] )

O parâmetro opcional $process_sections aceita apenas valores booleanos e o padrão é FALSE, caso não especificado. É interessante quando você têm muitas sessões e quer obter a interpretação disso como um array multidimensional. Por exemplo, conforme exemplo do manual online (http://php.net/manual/pt_BR/function.parse-ini-file.php), em um arquivo assim:


; Este é um arquivo de configuração de exemplo
; Comentários iniciam com ';', como no php.ini
[primeira_secao]
um = 1
cinco = 5
animal = PASSARO

[segunda_secao]
path = "/usr/local/bin"
URL = "http://www.example.com/~username"

4) Agora, precisamos de um método que faça uso do método load para ‘fabricar’ a conexão. Este é o método make, que também é privado. O método make faz uso do array retornado pelo método load e montará a string de conexão conforme o tipo de banco de dado informado.


    /**
     * Método montar string de conexao e gerar o objeto PDO
     * @param $dados array
     * @return PDO
     */

    private static function make(array $dados): PDO
    {
        // capturar dados
        $sgdb     = isset($dados['sgdb']) ? $dados['sgdb'] : NULL;
        $usuario  = isset($dados['usuario']) ? $dados['usuario'] : NULL;
        $senha    = isset($dados['senha']) ? $dados['senha'] : NULL;
        $banco    = isset($dados['banco']) ? $dados['banco'] : NULL;
        $servidor = isset($dados['servidor']) ? $dados['servidor'] : NULL;
        $porta    = isset($dados['porta']) ? $dados['porta'] : NULL;

        if(!is_null($sgdb)) {
            // selecionar banco - criar string de conexão
            switch (strtoupper($sgdb)) {
                case 'MYSQL' : $porta = isset($porta) ? $porta : 3306 ; return new PDO("mysql:host={$servidor};port={$porta};dbname={$banco}", $usuario, $senha);
                   break;
                case 'MSSQL' : $porta = isset($porta) ? $porta : 1433 ;return new PDO("mssql:host={$servidor},{$porta};dbname={$banco}", $usuario, $senha);
                   break;
                case 'PGSQL' : $porta = isset($porta) ? $porta : 5432 ;return new PDO("pgsql:dbname={$banco}; user={$usuario}; password={$senha}, host={$servidor};port={$porta}");
                   break;
                case 'SQLITE' : return new PDO("sqlite:{$banco}");
                   break;
                case 'OCI8' : return new PDO("oci:dbname={$banco}", $usuario, $senha);
                   break;
                case 'FIREBIRD' : return new PDO("firebird:dbname={$banco}",$usuario, $senha);
                   break;
            }
        } else {
            throw new Exception('Erro: tipo de banco de dados não informado');
        }
    }

Está aqui a parte inteligente da classe! Estamos emprestando a funcionalidade do maravilhoso padrão factory method!

Novamente, conforme a Wikipedia: …que permite às classes delegar para subclasses decidirem, isso é feito através da criação de objetos que chamam o método fabrica especificado numa interface e implementado por um classe filha ou implementado numa classe abstrata e opcionalmente sobrescrito por classes derivadas. (https://pt.wikipedia.org/wiki/Factory_Method)

Se você ler mais, sobre factory, vai perceber que existem mais classes envolvidas, pelos menos duas classes abstratas e duas classes concretas. Sinceramente, você acha necessário tudo isso aqui neste nosso projeto? Por isso, o método está servindo como uma fábrica de objetos conforme o Factory Method trabalha, mas não precisamos criar as classes abstratas. O Factory Method geralmente conta com um bloco de comandos switch case ou if, para tomar decisão sobre qual a classe concreta instanciar, e este trabalho nosso método está fazendo.

Falamos muito sobre o Factory, mas a grande sacada deste método é possibilitar a você trabalhar com bancos de dados diferentes. Talvez você esteja desenvolvendo um projeto com MySQL como banco de dados, mas em outro momento, um outro projeto em Postgree. Porque reconstruir a roda?

5) Certo, mas a classe ainda não está fazendo nada! Então, qual era o terceiro requisito de nosso projeto para que a classe seja considerada Singleton?

  • Método getInstance() é o principal ponto da classe. Ele verifica se a variável instance já foi iniciada, caso não tenha sido, ele faz sua criação pela primeira e única vez.

Agora por fim, entra em cena o nosso método público getInstance:


    /**
     * Método estático que devolve a instancia ativa
     *
     */
    public static function getInstance(string $arquivo): PDO
    {
        if(self::$connection == NULL) {
           // Receber os dados do arquivo
           self::$connection = self::make(self::load($arquivo));
           self::$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
           self::$connection->exec("set names utf8");
        }
        return self::$connection;
    }

Veja que o método getInstance é consumidor dos outros métodos load e make. Além disso é ele quem recebe o nome do arquivo como argumento pelo código cliente, visto que é o único método público. Associa o objeto PDO na propriedade estática $connection, que será retornada ao código cliente. Mas o interessante é que o papel do singleton é manter uma única instância ativa, não permitir duplicidade da instância de um objeto, como se fosse uma variável global. Essa mecânica acontece naquele teste:


if(self::$connection == NULL) {

Então, sempre o método deverá retornar uma instância salva em $connection.

6) O último requisito de nosso projeto?

  • Para fazer a conexão, devemos chamar o getInstance.

Então, faça o teste da classe, criando um arquivo index.php e insira este código:


$con = connection::getInstance('./configdb.ini');

echo (is_a($con, PDO))?'Instanciado com êxito' :'Não deu certo!';

Se você obteve o retorno ‘Instanciado com êxito’, é porque está tudo Ok.

7) Concluíndo

Como dito na introdução, quase sempre você irá precisar de uma classe para gerar a conexão com o banco, ou um arquivo de conexão. Então porque não criar uma classe um pouco mais robusta? Além disso, como programador, você poderá em algum momento se deparar com projetos que exijam bancos de dados diferentes. Esta classe atenderá perfeitamente as suas necessidades, sem precisar reconstruir a roda.

[]’s

8 comentários

  1. Professor o senhor não deu um exemplo de execução da query nessa classe!!! desculpe mas eu não tenho muita prática… seria possível?

    1. Olá Carlos, esse projeto não abrange a apresentação de query porque ele apenas abstrai a conexão. Se você quer algo como CRUD, eu tenho outros artigos no blog, inclusive sobre ORM, que utiliza um classe de conexão como esta deste artigo: https://alexandrebbarbosa.wordpress.com/2018/01/08/php-padrao-de-projeto-active-record-orm/
      Você pode combinar os conteúdos para obter um CRUD completo com essa camada de conexão com banco.

      1. Obrigado professor!!! Já consegui aprender a usar… Mas fiquei com uma dúvida muito grande…

        Fiz um teste para dar erro de proposito, e não vi a classe responder com erros para que eu pudesse tratar…

        Por exemplo como posso confirmar se o registro deu update ou que a query foi bem sucedida ou mal sucedida para que eu possa criar as devidas mensagens?

        Perdão se for uma pergunta muito simples!

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.