Por que você deve usar o PDO do PHP para acesso ao banco de dados

2 de novembro de 2021 Off Por sudoroot

Muitos programadores de PHP aprenderam como acessar bancos de dados usando as extensões MySQL ou MySQLi. A partir do PHP 5.1, existe uma maneira melhor. O PHP Data Objects  (PDO) fornece métodos para instruções preparadas e trabalho com objetos que o tornarão muito mais produtivo!

Geradores e frameworks CRUD

O código do banco de dados é repetitivo, mas é muito importante acertar. É aí que entram os geradores e estruturas de PHP CRUD – eles economizam tempo gerando automaticamente todo esse código repetitivo para que você possa se concentrar em outras partes do aplicativo.

No CodeCanyon, você encontrará geradores e estruturas CRUD que o ajudarão a entregar produtos de excelente qualidade dentro do prazo. (CRUD é um acrônimo para criar, ler, atualizar e excluir – as manipulações básicas para um banco de dados.)

Introdução ao PDO

PDO – PHP Data Objects – é uma camada de acesso ao banco de dados que fornece um método uniforme de acesso a vários bancos de dados.

Ele não leva em consideração a sintaxe específica do banco de dados, mas pode permitir que o processo de troca de bancos de dados e plataformas seja bastante indolor, simplesmente trocando a string de conexão em muitos casos.

Este tutorial não pretende ser um tutorial completo sobre SQL. Ele foi escrito principalmente para pessoas que atualmente usam a  extensão mysql ou  mysqlipara ajudá-los a fazer o salto para o PDO mais portátil e poderoso.

Quando se trata de operações de banco de dados em PHP, o PDO oferece muitas vantagens sobre a sintaxe bruta. Vamos listar rapidamente alguns:

  • camada de abstração
  • sintaxe orientada a objetos
  • suporte para declarações preparadas
  • melhor tratamento de exceções
  • APIs seguras e reutilizáveis
  • suporte para todos os bancos de dados populares
Advertisement

Suporte de banco de dados

A extensão pode suportar qualquer banco de dados para o qual um driver PDO tenha sido escrito. No momento em que este artigo foi escrito, os seguintes drivers de banco de dados estavam disponíveis:

  • PDO_DBLIB (FreeTDS / Microsoft SQL Server / Sybase)
  • PDO_FIREBIRD (Firebird / Interbase 6)
  • PDO_IBM (IBM DB2)
  • PDO_INFORMIX (IBM Informix Dynamic Server)
  • PDO_MYSQL (MySQL 3.x / 4.x / 5.x)
  • PDO_OCI (Oracle Call Interface)
  • PDO_ODBC (ODBC v3 (IBM DB2, unixODBC e win32 ODBC))
  • PDO_PGSQL (PostgreSQL)
  • PDO_SQLITE (SQLite 3 e SQLite 2)
  • PDO_4D (D)

Todos esses drivers não estão necessariamente disponíveis em seu sistema; aqui está uma maneira rápida de descobrir quais drivers você possui:

1
print_r(PDO::getAvailableDrivers());

Conectando

Bancos de dados diferentes podem ter métodos de conexão ligeiramente diferentes. Abaixo, você pode ver o método para se conectar a alguns dos bancos de dados mais populares. Você notará que os três primeiros são idênticos, exceto o tipo de banco de dados – e então o SQLite tem sua própria sintaxe.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
try {
  # MS SQL Server and Sybase with PDO_DBLIB
  $DBH = new PDO("mssql:host=$host;dbname=$dbname", $user, $pass);
  $DBH = new PDO("sybase:host=$host;dbname=$dbname", $user, $pass);
 
  # MySQL with PDO_MYSQL
  $DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
 
  # SQLite Database
  $DBH = new PDO("sqlite:my/database/path/database.db");
}
catch(PDOException $e) {
    echo $e->getMessage();
}

Observe o bloco try / catch. Você deve sempre envolver suas operações PDO em um try / catch e usar o mecanismo de exceção – mais sobre isso em breve. Normalmente, você só fará uma única conexão – há várias listadas para mostrar a sintaxe. $DBH significa ‘identificador de banco de dados’ e será usado ao longo deste tutorial.

Você pode fechar qualquer conexão definindo o identificador como nulo.

1
2
# close the connection
$DBH = null;

Você pode obter mais informações sobre opções específicas de banco de dados e / ou strings de conexão para outros bancos de dados em  PHP.net .

Exceções e PDO

O PDO pode usar exceções para lidar com erros, o que significa que qualquer coisa que você fizer com o PDO deve ser encapsulada em um bloco try / catch. Você pode forçar o PDO em um dos três modos de erro, definindo o atributo de modo de erro em seu identificador de banco de dados recém-criado. Esta é a sintaxe:

