Conhecendo o PDO do PHP

Artigo que apresenta detalhes sobre o PDO - PHP Data Objects, que representa uma abstração de conexões com bancos de dados. Também ensina como usar o PDO do PHP.

O que é PDO?

PDO (PHP Data Objects) é um módulo de PHP montado sob o paradigma Orientado a Objetos e cujo objetivo é prover uma padronização da forma com que PHP se comunica com um banco de dados relacional. Este módulo surgiu a partir da versão 5 de PHP. PDO, portanto, é uma interface que define um conjunto de classes e a assinatura dos métodos de comunicação com uma base de dados.

Cada sistema gerenciador de bancos de dados (SGBD) pode prover um driver para PDO. Apesar de PDO definir a assinatura dos métodos que cada driver deve implementar, alguns recursos podem não ser suportados. Logo, a chamada a um método pode ser "inútil" dependendo do driver utilizado ou da versão/modelo do servidor de Banco de Dados. Por exemplo, algumas engines do SGBD MySQL não dão suporte a transações, logo, o método "beginTransaction" (responsável por iniciar uma transação) simplesmente não terá efeito sob tais condições.

Ao contrário do que algumas pessoas pensam, PDO não é uma camada de abstração de SQL. Cada SGBD relacional possui uma sintaxe própria para construção de SQL. Embora muitos deles se aproximem da especificação da SQL-92, algumas diferenças existem. Portanto, usar PDO não significa que seu sistema será portável entre diferentes SGBDs. Significa apenas que você se comunicará com uma base de dados através de um conjunto determinado de métodos e classes.

Observação: Criar uma camada da aplicação para abstração de SQL é extremamente complexo. Várias variáveis devem ser levadas em conta e, possivelmente, é preciso abrir mão de muitos recuros específicos. Portanto, existem duas linhas de desenvolvimento: uma defende a utilização de um único tipo de SGBD por aplicação, para que os recursos específicos sejam utilizados ao máximo e de forma otimizada, enquanto outra defende a generalização e o suporte a diferentes bases de dados, tornando o sistema mais portável. Tudo depende do objetivo da aplicação.


Por que usar PDO?

Antes da chegada de PDO, a linguagem PHP oferecia suporte à comunicação com diferentes modelos de SGBD através de módulos específicos. A maioria deles provia uma biblioteca de funções e utilizava um resource para representar a conexão e outro para representar um resultset (o resultado de uma consulta). As operações eram feitas sobre as variáveis de resource.

Cada driver implementava suas operações conforme imaginavam ser mais adequados. Embora alguns deles tivessem um funcionamento semelhante, a ordem dos parâmetros nem sempre era a mesma e podia causar uma certa confusão entre programadores.

Quem já trabalhou com a biblioteca de funções de MySQL ou PostgreSQL, deve conhecer este funcionamento:

// MySQL
$c = mysql_connect('host', 'usuario', 'senha');
mysql_select_db('bd', $c);
mysql_set_charset('UTF8', $c);

$resultado = mysql_query('SELECT nome FROM usuarios', $c);
while ($obj = mysql_fetch_object($resultado)) {
    echo $obj->nome;
}
mysql_free_result($resultado);
mysql_close($c);

// PostgreSQL
$c = pg_connect('host=host port=5432 dbname=bd user=usuario password=senha');
pg_set_client_encoding($c, 'UNICODE');

$resultado = pg_query($c, 'SELECT nome FROM usuarios');
while ($obj = pg_fetch_object($resultado)) {
    echo $obj->nome;
}
pg_free_result($resultado);
pg_close($c);

Note que a forma de conexão é feita através de estratégias diferentes: MySQL passava os dados de conexão através de parâmetros, já o PostgreSQL utilizava uma string de conexão (com vários dados), que é uma alternativa mais extensível. Observe, ainda, que MySQL costuma receber o resource de conexão como último parâmetro de suas funções (já que é opcional), enquanto o PostgreSQL costuma receber o resource como primeiro parâmetro.

PDO juntou o que havia de melhor em cada driver e gerou uma especificação. Embora a especificação não trabalhe com resource explicitamente, ela define duas classes com significados semelhantes: PDO (que representa uma conexão) e PDOStatement (que representa uma consulta/resultado). Além destas, existe a classe PDOException, que é disparada por alguns métodos para que seja realizado o tratamento de exceções.

