PHP::Persistência de dados com Design Pattern Table Data Gateway

php_logo2

Nível intermediário

 

  • Neste artigo apresento mais um pouco sobre a utilização de banco de dados no PHP orientado a objetos, após concluir a leitura você terá uma boa visão sobre o Design Pattern Table Data Gateway. Se você já leu um artigo meu anterior sobre CRUD e DAO, notará muita semelhança.

Significa fazer os dados continuarem a existirem independente do sistema aonde estão funcionando, mesmo quando terminar a execução do sistema, estes dados precisam continuar existindo em outro ambiente externo. Então entram agora em cena os bancos de dados relacionais, que são o tipo mais utilizado. Entretanto, o conceito de banco de dados relacional é bem diferente de um conceito do paradigma de orientação a objetos, onde contamos com atributos e métodos. Bancos de dados trabalham apenas com armazenamento de dados em tabelas estratégias de destes dados. Mas os objetos se relacionam apenas em tempo de execução através de seus métodos. Visto que são tão diferentes, mas não podem “viver separados”, precisamos criar um meio que interpreta a comunicação entre os dois. Esse “mediador” funciona através de uma técnica chamada mapeamento objeto-relacional ou ORM. Veja mais detalhes (https://pt.wikipedia.org/wiki/Mapeamento_objeto-relacional)

Sabemos que o grande sucesso dos bancos de dados relacionais se deve a linguagem padronizada SQL utilizada em todos os sistemas de bancos de dados relacionais. Mas acontece que esse mesmo recurso poderoso ainda têm seus usos equivocados por programadores, que acaba por criar outros problemas como baixo desempenho do sistema e difícil manutenção. Por isso precisamos isolar todos os comandos utilizados da linguagem SQL em uma camada separada que poderá até ser designada a alguém mais especializado na linguagem. Para isso vamos ver um padrão de projeto muito usual que é conhecido como Table Data Gateway.

Esse padrão Table Data Gateway provê um meio mais simplificado de manipular uma tabela no banco de dados. O brilho disso está na forma de utilizá-la, porque os detalhes ficam todos escondidos atrás de uma interface clara. Isso é fantástico quando você imagina que não encontrará mais nenhuma declaração SQL espalhada pelo seu código. Aliás, quando você criou a classe e efetuou seus testes, aprovando o funcionamento da classe, dificilmente enfrentará problemas SQL com as operações envolvidas através do objeto gerado pela classe. Neste padrão você conta com as quatro operações básicas do CRUD: Inserir (Create), buscar (Retrieve), alterar (Update), excluir (Delete).

O desenho funciona muito semelhante a outro artigo que publiquei sobre DAO, e podemos dizer que os conceitos são quase os mesmos. Entretanto, seus usos dependem das necessidades especificadas pelos requisitos do seu projeto. Neste Design Pattern, você também precisa definir uma classe “ponte” com cada tabela utilizada no sistema, mas, além disso, precisa definir uma classe domínio, que dependerá desta classe gateway. Será através do objeto de domínio que seu sistema se comunicará com a tabela do banco. Vamos criar um exemplo simples para um cadastro de cliente, conforme o seguinte desenho:

Diagrama Table Data Gateway Clientes
Diagrama Table Data Gateway Clientes

Para criar a tabela para utilizar neste exemplo, segue a declaração SQL:

create table clientes (
   id int unsigned not null primary key,
   primeiro_nome varchar(25) not null,
   ultimo_nome varchar(25) not null,
   email varchar(60) default null,
   telefone varchar(16) default null,
   data_cadastro datetime not null,
   data_atualizacao datetime not null
);

Teremos duas classes, a primeira clientesGateway.php, define o nosso objeto Table Data Gateway:

 	
class ClientesGateway
{
    /**
     *
     * @var PDO $instanceDb
     */
    private static $instanceDb;
    /**
     *
     * @var string $tableDb
     */
    private $tableDb;
    /**
     *
     * @var array
     */
    private $tableColumns;
    /**
     *
     */
    public function __construct()
    {
        $this->tableDb = ‘clientes’;
        $this->tableColumns = array(“id” => \PDO::PARAM_INT,
            “primeiro_nome” => \PDO::PARAM_STR,
            “ultimo_nome” => \PDO::PARAM_STR,
            “email” => \PDO::PARAM_STR,
            “telefone” => \PDO::PARAM_STR,
            “data_cadastro” => \PDO::PARAM_STR,
            “data_atualizacao” => \PDO::PARAM_STR);
    }
    /**
     *
     * @param \PDO $instanceDb
     * @return void
     */
    public static function setInstanceDb(\PDO $instanceDb)
    {
        self::$instanceDb = $instanceDb;
    }
    /**
     *
     * @param integer $id
     * @param string $class
     * @return object
     */
    public function retrieveById(int $id, string $class = ‘stdclass’)
    {
        $sqlStmt = “SELECT * FROM {$this->tableDb} WHERE id = ‘$id'”;
        print “<br />”.date(‘d-m-Y H:i:s’).” :: $sqlStmt<br>\n”;
        $result = self::$instanceDb->query($sqlStmt);
        return $result->fetchObject($class);
    }
    /**
     *
     * @param string $filter
     * @param string $class
     * @return array
     */
    public function retrieveAll(string $filter, string $class = ‘stdclass’)
    {
        $sqlStmt = “SELECT * FROM {$this->tableDb} “;
        if ($filter) {
            $sqlStmt .= “WHERE $filter”;
        }
        print “<br />”.date(‘d-m-Y H:i:s’).” :: $sqlStmt<br>\n”;
        $result = self::$instanceDb->query($sqlStmt);
        return $result->fetchAll(PDO::FETCH_CLASS, $class);
    }
    /**
     *
     * @param interger $id
     * @return boolean
     */
    public function delete(int $id)
    {
        $sqlStmt = “DELETE FROM {$this->tableDb} WHERE id = ‘$id'”;
        print “<br />”.date(‘d-m-Y H:i:s’).” :: $sqlStmt<br />”;
        return self::$instanceDb->query($sqlStmt);
    }
    /**
     *
     * @param object $object
     * @return boolean
     */
    public function persist($object)
    {
        if (is_object($object)) {
            $date = date(“Y-m-d H:i:s”);
            // Se não possuir um id
            if (empty($object->id)) {
                $object->data_cadastro    = $date;
                $object->data_atualizacao = $date;
                $object->id               = $this->getLastId() + 1;
                $sqlStmt_1   = “INSERT INTO {$this->tableDb} (“;
                $sqlStmt_2   = ” VALUES (“;
                $sqlStmt_log = $sqlStmt_2;
                $count = 1;
                foreach ($this->tableColumns as $columnName => $pdoParam) {
                    $sqlStmt_1 .= $columnName;
                    $sqlStmt_2 .= “:{$columnName}”;
                    $sqlStmt_log .= “‘{$object->$columnName}'”;
                    if ($count < count($this->tableColumns)) {
                        $sqlStmt_1 .= “, “;
                        $sqlStmt_2 .= “, “;
                        $sqlStmt_log .=”, “;
                    }
                    $count++;
                }
                unset($count);
                $sqlStmt     = $sqlStmt_1.”)”.$sqlStmt_2.”)”;
                $sqlStmt_log = $sqlStmt_1.”)”.$sqlStmt_log.”)”;
            }
            // Se já possuir um id
            else {
                $object->data_atualizacao = $date;
                $sqlStmt     = “UPDATE {$this->tableDb} SET “;
                $sqlStmt_log = “UPDATE {$this->tableDb} SET “;
                $count = 2;
                foreach ($this->tableColumns as $columnName => $pdoParam) {
                    if ($columnName != “id”) {
                        $sqlStmt .= “{$columnName}=:{$columnName}”;
                        $sqlStmt_log .= “{$columnName}='{$object->$columnName}'”;
                        if ($count < count($this->tableColumns)) {
                            $sqlStmt .= “, “;
                            $sqlStmt_log .= “, “;
                        }
                        $count++;
                    }
                }
                unset($count);
                $sqlStmt .= ” WHERE id = :id”;
                $sqlStmt_log .= ” WHERE id = ‘$object->id'”;
            }
            $process = self::$instanceDb->prepare($sqlStmt);
            // Build param
            foreach ($this->tableColumns as $columnName => $pdoParam) {
                if (!$process->bindParam(“:{$columnName}”, $object->$columnName, $pdoParam)) {
                    throw new Exception(“Falha na colagem dos valores na declaração sql”);
                }
            }
            print “<br />”.date(‘d-m-Y H:i:s’).” :: $sqlStmt_log<br>\n”;
            // Return boolean
            return $process->execute();
        }
    }
    /**
     *
     * @return int
     */
    protected function getLastId()
    {
        $sqlStmt = “SELECT MAX(id) AS LAST FROM {$this->tableDb}”;
        print “<br />”.date(‘d-m-Y H:i:s’).” :: $sqlStmt<br>\n”;
        $result = self::$instanceDb->query($sqlStmt);
        $data = $result->fetch(\PDO::FETCH_OBJ);
        return (int) $data->LAST;
    }
}