1
2
3
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

Não importa o modo de erro definido, um erro de conexão sempre produzirá uma exceção, e a criação de uma conexão deve sempre estar contida em um bloco try / catch.

PDO::ERRMODE_SILENT

Este é o modo de erro padrão. Se você deixá-lo nesse modo, terá que verificar se há erros da maneira como provavelmente está acostumado, caso tenha usado as  extensões mysql ou  mysqli. Os outros dois métodos são mais adequados para a programação DRY.

PDO::ERRMODE_WARNING

Este modo emitirá um aviso padrão do PHP e permitirá que o programa continue a execução. É útil para depuração.

PDO::ERRMODE_EXCEPTION

Este é o modo que você deseja na maioria das situações. Ele dispara uma exceção, permitindo que você lide com erros normalmente e oculte dados que podem ajudar alguém a explorar seu sistema. Aqui está um exemplo de como aproveitar as exceções:

01
02
03
04
05
06
07
08
09
10
11
12
# connect to the database
try {
  $DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
  $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
 
  # UH-OH! Typed DELECT instead of SELECT!
  $DBH->prepare('DELECT name FROM people');
}
catch(PDOException $e) {
    echo "I'm sorry, Dave. I'm afraid I can't do that.";
    file_put_contents('PDOErrors.txt', $e->getMessage(), FILE_APPEND);
}

Há um erro intencional na instrução select; isso causará uma exceção. A exceção envia os detalhes do erro para um arquivo de log e exibe uma mensagem amigável (ou não tão amigável) para o usuário.

Inserir e atualizar

Inserir novos dados (ou atualizar dados existentes) é uma das operações de banco de dados mais comuns. Usando PHP PDO, este é normalmente um processo de duas etapas. Tudo o que é abordado nesta seção se aplica igualmente às  operações UPDATE e  INSERT.

Aqui está um exemplo do tipo mais básico de inserção:

1
2
3
# STH means "Statement Handle"
$STH = $DBH->prepare("INSERT INTO folks ( first_name ) values ( 'Cathy' )");
$STH->execute();

Você também pode realizar a mesma operação usando o  exec() método, com uma chamada a menos. Na maioria das situações, você usará o método mais longo para aproveitar as vantagens das instruções preparadas. Mesmo se você for usá-lo apenas uma vez, o uso de instruções preparadas ajudará a protegê-lo de ataques de injeção de SQL.

Declarações Preparadas

O uso de instruções preparadas ajudará a protegê-lo da injeção de SQL.

Uma instrução preparada é uma instrução SQL pré-compilada que pode ser executada várias vezes, enviando apenas os dados para o servidor. Ele tem a vantagem adicional de tornar automaticamente os dados usados ​​nos marcadores de posição protegidos contra ataques de injeção de SQL.

Você usa uma instrução preparada incluindo marcadores em seu SQL. Aqui estão três exemplos: um sem marcadores de posição, um com marcadores de posição não nomeados e outro com marcadores de posição nomeados.

1
2
3
4
5
6
7
8
# no placeholders - ripe for SQL Injection!
$STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values ($name, $addr, $city)");
 
# unnamed placeholders
$STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (?, ?, ?)");
 
# named placeholders
$STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (:name, :addr, :city)");

Você deseja evitar o primeiro método; está aqui para comparação. A escolha de usar marcadores de posição nomeados ou não nomeados afetará como você configura os dados para essas instruções.

Advertisement

Marcadores de posição sem nome

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
# assign variables to each place holder, indexed 1-3
$STH->bindParam(1, $name);
$STH->bindParam(2, $addr);
$STH->bindParam(3, $city);
 
# insert one row
$name = "Daniel"
$addr = "1 Wicked Way";
$city = "Arlington Heights";
$STH->execute();
 
# insert another row with different values
$name = "Steve"
$addr = "5 Circle Drive";
$city = "Schaumburg";
$STH->execute();

Existem duas etapas aqui. Primeiro, atribuímos variáveis ​​aos vários marcadores de posição (linhas 2–4). Em seguida, atribuímos valores a esses marcadores de posição e executamos a instrução. Para enviar outro conjunto de dados, basta alterar os valores dessas variáveis ​​e executar a instrução novamente.

Isso parece um pouco pesado para instruções com muitos parâmetros? Isto é. No entanto, se seus dados estiverem armazenados em uma matriz, há um atalho fácil:

1
2
3
4
5
# the data we want to insert
$data = array('Cathy', '9 Dark and Twisty Road', 'Cardiff');
 
$STH = $DBH->prepare("INSERT INTO folks (name, addr, city) values (?, ?, ?)");
$STH->execute($data);

Isso é fácil!

