Classe de conexão com banco Mysql em PHP utilizando a interface PDO

Olá estive procurando um assunto para abordar neste blog e resolvi fazer um tutorial sobre como se conectar ao banco de dados mysql utilizando a interface de abstração PDO e por cima deselvolver um classe que nos ajudasse a abstrair mais inda as açoes de DELETE,SELECT,UPDATE e INSERT.

Antes de desenvolvermos qualquer codigo vamos listar os passos que vamos seguir para criarmos nossa classe.

  1. Criar a interface DBInterface que nossa classe DataBase implementará
  2. Criar nossa classe DB
  3. Criar nossos atributos que vamos utilizar em nossa classe
  4. Criar nosso metodo construtor
  5. Criar metodo para realizar nossa conexão com o banco
  6. Desenvolver os metodos que implementamos da interface
  7. Realizar os testes

Agora que nossos objetos foram listados vamos começar pelo primeiro passo que é criar a nossa interface.Não se preocupe com os metodos listados nessa interface pois abordaremos cada um deles ao longo do tutorial.

interface  DBInterface {

    public function newConnection(String $dbName, String $dbUser, String $dbPass, String $dbHost);

    public function setBindValues(array $args);
    public function executeSql(String $sql, array $args, bool $isKeyValue);
 
    public function setSqlInsert(String $table, array $args, bool $isKeyValue);
    public function insert(String $table, array $args,bool $isKeyValue);

}

Agora vamos criar nossa classe DB.

class DB {
}

Nossa classe possui alguns atributos:

//Host onde está localizado seu banco
const DBHOST = "localhost"; 

//Nome do usuário do banco 
const DBUSER = "root";

//Senha de acesso ao banco
const DBPASS = "";

//Senha de seu banco
const DBNAME = "";

//Após iniciado a conexão iremos armazenalá aqui
public $conn;

//Armazenamos nossa sql de consuta aqui
public $sql;

// Armazenamos nosso a transação aqui
public $stmt;

Se você chegou até aqui nossa classe deve ficar assim:

class DB{

   const DBHOST = ""; 
   const DBUSER = "";
   const DBPASS = "";
   const DBNAME = "";

   public $conn;
   public $sql;
   public $stmt;

}

Agora vamos criar nosso metodo construtor de nossa classe que recebe os parametros de conexão com o banco, caso queira se conectar com um outro banco ao instanciar nossa classe.

public function  __construct($dbName=DB::DBNAME, $dbUser=DB::DBUSER, $dbPass=DB::DBPASS, $dbHost=DB::DBHOST ){

}

Vamos criar nosso metodo que é responsável por realizar nossa conexão com o banco a newConnection, assim como nosso metodo construtor ele também recebe os parametros de conexão do banco, poís as vezes não queremos instanciar um novo objeto com uma conexão diferente apenas queremos mudar a conexão atual.