Em seguida temos a nossa classe que define o objeto de domínio a cliente.php:


class Cliente
{
    private $data;
    /**
     *
     * @param string $prop
     * @return mixed
     */
    public function __get($prop)
    {
        return $this->data[$prop];
    }
    /**
     *
     * @param string $prop
     * @param mixed $value
     */
    public function __set($prop, $value)
    {
        $this->data[$prop] = $value;
    }
    /**
     *
     * @param \PDO $instanceDb
     * @return void
     */
    public static function setInstanceDb(\PDO $instanceDb)
    {
        self::$instanceDb = $instanceDb;
    }
    /**
     *
     * @param integer $id
     * @return object
     */
    public static function retrieveById(int $id)
    {
        $gateway = new ClientesGateway;
        return $gateway->retrieveById($id, ‘Cliente’);
    }
    /**
     *
     * @param string $filter
     * @return array
     */
    public static function retrieveAll(string $filter = ”)
    {
        $gateway = new ClientesGateway;
        return $gateway->retrieveAll($filter, ‘Cliente’);
    }
    /**
     *
     * @return bool
     */
    public function delete()
    {
        $gateway = new ClientesGateway;
        return $gateway->delete($this->id);
    }
    /**
     *
     * @return bool
     */
    public function persist()
    {
        $gateway = new ClientesGateway;
        return $gateway->persist((object) $this->data);
    }
    /**
     *
     * @return string
     */
    public function getFullName()
    {
        return “Nome….: {$this->primeiro_nome} {$this->ultimo_nome}”;
    }
    /**
     *
     * @return string
     */
    public function getContact()
    {
        return “Contato..: {$this->email} <br />\n Telefone.:{$this->telefone}”;
    }
    public function getDates()
    {
        $gateway = new ClientesGateway;
        $objTmp = $gateway->retrieveById($this->id);
        $this->data_cadastro = $objTmp->data_cadastro;
        $this->data_atualizacao = $objTmp->data_atualizacao;
        unset ($objTmp);
        return “Criado em..: {$this->data_cadastro} – Atualizado em.:{$this->data_atualizacao}”;
    }
}