Os dados na matriz se aplicam aos marcadores de posição em ordem. $data[0] vai para o primeiro espaço reservado,  $data[1] o segundo, etc. No entanto, se os índices do array não estiverem em ordem, isso não funcionará corretamente e você precisará reindexar o array.

Marcadores de posição nomeados

Você provavelmente poderia adivinhar a sintaxe, mas aqui está um exemplo:

1
2
3
# the first argument is the named placeholder name - notice named
# placeholders always start with a colon.
$STH->bindParam(':name', $name);

Você também pode usar um atalho aqui, mas funciona com matrizes associativas. Aqui está um exemplo:

1
2
3
4
5
6
# the data we want to insert
$data = array( 'name' => 'Cathy', 'addr' => '9 Dark and Twisty', 'city' => 'Cardiff' );
 
# the shortcut!
$STH = $DBH->prepare("INSERT INTO folks (name, addr, city) value (:name, :addr, :city)");
$STH->execute($data);

As chaves de sua matriz não precisam começar com dois pontos, mas, caso contrário, precisam corresponder aos marcadores de posição nomeados. Se você tiver uma matriz de matrizes, poderá iterar sobre elas e simplesmente chamar o executecom cada matriz de dados.

Outro recurso interessante dos marcadores de posição nomeados é a capacidade de inserir objetos diretamente em seu banco de dados, assumindo que as propriedades correspondam aos campos nomeados. Aqui está um objeto de exemplo e como você executaria sua inserção:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
# a simple object
class person {
    public $name;
    public $addr;
    public $city;
 
    function __construct($n,$a,$c) {
        $this->name = $n;
        $this->addr = $a;
        $this->city = $c;
    }
    # etc ...
}
 
$cathy = new person('Cathy','9 Dark and Twisty','Cardiff');
 
# here's the fun part:
$STH = $DBH->prepare("INSERT INTO folks (name, addr, city) value (:name, :addr, :city)");
$STH->execute((array)$cathy);

Converter o objeto em uma matriz executesignifica que as propriedades são tratadas como chaves de matriz.

Seleção de dados

Os dados são obtidos por meio do  ->fetch(), um método de seu controle de declaração. Antes de chamar fetch, é melhor dizer ao PDO como você gostaria que os dados fossem buscados. Você tem as seguintes opções:

  • PDO::FETCH_ASSOC:  retorna uma matriz indexada pelo nome da coluna.
  • PDO::FETCH_BOTH (padrão): retorna uma matriz indexada pelo nome e número da coluna.
  • PDO::FETCH_BOUND: atribui os valores de suas colunas às variáveis ​​definidas com o  ->bindColumn() método.
  • PDO::FETCH_CLASS: atribui os valores de suas colunas às propriedades da classe nomeada. Ele criará as propriedades se não existirem propriedades correspondentes.
  • PDO::FETCH_INTO: atualiza uma instância existente da classe nomeada.
  • PDO::FETCH_LAZY: combina  PDO::FETCH_BOTH/ PDO::FETCH_OBJ, criando os nomes das variáveis ​​do objeto à medida que são usados.
  • PDO::FETCH_NUM:  retorna uma matriz indexada pelo número da coluna.
  • PDO::FETCH_OBJ:  retorna um objeto anônimo com nomes de propriedades que correspondem aos nomes das colunas.

Na realidade, há três que irá abranger a maioria das situações:  FETCH_ASSOCFETCH_CLASS, e  FETCH_OBJ. Para definir o método fetch, a seguinte sintaxe é usada:

1
$STH->setFetchMode(PDO::FETCH_ASSOC);

Você também pode definir o tipo de busca diretamente na ->fetch() chamada do  método.

FETCH_ASSOC

Este tipo de busca cria uma matriz associativa, indexada pelo nome da coluna. Isso deve ser bastante familiar para qualquer pessoa que tenha usado as extensões mysql / mysqli. Aqui está um exemplo de seleção de dados com este método:

01
02
03
04
05
06
07
08
09
10
11
12
# using the shortcut ->query() method here since there are no variable
# values in the select statement.
$STH = $DBH->query('SELECT name, addr, city from folks');
 
# setting the fetch mode
$STH->setFetchMode(PDO::FETCH_ASSOC);
 
while($row = $STH->fetch()) {
    echo $row['name'] . "\n";
    echo $row['addr'] . "\n";
    echo $row['city'] . "\n";
}

O loop while continuará a percorrer o conjunto de resultados, uma linha por vez, até que seja concluído.

FETCH_OBJ

Este tipo de busca cria um objeto da classe std para cada linha de dados buscados. Aqui está um exemplo:

01
02
03
04
05
06
07
08
09
10
11
12
# creating the statement
$STH = $DBH->query('SELECT name, addr, city from folks');
 
# setting the fetch mode
$STH->setFetchMode(PDO::FETCH_OBJ);
 
# showing the results
while($row = $STH->fetch()) {
    echo $row->name . "\n";
    echo $row->addr . "\n";
    echo $row->city . "\n";
}

FETCH_CLASS

As propriedades do seu objeto são definidas ANTES de o construtor ser chamado. Isso é importante.

Este método de busca permite que você busque dados diretamente em uma classe de sua escolha. Quando você usa  FETCH_CLASS, as propriedades do seu objeto são definidas e  BEFOREo construtor é chamado. Leia isso de novo – é importante. Se as propriedades correspondentes aos nomes das colunas não existirem, essas propriedades serão criadas (como públicas) para você.

Isso significa que, se seus dados precisarem de alguma transformação depois de saírem do banco de dados, isso pode ser feito automaticamente por seu objeto à medida que cada objeto é criado.

Por exemplo, imagine uma situação em que o endereço precisa ser parcialmente obscurecido para cada registro. Poderíamos fazer isso operando nessa propriedade no construtor. Aqui está um exemplo:

01
02
03
04
05
06
07
08
09
10
11
class secret_person {
    public $name;
    public $addr;
    public $city;
    public $other_data;
 
    function __construct($other = '') {
        $this->address = preg_replace('/[a-z]/', 'x', $this->address);
        $this->other_data = $other;
    }
}

Conforme os dados são buscados nesta classe, o endereço tem todas as suas letras  az  minúsculas substituídas pela letra  x . Agora, usar a classe e fazer com que a transformação de dados ocorra é completamente transparente:

1
2
3
4
5
6
$STH = $DBH->query('SELECT name, addr, city from folks');
$STH->setFetchMode(PDO::FETCH_CLASS, 'secret_person');
 
while($obj = $STH->fetch()) {
    echo $obj->addr;
}

Se o endereço fosse ‘5 Rosebud’, você veria ‘5 Rxxxxxx’ como sua saída. Claro, pode haver situações em que você deseja que o construtor seja chamado antes que os dados sejam atribuídos. O PDO também cobre isso.

1
$STH->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'secret_person');

Agora, ao repetir o exemplo anterior com este modo de busca ( PDO::FETCH_PROPS_LATE), o endereço não será obscurecido, pois o construtor foi chamado e as propriedades foram atribuídas.

Finalmente, se realmente precisar, você pode passar argumentos para o construtor ao buscar dados em objetos com PDO:

1
$STH->setFetchMode(PDO::FETCH_CLASS, 'secret_person', array('stuff'));

Se você precisar passar dados diferentes para o construtor para cada objeto, você pode definir o modo de busca dentro do fetchmétodo:

1
2
3
4
5
$i = 0;
while($rowObj $STH->fetch(PDO::FETCH_CLASS, 'secret_person', array($i))) {
    // do stuff
    $i++
}

Alguns outros métodos úteis

Embora isso não pretenda cobrir tudo no PDO (é uma grande extensão!), Existem mais alguns métodos que você vai querer saber para fazer coisas básicas com o PDO.

1
$DBH->lastInsertId();

->lastInsertId() método é sempre chamado no identificador do banco de dados, não no identificador de instrução, e retornará o ID auto-incrementado da última linha inserida por essa conexão.

1
2
$DBH->exec('DELETE FROM folks WHERE 1');
$DBH->exec("SET time_zone = '-8:00'");

->exec() método é usado para operações que não podem retornar dados além das linhas afetadas. Acima são dois exemplos de como usar o método exec.

1
$safe = $DBH->quote($unsafe);

->quote() método cita strings para que sejam seguras para uso em consultas. Este é o seu fallback se você não estiver usando instruções preparadas.

1
$rows_affected = $STH->rowCount();

->rowCount() método retorna um inteiro indicando o número de linhas afetadas por uma operação. Em pelo menos uma versão conhecida do PDO,  o método não funcionava com instruções selecionadas . No entanto, ele funciona corretamente na versão PHP 5.1.6 e superior.

Se você está tendo esse problema e não consegue atualizar o PHP, pode obter o número de linhas com o seguinte:

01
02
03
04
05
06
07
08
09
10
11
$sql = "SELECT COUNT(*) FROM folks";
if ($STH = $DBH->query($sql)) {
    # check the row count
    if ($STH->fetchColumn() > 0) {
 
    # issue a real select here, because there's data!
    }
    else {
        echo "No rows matched the query.";
    }
}

Conclusão

Espero que isso ajude você a migrar para fora das extensões mysql e  mysqli. O que você acha? Existe algum de vocês aí que pode fazer a troca?