Utilizar PDO tende a ser mais simples do que utilizar biblioteca de funções, mas continua exigindo conhecimento da sintaxe SQL específica do modelo de SGBD envolvido. Embora muitos programadores ainda utilizam as funções de conexão, existe a promessa de que PDO será o padrão de conexão em PHP 6, enquanto as bibliotecas de funções passarão a ser extensões PECL, precisando ser instaladas a parte.


Como usar o PDO?

Para utilizar o PDO, primeiro é instanciado um objeto da classe PDO, que representa a conexão com um banco. No construtor da classe, deve ser informado o chamado "DSN", que é uma string de conexão semelhante àquela vista anteriormente na função de conexão com PostgreSQL. Cada driver PDO especifica uma forma de como é montado o DSN para o SGBD correspondente. Além do DSN, também é informado, por parâmetro, o usuário, a senha de acesso e as opções adicionais.

// Exemplo de conexao com MySQL via PDO
$dsn = 'mysql:host=host;port=3306;dbname=bd';
$usuario = 'usuario';
$senha = 'senha';
$opcoes = array(
    PDO::ATTR_PERSISTENT => true,
    PDO::ATTR_CASE => PDO::CASE_LOWER
);

try {
    $pdo = new PDO($dsn, $usuario, $senha, $opcoes);
} catch (PDOException $e) {
    echo 'Erro: '.$e->getMessage();
}

Após abrir uma conexão, as consultas podem ser feitas de duas maneiras:

  1. Através da própria classe de conexão, com o método "exec" ou o "query";
  2. Montando uma prepared statement com o método "prepare", que devolve um objeto da classe PDOStatement, e depois executando o método "execute" (desta classe).

O método "query" é utilizado para consultas que retornam resultados tabulares (como o SELECT) e devolve um objeto da classe PDOStatement com o resultado. Já o método "exec" é utilizado para consultas que não retornam resultados tabulares (como o INSERT, UPDATE, DELETE) e retorna apenas o número de linhas afetadas.

Estes métodos são úteis para executar consultas fixas (não-variáveis). Afinal, se envolvessem valores recebidos do usuário, estes valores precisariam ser escapados através do método "quote" (para evitar falhas de segurança com SQL Injection).

Já o método "prepare" é útil para montar uma consulta com dados variáveis. É possível especificar uma SQL com pontos de substituição que, ao serem substituídos, são escapados pela classe automaticamente. Vejamos alguns exemplos:

// Usando "exec"
$inseriu = $pdo->exec('INSERT INTO logs (operacao) VALUES (1)');
$ultimo_id = $pdo->lastInsertId();

// Usando "query"
$stmt = $pdo->query('SELECT nome, login FROM usuarios');

// Percorrento um resultset com while
while ($obj = $stmt->fetchObject()) {
    ...
}

// Percorrendo um resultset com foreach
foreach ($stmt as $linha) {
    ...
}

Note que a classe PDOStatement (objeto $stmt) implementa a interface Traversable, indicando que ela pode ser percorrida por uma estrutura "foreach".

Existem diferentes formas de se executar uma prepared statement com PDO:

// 1 - Usando "?" nos pontos-chave (de substituicao)
$stmt = $pdo->prepare('INSERT INTO usuarios (nome, login) VALUES (?,?)');

// Passando os valores a serem usados no primeiro e segundo "?"
$dados = array('Rubens', 'rubens');
$inseriu = $stmt->execute($dados);

// 2 - Usando pontos-chave nomeados e um array de dados
$stmt = $pdo->prepare('INSERT INTO usuarios (nome, login) VALUES (:nome, :login)');

// Passando os valores a serem usados em :nome e :login
$dados = array(':nome' => 'Rubens', ':login' => 'rubens');
$inseriu = $stmt->execute($dados);

// 3 - Usando pontos-chave nomeados e valores indivisuais
$stmt = $pdo->prepare('INSERT INTO usuarios (nome, login) VALUES (:nome, :login)');

// Fazendo binding de parametros
$nome = 'Rubens Takiguti Ribeiro';
$login = 'rubens';
$stmt->bindValue(':nome', $nome, PDO::PARAM_STR, 128);
$stmt->bindValue(':login', $login, PDO::PARAM_STR, 20);