Você deve ter percebido no sua leitura destas classes que elas precisam de uma instância de conexão, então como brinde deixo um código bem funcional segundo o Desing Pattern Singleton, a nossa classe PdoConnection.php:


class PdoConnection
{
    /**
     *
     * @var pdo $instancia
     */
    private static $instance;
    // Impedir instanciação
    private function __construct()
    {
    }
    // Impedir clonagem
    private function __clone()
    {
    }
    // Impedir utilização do Unserialize
    private function __wakeup()
    {
    }
    /**
     *
     * @return pdo object as active instance
     */
    public function getInstance()
    {
        if (!isset(self::$instance)) {
            try {
                $dbName  = “<<Nome do banco de dados que você está usando>>“;
                $server = “localhost”;
                $dsn    = “mysql:host={$server};dbname={$dbName};”;
                $user   = “root”;
                $pass   = “<<Senha do seu servidor>>“;
                // Instânciando um novo objeto do tipo PDO informando o DSN e parâmetros de array
                self::$instance = new \PDO($dsn, $user, $pass);
                // Gerando uma excessão do tipo PDOException com o código de erro
                self::$instance->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
                return self::$instance;
            } catch (\PDOException $e) {
                return $e->getMessage();
                // encerra o aplicativo
                exit();
            }
        } else {
            return self::$instance;
        }
    }
}

