+3

Criando uma API independente de Banco de Dados (Parte 1)

criado por Wellington Costa de Almeida em 07/02/2003 9:02pm
Discussão:

Passei exatamente 3 semanas para preparar esse artigo, tornando o mesmo bastante complexo. Os detalhes é que para a total compreensão do artigo eu fiz uma trabalho bem completo sendo assim mesmo ficou grande, então o dividi em varias partes. Contei com a ajuda de alguns amigos para me ajudar na correção ortográfica, pois não sou de costumes nacionais (Brasil), pelo fato de vir de filiações Libanesas. Me desculpo dos administradores que sofreram para corrigir os meus primeiros artigos.

Vamos direto ao assunto, nessa primeira parte do artigo estarei explicando o principal da API independente chegando até o módulo do MySQL. Nas próximas partes do artigo estarei apontando módulos de vários tipos de bancos de dados.

Introdução:

Um dos recursos mais poderosos do PHP é a sua capacidade de se conectar a um banco de dados, seja MySQL ou Oracle. Essa é a razão pela qual a maioria das pessoas usa o PHP: para pegar dados num banco de dados e trazê-los para a web.

No PHP, os programadores tradicionalmente fazem chamadas específicas de banco de dados para se conectar a um servidor diferente. Por exemplo, você usaria o mysql_connect() para conectar a um banco de dados MySQL, ao passo que você teria de usar a função OCILogon() para conectar à um banco de dados Oracle.

Usar diferentes funções para se conectar a diferentes banco de dados era muito lento para muitos programadores, especialmente aqueles que tinham o Perl e que estavam acostumados com a DBI do Perl, a qual permite a portabilidade ao mudar de banco de dados. Para exemplificar, examine os dois pedaços de código a seguir. Um deles usa a API do PHP para conectar ao MySQL e outro, a DBI do Perl para se conectar ao MySQL:

Versão em PHP:
<?php
$dbh = mysql_connect($host, $user, $password);
mysql_select_db($dbname);
$stmt = mysql_query($stmt, $dbh);
while ($row = mysql_fetch_array($sth, MYSQL_ASSOC)) {
    echo $row[“FirstName”];
    echo $row[“lastName”];
}
mysql_free_result($sth);
mysql_close($dbh);
?>

Perl com DBI:
use DBI;

my $dbh = DBI->connect(“dbi:mysql:$dbname”, $user, $password);
my $sth = $dbh->do(“SELECT * FROM table”);

while (my $row = $sth.>fetchrow_hashref)) {
    print $row->{firstName};
    print $row->{lastName};
}
$sth->finish;
$dbh->disconnect;

Como você pode ver, eles têm aproximadamente as mesma complexidade, mas o exemplo de Perl é mais portável. No PHP, se você quisesse mudar o banco de dados de MySQL para Oracle, teria de usar um conjunto inteiramente diferente de funções. Entretanto, no exemplo do Perl, somente uma linha precisa ser alterada:

$dbh = DBI->connect(“dbi:oracle:$dbname”, $user, $pass);

A DBI do Perl usa um método orientado a objetos para criar uma API independente de banco de dados, mas essa não é a única maneira de conseguir essa independência. Na verdade, nós não vamos usar o método orientado a objetos:

“Por que não usar o método orientado a objetos? Ele funciona tão bem no Perl, por que não no PHP?” Primeira razão: nas palavras Zeev Suraski, um dos projetistas do PHP, “o PHP não é uma linguagem OO.” Isso não significa que o PHP não tenha recursos orientados a objetos – na verdade, ele tem a maioria das principais construções orientadas em objetos, mas isso não significa que a solução OO não será tão rápida ou elegante.

Quais são as outras opções? Há realmente uma escolha viável, que é o método orientado a funções para criar uma API independente de banco de dados com PHP. No método orientado a funções, nós temos um conjunto de funções de “embalagens” genéricas, que são substituídas pelas chamadas de funções específicas de banco de dados. Por exemplo, em vez de mysql_connect(), temos o db_connect().

Considere o seguinte exemplo de conexão à um banco de dados e busca de uma linha usando a API orientada a função.

<?php
include_once("DB/mysql.php");

