garotosopa

Organizando classes de acesso ao banco de dados

Publicado em OOP, PHP por garotosopa em Janeiro 17, 2009

E então o camarada decide programar orientado a objetos. Coloca todas as funções procedurais dentro de uma classe e acha o máximo. Comete o erro que eu chamo de Programação Orientada a Classes.

Assunto batido demais na altura do campeonato, mas com a quantidade de vezes que tenho visto isso acontecer, melhor tentar esclarecer algumas coisas.

Ultimamente, o erro mais comum tem sido ao criar classes de acesso ao banco. Não entrarei no mérito de usar ou não um framework já existente ou da vantagem de ORM. Se o cara quer criar as classes, que crie. Mas crie corretamente.

A dificuldade está em perceber os objetos e criar métodos orientados a cada um deles. Ao lidar com banco de dados, o primeiro passo é identificar os três sujeitos principais:

  • a conexão com o banco
  • o resultado de uma consulta, ou rowset
  • e cada item desse resultado, ou row

Como assumem três papéis distintos, devem ser representados por três classes diferentes.

Surge então a dúvida se o rowset deve estender a conexão ou vice-versa. Esse pensamento é fruto direto do que o desenvolvedor acha que entendeu ao ver tantos extends por aí. Se não houvesse influência de achar que orientação a objeto significa objetos estendendo objetos, ficaria mais claro que as classes trabalham independentemente.

Estende-se uma classe para criar um subtipo dela. É perfeitamente válido que a classe Carro estenda a classe Automóvel, porque carro é um tipo de automóvel. Mas fisicamente não faz sentido que Carro estenda Motor; ele simplesmente usa um motor. Até porque, se fosse seguir essa linha de raciocínio, um carro teria que estender motor, porta, roda, volante…

A dúvida seguinte é de como interligar essas classes. A solução vem conforme identificamos o papel de cada uma delas.

A classe que representa a conexão precisa de um método para conectar no banco e outro para executar queries. Podemos assumir também que cada query tem um resultado. Sendo assim, a execução de queries deve retornar um objeto rowset.

O rowset por sua vez precisa de um resource no qual trabalhar. Logo, a conexão, que é quem vai criar este objeto, deve passar o resource no constructor, que nada mais é do que o retorno de mysql_query(), por exemplo. Uma vez construído, o rowset precisa minimamente de um método para contar a quantidade de rows retornados e outro para retornar cada row.

Seguindo este modelo, uma classe para conexão com o MySQL pode começar assim:

class Conexao_Mysql {
    private $dbh;
 
    public function connect($host, $user, $password) {
        $this->dbh = mysql_connect($host, $user, $password);
    }
 
    public function useDatabase($database) {
        mysql_select_db($database, $this->dbh);
    }
 
    public function query($sql) {
        $res = mysql_query($sql, $this->dbh);
 
        return new Rowset_Mysql( $res );
    }
}

Um ponto importante a observar é que o resultado de mysql_connect() é associado a uma propriedade do objeto para ser referenciado mais tarde. Isso garante que, caso existam outras conexões, as funções mysql_select_db e mysql_query sejam utilizadas na conexão correspondente ao objeto no qual foram chamadas.

O método query executa a SQL passada como argumento e monta um objeto para representar o rowset, passando o resource retornado pela mysql_query() para o constructor.

class Rowset_Mysql {
    private $res;
 
    public function __construct($res) {
        $this->res = $res;
    }
 
    public function count() {
        return mysql_num_rows($this->res);
    }
 
    public function fetchAssoc() {
        return mysql_fetch_assoc($this->res);
    }
}

No constructor, colocamos o resource em uma propriedade do objeto porque precisaremos dele nos métodos count() e fetchAssoc().

No fetchAssoc(), seria legal retornar um objeto próprio do tipo Row ao invés de retornar simplesmente um array. Isso ajuda principalmente se for necessário encapsular alguma lógica nos rows. Mas isso fica como dever de casa.

As consultas ficariam assim:

$conexao = new Conexao_Mysql();
$conexao->connect('localhost', 'nobody', 'nobody');
$conexao->useDatabase('lyriken');
 
$artistas = $conexao->query('SELECT * FROM artists');
 
while ($artista = $artistas->fetchAssoc()) {
    echo $artista['name'], " <br />\n";
}

A principal diferença desta implementação com o que alguns desenvolvedores têm implementado é que os métodos estão realmente orientados aos respectivos objetos, e não amontuados em uma única classe.

Agora, pra impressionar mais as gatinhas:

  1. Controle erros utilizando Exceptions
  2. Leia sobre interfaces e factory pattern para saber como abstrair o driver do código final
  3. Implemente a interface Iterator da SPL e utilize o rowset em um foreach
  4. Crie classes de acesso para não ter SQL espalhado nos scripts
  5. Finalmente, jogue tudo fora e use um framework com ORM :)

Espero que essa tentativa ajude a esclarecer também aqueles que estejam com dificuldade de desenhar seus objetos, mesmo que não sejam de acesso a banco.

2 Respostas

Subscreva aos comentários comRSS.

  1. parrudinho said, on Fevereiro 27, 2009 at 9:19 am

    Vlw pelo tuto

  2. Joel Wallis said, on Junho 2, 2009 at 5:03 pm

    Poxa, muito interessante o texto!!

    Eu já trabalhei escrevendo minhas próprias classes (hoje em dia, uso CodeIgniter), e na época que eu fazia tudo na unha, era mais ou menos assim. Claro que não era exatamente, mas vi que eu acertei algumas coisas, mesmo sem saber!

    Tá de parabéns!


Deixe uma resposta