Agora note uma explicação básica sobre os códigos das classes Table Data Gateway:

Table Data Gateway - Classe de Gateway

Agora a referente ao objeto de domínio:

Table Data Gateway - Domínio

Muito bem, mas como fazemos então uso destas classes? Vamos criar mais um arquivo, onde primeiramente importamos as classes necessárias, que criamos anteriormente. Em seguida, para termos um mínimo de tratamento de erros, vamos criar o código da aplicação dentro de um bloco try e catch. O código está comentado então você não encontrará dificuldades para compreendê-lo.


require_once ‘class/ClientesGateway.php’;
require_once ‘class/Cliente.php’;
require_once ‘class/PdoConnection.php’;

try {
    // Obtendo instância do banco de dados e mantendo salvo a nível de classe
    ClientesGateway::setInstanceDb(PdoConnection::getInstance());
    
    // Criando novo objeto Cliente
    $cliente1 = new Cliente;
    $cliente1->primeiro_nome = “Alexandre”;
    $cliente1->ultimo_nome   = “Barbosa”;
    $cliente1->email         = “alxbbarbosa@yahoo.com.br”;
    $cliente1->telefone      = “99-99999-9999”;
    $cliente1->persist(); // Persiste objeto no banco de dados

    // Criando novo objeto Cliente
    $cliente2                = new Cliente;
    $cliente2->primeiro_nome = “João”;
    $cliente2->ultimo_nome   = “Silva”;
    $cliente2->email         = “jsilva@teste.com.br”;
    $cliente2->telefone      = “99-99999-9999”;
    $cliente2->persist(); // Persiste objeto no banco de dados

     // Criando novo objeto Cliente por recuperação de registro
     $cliente3 = Cliente::retrieveById(1);
     print “<br />” . $cliente3->getFullName();
     print “<br />” . $cliente3->getContact();
     print “<br />”;

      // Atualizando o objeto Cliente3
      $cliente3->ultimo_nome = “Silva”;

      // Pausa para verificar atualização de cadastro
      sleep(3);
      $cliente3->persist(); // Persiste objeto no banco de dados
      print “<br />” . $cliente3->getFullName();
      print “<br />” . $cliente3->getDates();

      // Recuperando todos objetos clientes
      foreach (Cliente::retrieveAll() as $cliente){
          // Chamando método para exclusão
          $cliente->delete();
      }
} catch (Exception $e) {
      print $e->getMessage();
}

Acredito que se você acompanhou até provavelmente terá uma boa visão deste Pattern. Um CRUD é criado em cima de um padrão semelhante a este. Se você compreender o funcionamento deste pattern e caso já tenha lido meu artigo sobre CRUD com DAO no PHP, já poderá criar um sistema CRUD para qualquer tabela que desejar. Não existe padrão superior ou melhor que outro, tudo depende do projeto que você está desenvolvendo, dos requisitos deste.

Entre os livros que recomendo para leitura referente a este tema e que, inclusive influenciou este artigo, é o PHP – Programando com Orientação a Objetos tereceira edição de Pablo Dall’Oglio produzido e distribuído pela editora Novatec.

[]’s

3 comentários

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.