$dbh = db_connect(array($host, $user, $pass));
if (!$dbh) {
    die("Não conectado ao DATABASE");
}
db_selectdb(array("sampleDB"));
$sth = db_query("SELECT * FROM sampleTable", $dbh);
if (!sth) {
    die("Query não execultado");
}
while ($row = db_fetch_row(array($sth))) {
    echo $row["firstname"];
    echo $row["lastname"];
}
db_free_result(array($sth));
db_close(array($dbh));
?>

É isso – Sem interfaces complexas. Na parte superior do script, você pode incluir o arquivo que contém as funções de embalagem, e usar essas funções gerais em vez das funções específicas de banco de dados.

Neste artigo nós discutiremos como implementar essa API independente de banco de dados usando o PHP.

A Cola:

Se estivéssemos usando o método orientado a objetos para a independência de banco de dados com o PHP, precisaríamos de vários códigos para juntar tudo. Entretanto, com a interface orientada a funções, o código torna-se mais fácil. Inclua apenas o arquivo de cabeçalho correto e você está pronto. Por exemplo, para usar um banco de dados MySQL a linha include seria include_once(“DB/mysql.php”), e para incluir e trabalhar com um banco de dados Oracle, a linha seria include_once(“DB/oracle.php”).

Note também que todas as chamadas de funções aceitarão somente um parâmetro – uma array que contém os paramentos reais. Isso é usado de modo que a função pegue um numero opcional de argumentos, casso a chamada de funções de um banco de dados já exija mais argumentos do que uma chamada de função para outro banco de dados. (Isso também é feito dessa maneira para a compatibilidade cm o PHP3; no PHP4, você pode usar a combinação de func_num_args(), func_get_arg() e func_get_args() para alcançar o mesmo efeito.)

Uma coisa que é interessante, e não tão difícil de implementar, é um conjunto padrão de funções freqüentemente necessárias. Podem ser funções API (como db_fetchall() no código seguinte) ou funções internas (como db_simulate_prepare(), também no código seguinte). Colocaremos essas funções em um arquivo chamando DB/standard.php e depois incluiremos em todos o módulos diferentes para que fiquem disponíveis aos usuários.

conteúdo de DB/standard.php:
<?php
function &db_fetchall(args=array())
{
    $rows = array();
    while ($row = db_fetchrow($args[0], DB_GETMODE_NUM)) {
        array_push($rows, &$row);
    }
    return($rows);
}

function &db_fetchall_assoc($args=array())
{
    $rows = array();
    while ($row = db_fetchrow($args[0], DB_GETMODE_ASSOC)) {
        array_push($rows, &$row);
    }
    return($rows);
}

function db_simulate_execulte($args=array())
{
    array_shift($args);
}

function db_simulate_prepare($args=array())
{
    $stmt      = array_shift($args);
    $prepArgs  = array_shift($args);
    if (!is_array($prepArgs)) {
        return($stmt);
    }
    $parts = explode('?', $stmt);
    foreach ($parts as $part) {
        $new_stmt .= $part . array_shift($args);
    }

    return($new_stmt);
}
?>

As funções db_fetchall() e db_fetchall_assoc() usam a API independente de banco de dados criada para implementar as funções necessária úteis na manipulação de conjuntos de resultados usando o PHP. As funções constróem uma array de todas as linhas no conjunto resultante (dado pelo primeiro argumento). Essas funções podem ser usadas da seguinte maneira:

<?php
include_once("DB/mysql.php");

$dbh = db_connect(array("localhost", "username", "password"));
if (!$dbh) {
    die (sprintf("An erro occurred, %s: %s", db_erro(), db_erro()));
}
$row = db_fetchall_assoc($sth);
foreach ($rows as $row) {
    print $row['firstname'];
    print $row['lastname'];
    print $row['ocupation'];
}
db_free_result ($sth);
db_close ($dbh);
?>

As funções db_simulate_prepare() e db_simulate_execute() simularão as funções de preparo e execução de banco de dados onde esses recursos não são suportados. O método usado é muito fácil. A função db_simulate_prepare() é simplesmente um boneco; ela pega a consulta que precisa ser preparada e retorna ao usuário.