public function newConnection($dbName=DB::DBNAME,$dbUser=DB::DBUSER,$dbPass=DB::DBPASS,$dbHost=DB::DBHOST){
   try
   {
      $this->conn = new \PDO(
         "mysql:dbname=".$dbName.";charset=utf8;host=".$dbHost, 
          $dbUser,
          $dbPass,
          array(\PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'")
     );

     $this->conn->setAttribute(
         \PDO::ATTR_ERRMODE,
         \PDO::ERRMODE_EXCEPTION
     ); 

     $this->conn->setAttribute(
         \PDO::ATTR_DEFAULT_FETCH_MODE,
         \PDO::FETCH_ASSOC
     ); 
   }
   catch ( PDOException $e)
   {   
     echo 'Não foi possível se conectar com a base de dados';
     exit;
   }
} 

Agora vamos chamar o metodo newConnection em nosso contrutor, então nosso classe deve ficar assim:

class DB {

    const DBHOST = "localhost"; 
    const DBUSER = "root";
    const DBPASS = "";
    const DBNAME = "app";

    public $conn;
    public $sql;
    public $stmt;

    public function  __construct($dbName=DB::DBNAME,$dbUser=DB::DBUSER,$dbPass=DB::DBPASS,$dbHost=DB::DBHOST){
        $this->newConnection($dbName,$dbUser,$dbPass,$dbHost);
    }
    
    public function newConnection($dbName=DB::DBNAME,$dbUser=DB::DBUSER,$dbPass=DB::DBPASS,$dbHost=DB::DBHOST){
        try
        {
            $this->conn = new \PDO("mysql:dbname=".$dbName.";charset=utf8;host=".$dbHost, 
                $dbUser,
                $dbPass,
                array(\PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'")
            );
            
            $this->conn->setAttribute(
                \PDO::ATTR_ERRMODE,
                \PDO::ERRMODE_EXCEPTION
            );

            $this->conn->setAttribute(
                \PDO::ATTR_DEFAULT_FETCH_MODE,
                \PDO::FETCH_ASSOC
            );
        }
        catch ( PDOException $e)
        {   
            echo 'Não foi possível se conecytar com a base de dados';
            exit;
        }
    }
}

Agora que a conexão com o banco já está feita vamos implementar nossa interface, e criar nossos metodos contidos em nossa interface.

class DB implements DBInterface {
  ..........
  
}

Após implementada nossa interface se você tentar instanciar nossa classe ocorrerá um erro pois não implementasmos os metodos que definimos em nossa interface, então agora vamos começar a deselvolve-los, então nossa classe deve ficar nesse estado.

 class DB implements DBInterface {

    const DBHOST = "localhost"; 
    const DBUSER = "root";
    const DBPASS = "";
    const DBNAME = "app";

    public $conn;
    public $sql;
    public $stmt;

    public function  __construct($dbName=DB::DBNAME,$dbUser=DB::DBUSER,$dbPass=DB::DBPASS,$dbHost=DB::DBHOST){
        $this->newConnection($dbName,$dbUser,$dbPass,$dbHost);
    }
    
    public function newConnection($dbName=DB::DBNAME,$dbUser=DB::DBUSER,$dbPass=DB::DBPASS,$dbHost=DB::DBHOST){
        try
        {
            $this->conn = new \PDO("mysql:dbname=".$dbName.";charset=utf8;host=".$dbHost, 
                $dbUser,
                $dbPass,
                array(\PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'")
            );
            
            $this->conn->setAttribute(
                \PDO::ATTR_ERRMODE,
                \PDO::ERRMODE_EXCEPTION
            );

            $this->conn->setAttribute(
                \PDO::ATTR_DEFAULT_FETCH_MODE,
                \PDO::FETCH_ASSOC
            );
        }
        catch ( PDOException $e)
        {   
            echo 'Não foi possível se conecytar com a base de dados';
            exit;
        }
    }
    
    public function setBindValues(array $args){

    }

    public function executeSql(String $sql, array $args, bool $isKeyValue){

    }

    public function setSqlInsert($table,$args,$isKeyValue){

    }

    public function insert(String $table, array $args,bool $isKeyValue){

    }
    
} 

setBindValues: no PDO temos a opção de fazer o bind dos parametros que passaremos para nossa sql em vez de concatenar diretamente em nossa sql, isso se dá por motivos de segurança assim podendo previnir contra sql inject, segue um exemplo abaixo fazendo bind de maneira bruta.

$this->stmt = $thi->conn->prepare("SELECT * FROM users WHERE USER_CODE = :A");
$stmt->bindValue(":A",1);

Como você pode ver não colocamos o valor diretamente na sql , assim no lugar do valor colocamos um apelido onde o valor será colocado , então passamos o nome do apelido (:A) e o valor , então o PDO gerencia automaticamente isso para nós.O objetivo de criar o metodo setBindValues é automatizar mais ainda está operação pois se você estivesse que passar 10 parametros para a sql então daria um trabalho fazer tudo isso na mão, então aproposta é passar um array com o pelido e valor e deixar o metodo fazer os resto do trabalho.

Nosso metodo setBindValues deverá ficar assim:

 public function setBindValues(array $args)
 {
     foreach ($args as $key => $value) {
        $this->stmt->bindValue(':'.$key, $value);
     }
 } 

executeSql : este metodo será os mais genérico de todos, pois ele poderá executar as ações de INSERT, SELECT, UPDATE e DELETE. Ele recebe como parametro nossa sql, o array de apelido e evalor que serão substituidos em nossa sql e por último um boolean que diz se é um array de chave evalor ou apenas de valor.

Nosso metodo executeSql deverá ficar assim:

  public function executeSql(String $sql, array $args, bool $isKeyValue){

        $this->sql=$sql;

        if(count($args)>0)
        {
            $this->stmt = $this->conn->prepare($this->sql);

            if($isKeyValue)
            {
                $this->setBindValues($args);
            }
            else
            {
                $this->stmt->execute($args);
            }
        }
        else
        {
            $this->stmt = $this->conn->query($this->sql);            
        }
    
        return $this->stmt;
    } 

setSqlInsert: este metodo fica resposável por montar nossa sql de inserção.

   public function setSqlInsert(String $table, array $args, bool $isKeyValue){
        if($isKeyValue)
        {
            $keys = array_keys($args);
            $this->sql= "INSERT INTO $table (".implode(',',$keys).")VALUES(:".implode(',:',$keys).");";
        }
        else
        {
            $this->sql= "INSERT INTO $table VALUES(?".str_repeat(',?',count($args)-1).")"; 
        }
        return $this->sql;
    } 

insert : nosso último metodo é responsável pelas inserções em nosso banco.

 public function insert(String $table, array $args,bool $isKeyValue){
  
        $this->setSqlInsert($table,$args,$isKeyValue);

        $this->stmt = $this->conn->prepare($this->sql);
 
        if($isKeyValue)
        {
            $this->setBindValues($args);  
        }
        else
        {
            $this->stmt->execute($args);
        }
 
        return $this->stmt;
    } 

Você deve estar se perguntando porque não utilizar apenas executeSql, pois este também faz a inserções, essa resposta vou dar no seguinte exemplo:

Insert utlizando o executeSql:

$db = new DB();
$stmt = $db->executeSql("INSERT INTO users(USER_NAME,USER_EMAIL)VALUES(:A,:B)",[
         "A"=>"Danilo Dos Santos Carreiro",
         "B"=>"danilocarsan@gmail.com"
        ]);

Insert utilizando o insert :

$db = new DB(); 
$stmt = $db->insert("users",[ 
         "A"=>"Danilo Dos Santos Carreiro",
         "B"=>"danilocarsan@gmail.com"
        ]);

Ufa, agora que terminamos de construir nossos metodos a nossa classe finalmente está pronta, lembrando que você está aberto para mudar e adicionar novas funcionalidades nesta classe, nossa classe final ficou assim.

 <?php

interface DBInterface {

    public function newConnection(String $dbName, String $dbUser, String $dbPass, String $dbHost);

    public function setBindValues(array $args);
    public function executeSql(String $sql, array $args, bool $isKeyValue);

    public function setSqlInsert(String $table, array $args,bool $ isKeyValue);
    public function insert(String $table, array $args,bool $isKeyValue);

}

class DB implements DBInterface {

    const DBHOST = "localhost"; 
    const DBUSER = "root";
    const DBPASS = "";
    const DBNAME = "app";

    public $conn;
    public $sql;
    public $stmt;

    public function  __construct($dbName=DB::DBNAME,$dbUser=DB::DBUSER,$dbPass=DB::DBPASS,$dbHost=DB::DBHOST){
        $this->newConnection($dbName,$dbUser,$dbPass,$dbHost);
    }
    
    public function newConnection($dbName=DB::DBNAME,$dbUser=DB::DBUSER,$dbPass=DB::DBPASS,$dbHost=DB::DBHOST){
        try
        {
            $this->conn = new \PDO("mysql:dbname=".$dbName.";charset=utf8;host=".$dbHost, 
                $dbUser,
                $dbPass,
                array(\PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'")
            );
            
            $this->conn->setAttribute(
                \PDO::ATTR_ERRMODE,
                \PDO::ERRMODE_EXCEPTION
            );

            $this->conn->setAttribute(
                \PDO::ATTR_DEFAULT_FETCH_MODE,
                \PDO::FETCH_ASSOC
            );
        }
        catch ( PDOException $e)
        {   
            echo 'Não foi possível se conecytar com a base de dados';
            exit;
        }
    }
    
    public function setBindValues(array $args){
  
        foreach ($args as $key => $value) {
             $this->stmt->bindValue(':'.$key, $value);
        }
        
    }

    public function executeSql(String $sql, array $args, bool $isKeyValue){

        $this->sql=$sql;

        if(count($args)>0)
        {
            $this->stmt = $this->conn->prepare($this->sql);

            if($isKeyValue)
            {
                $this->setBindValues($args);
    
            }
            else
            {
                $this->stmt->execute($args);
            }
        }
        else
        {
            $this->stmt = $this->conn->query($this->sql);            
        }
    
        return $this->stmt;
    }
    

    public function setSqlInsert(String $table, array $args, bool $isKeyValue){
        if($option)
        {
            $keys = array_keys($args);
            $this->sql= "INSERT INTO $table (".implode(',',$keys).")VALUES(:".implode(',:',$keys).");";
        }
        else
        {
            $this->sql= "INSERT INTO $table VALUES(?".str_repeat(',?',count($args)-1).")"; 
        }
        return $this->sql;
    }

    public function insert(String $table, array $args,bool $isKeyValue){
  
        $this->setSqlInsert($table,$args,$option);

        $this->stmt = $this->conn->prepare($this->sql);
 
        if($option)
        {
            $this->setBindValues($args);  
        }
        else
        {
            $this->stmt->execute($args);
        }
 
        return $this->stmt;
    }

} 

Relizando nossos teste

Criando uma instancia de nossa classse DB:

//conexão padrão definida na classe
$db = new DB();

// conexão diferente da definida na classe
$db = new DB("dbName","dbUser","dbPass","dbHost");

//mudar conexão
$db->newConnection("dbName","dbUser","dbPass","dbHost");

Utilizando o PDO de maneira bruta:

$stmt = $db->conn->prepare("SELECT * FROM users WHERE USER_CODE = :USER_CODE);
$stmt->bindValue(":USER_CODE",1);
$stmt->execute();
print_r($stmt->fetch());

Utilizando o metodo executeSql para fazer:

$stmt = $db->executeSql("SELECT * FROM users WHERE USER_CODE = :USER_CODE",[
            "USER_CODE"=>1
        ]);
$stmt->execute();
print_r($stmt->fetch());

Passando apenas valores

$stmt = $db->executeSql("SELECT * FROM users WHERE USER_CODE = ?",[
            1
        ],false);
print_r($stmt->fetch());

Utilizando o metodo insert:

$stmt = $db->insert("users",[
            "USER_NAME"=>"Danilo Santos Carreiro",
            "USER_EMAIL"=>"danilocarsan@gmail.com"
         ]);
$stmt->execute(); 
print_r($stmt->lastInsertId());

Passando apenas valores

$stmt = $db->insert("users",[
            "Danilo Santos Carreiro",
            "danilocarsan@gmail.com"
         ],false);
print_r($stmt->lastInsertId());

Em fim terminamos nossa classe, agora é só brincar com nossa classe, caso tenha, alguma duvída comenta aqui em baixo ou manda um email que ficarei feliz em responder, espero que tenha ajudado e se tiver algua ideia de um tutorial o abordagem de um tenha manda um email.

Link para baixar a classe deselvolvida é só clicar aqui.