// Executando a SQL com os valores definidos com binding
$inseriu = $stmt->execute();

Prepared statements tendem a ser mais rápidas que as consultas convencionais, já que a consulta fica previamente "compilada" e pronta para execução com novos valores. Ao invés do SGBD interpretar toda a SQL, ele apenas atribui novos valores aos pontos chave e realiza a operação. Funcionalidade muito útil para inserções ou atualizações em massa em uma tabela.

Uma outra possibilidade de bind é usando o bindParam. Neste caso, ao invés de atribuir um valor a um ponto da Prepared Statement, você atribui uma referência a uma variável. Desta forma, você pode modificar o valor da variável e chamar o método execute novamente, que irá executar a mesma query, mas com o novo valor da variável. Veja um exemplo:

// Preparando a query
$stmt = $pdo->prepare('INSERT INTO usuarios (nome, login) VALUES (:nome, :login)');

// Fazendo um binding com alguma variavel
$nome = '';
$login = '';
$stmt->bindParam(':nome', $nome, PDO::PARAM_STR, 128);
$stmt->bindParam(':login', $login, PDO::PARAM_STR, 20);

// Atribuindo valores novos e executando a query
$nome = 'Rubens Takiguti Ribeiro';
$login = 'rubs33';
$stmt->execute(); // Executa o insert com os valores 'Rubens Takiguti Ribeiro' e 'rubs33'

// Atribuindo novos valores e executando a query
$nome = 'Iúna Fricke';
$login = 'iunaf';
$stmt->execute(); // Executa o insert com os valores 'Iuna Fricke' e 'iunaf'

Note que a query é preparada uma única vez, mas executada várias vezes. Cada uma das vezes utiliza os valores que estiverem nas variáveis que foram associadas ao $stmt.


Outros benefícios do PDO

  1. Obtenção padronizada de erros ocorridos em consultas, através do método "errorInfo" e "errorCode" da classe PDO e PDOStatement.
  2. Suporte a inicialização e encerramento de transações de forma padronizada (esta funcionalidade depende do suporte do SGBD), através dos métodos "beginTransaction", "commit" e "rollBack".
  3. Suporte para trabalhar com handle de arquivos para realizar inserções ou consultas a grandes volumes de dados, para economizar memória. Isso acontece pois a própria classe PDO fica responsável por transmitir o arquivo "aos poucos", sem precisar carregá-lo inteiramente na memória, como seria feito da forma tradicional. Um handle de arquivo utiliza uma variável do tipo resource, que funciona como um "ponteiro" para o arquivo.

Conclusão

O objetivo deste artigo é motivar programadores a utilizar PDO. Nota-se que muitos programadores ainda não utilizam PDO pois já estão acostumados com as antigas bibliotecas de funções de conexão e não mudam por comodidade.

Porém, deve-se atentar ao futuro de PHP, que está caminhando cada vez mais para o paradigma orientado a objetos. Neste futuro, que já é presente, está inserido o PDO.

5 comentários

Rubens Takiguti Ribeiro (autor do blog) disse...

Obrigado, Renington.

Compreender mais, só depende de você usá-la na prática. Mas, certamente, após familiarizado, você vai notar o quão simples é PDO. Afinal, o objetivo foi justamente padronizar os métodos, deixando recursos específicos para serem usados/definidos via SQL específica.

Unknown disse...

Olá,

Aproveitando o tema sobre a extensão PDO, venho aqui recomendar o uso da classe PDO4You, a qual estou utilizando a um bom tempo atrás e tem se tornado a base dos meus projetos.

Para quem se interessar, possui um repositório no github para poder baixar e utilizar em suas aplicações, ou simplesmente acompanhar e/ou compartilhar.

Segue link: https://bitly.com/PDO4You

Sugestões ou críticas, são bem vindas e ajuda a tornar esta classe ainda melhor para benefício de todos e assim seja até quando o PHP existir. =D

Abraços.

Kady Aoun disse...

Muito bom o seu artigo, parabéns e obrigado!
Venho da escola antiga de PHP e tenho alguma dificuldade com POD, porém vc me ajudou a entender bastante coisa.