A função db_simulate_execute() é um pouco mais avançada. Primeiro, ela divide as strings com o delimitador “?” (usando a função explode()), e depois concatena a string apropriada de array do usuário. A função db_simulate_execute() então retorna a consulta que deveria ser executada.

O Módulo MySQL

Agóra que nós já temos uma idéia básica do conceito que está por trás da nossa API independente de banco de dados, é hora de implementar as funções de embalagem para os banco de dados diferentes. Devido à sua velocidade e simplicidade, o MySQL é um complemento ideal para o PHP. É por isso que, em relação ao PHP 4, o suporte à MySQL acompanha o PHP "de fábrica". Mais informações sobre o MySQL podem ser encontradas em http://www.mysql.com/ .

<?php
include_once("DB/standard.php");

function db_connect($args=array())
{
    switch (count($args)) {
        case 0:
            return @mysql_connect();
        case 1:
            return @mysql_connect($args[0]);
        case 2:
            return @mysql_connect($args[0], $args[1);
        default:
            return @mysql_connec($args[0], $args[1], $args[2]);
    }
}

function db_pconnect($args=array())
{
    switch (count($args)) {
        case 0:
            return @mysql_pconnect();
        case 1:
            return @mysql_pconnect($args[0]);
        case 2:
            return @mysql_pconnect($args[0], $args[1]);
        default:
            return @mysql_pconnect($args[0], $args[1], $args[2]);
    }
}

function db_select_db($args=array())
{
    if (isset($args[1])) {
        return @mysql_select_db($args[0], $args[1]);
    }
    return @mysql_query($args[0]);
}

function db_close($args=array())
{
    return @mysql_close($args[0]);
}

function db_query($args=array())
{
    if (isset($args[1])) {
        return @mysql_query($args[0], $args[1]);
    }
    return @mysql_query($args[0]);
}

function db_db_query($args=array())
{
    if (isset($args[2])) {
        return @mysql_db_qery($args[0], $args[1], $args[2]);
    }
    return @mysql_db_query($args[0], $args[1]);
}

function db_prepare($args=array
{
    return db_simulate_prepare($args);
}

function db_execute($args=array())
{
    $stmt = db_simulate_execute(&$args);
    return db_query(array($stmt, array_shift ($args)));
}

function db_fetchrow($args=array())
{
    if ($args[1] == DB_GET_MODE_ASSOC) {
        @mysql_fetch_array($args[0], MYSQL_ASSOC);
    } elseif ($args[1] = = DB_GETMODE_REG) {
        return @mysql_fetch_array($args[0], MYSQL_NUM);
    }
    return @mysql_fetch_array($args[1]);
}

function db_num_rows($args=array())
{
    return @mysql_num_row($args[0]);
}

function db_commit($args=array())
{
    return(true);
}

function db_rollback($args=array())
{
    return(false);
}

function db_autoCommit($args=array())
{
    return(true);
}

function db_free_result($args=array())
{
    return @mysql_free_result($args[0]);
}

function db_errno($args=array())
{
    return @mysql_errno();
}

function db_error($args=array())
{
    return @mysql_error();
}
?>

Explicação:

É muito fácil criar embalagens para a API do MySQL, porque como os módulos do mSQL e do MSSQL, as funções aderem ao padrão. Ele tem a maioria das funções que os outros banco de dados possuem não têm muitas funções da funções criticas que os ouro bancos de dados não têm.

Para os argumentos que podem pegar números variáveis d outro como (como mysql_connect()), nos usamos um loop switch..case par determinar quantos argumentos passarão para a função. Algo a ser notado é que para o numero máximo de argumentos, nós não usamos numero de argumentos, mas a instrução padrão. Isso assegura que, se nós estivermos movimentando d um banco de dados que requer um numero maior que argumentos para um banco de dados que solicita menos argumentos, o banco de dados que exige menos argumento ainda obterá os argumentos de que necessita.

É isso ai, nesta primeira parte do nosso artigo, cheguei até o módulo do MySQL, na próxima parte entraremos em mais módulos de banco de dados como. MSSQL, mSQL, Oracle e PostgreSQL e na última versão deste artigo estaremos entrando em módulos do InterBase e Sybase.

Valeu galera, espero que gostem!

Comentários:

Mostrando 1 - 10 de 16 comentários
Ficou muito bom mesmo. É uma abordagem interessante criar uma layer de abstração de dados orientada por funções. Algo parecido (mas orientado a objetos) podemos encontrar no framework PEAR. Este tem sido um repositório de classes onde, inclusive, qualquer um pode submeter sua classe a este repositório.

Mais uma vez, parabéns pelo artigo!
24/02/2003 6:47am (~21 anos atrás)

Brigado, espero que sejá bastante util pra você este artigo... As demais partes do mesmo, já estão em-caminhados para a PHPBrasil, apenas aguarte as segunda parte do arigo onde estou abordando o mSQL e Oracle e na terceira e ultima parte os demais banco de dados...

Obrigado!!!
18/02/2003 8:56am (~21 anos atrás)

Wellington Costa de Almeida, parabéns pelo artigo e estou esperando as demais partes para poder colocar em prática, pois nos meus trabalhos sempre uso base de dados interbase.
18/02/2003 8:13am (~21 anos atrás)

Ainda não li todo o artigo, vou deixar para ler com tempo no final de semana, não sei o que ainda vou encontrar, mas até aqui ja posso dizer que está muito bom.

Quanto ao que foi comentado por um de nossos colaboradores, sobre a não padronização do SQL, gostaria de sugerir a criação de uma base de dados repositoria, onde cada coluna fosse equivalente a um banco de dados e que de acordo com a base a se utilizar os scripts seriam direcionados para a string do seu banco. É um pouco confuso, mas acho que vale amadurecer a ideia.

Meus parabens pelo artigo!
17/02/2003 3:35pm (~21 anos atrás)

Esse é um outro problema grave... O Pessoal pensa em estár no status do Site, e esquece da qualidade dos seus artigos... Bom confésso pra vocês, no inicio eu pensava somente nisso... em criar artigos pensando em ter STATUS... mas ví que o melhor STATUS que existe é escrever alguma coisa boa, onde todos gostem e que tenha visitas.... :)
11/02/2003 3:41pm (~21 anos atrás)

Ou a maior parte do tempo o pessoal fica fissurado em pontos no site.Esquece a qualidade do artigo .....
11/02/2003 9:45am (~21 anos atrás)

Bom.. eu não tenho muito esse problema... pois sou + ou - um Free Lancer que trabalho pro governo do Paraná... e ganho um exelente valor por HORA!!
10/02/2003 4:34pm (~21 anos atrás)

Bom, ando trabalhando em média 13 horas por dia. E no fim de semana totalmente livre, sem nem ver pc.

13 horas trabalho
3 horas comendo
sobram 8, que uso vendo tv, lendo revista, dormindo, vendo a namorada, saindo.. e por aí vai.. hehe...

eu pelo menos ainda não achei muito tempo na minha vida pra escrever.. a não ser besteiras.. (:

[]s
Leonardo
vyper@maneh.org
maneh.org
10/02/2003 2:49pm (~21 anos atrás)

Kra... eu tenho a minha vida numa boa, dentro e fora do PC, consigo aconselhar tudo normalmente. Relacionamentos, trabalho entre outros... Mas realmente tempo é um problema sério...

Nestá questão não depende somente da Boa vontade dos usuários... Depende muito da disponibilidade...

Mas escrever um pouco é sempre bom... :)

[]'s
10/02/2003 8:26am (~21 anos atrás)

Nem sempre o pessoal tem o tempo ou a paciência necessária. Às vezes o serviço está muito apurado, ou o pessoal chega em casa e quer curtir a namorada, ou a esposa.

Para alguns, a vida existe fora o pc.. heoahoea

Mas é legal quando alguém dedica um pouco do seu tempo para ensinar, ajudar e aprender. Pois, sempre aprendemos ensinando.

Muito legal o artigo.

[]s
Leonardo
vyper@maneh.org
maneh.org
10/02/2003 7:34am (~21 anos atrás)

Novo Comentário:

(Você pode usar tags como <b>, <i> ou <code>. URLs serão convertidas para links automaticamente.)