Instruções Preparadas do PHP PDO para Evitar Injeção de SQL

Postado 18:47 27/03/2018 por THIAGO CONDÉ COMPARTILHAR

Fonte original: https://websitebeaver.com/php-pdo-prepared-statements-to-prevent-sql-injection


Instruções Preparadas do PHP PDO para Evitar Injeção de SQL



O que é PDO?

O PDO é uma camada de abstração para suas consultas ao banco de dados e é uma alternativa incrível ao MySQLi, já que suporta 12 drivers de banco de dados diferentes . Este é um imenso benefício para pessoas e empresas que precisam dele. No entanto, tenha em mente que o MySQL é de longe o banco de dados mais popular . Além disso, PHP e MYSQL são como manteiga de amendoim e geléia. O NoSQL é uma história diferente, e o Firebase e o MongoDB são excelentes opções, especialmente o primeiro, pois é um banco de dados ativo - obviamente, ambos não são suportados no PDO.

Nota: Para este tutorial, mostrarei instruções preparadas em PDO não emuladas (nativas) estritamente com o MySQL, portanto, pode haver algumas diferenças em um driver diferente.

Se você sabe que os únicos bancos de dados SQL que você estará usando são o MySQL ou o MariaDB, então você pode escolher entre PDO e MySQLi. Verificando o seguinte tutorial, Se você gostaria de aprender MySQLi . Qualquer um deles é perfeitamente aceitável de usar, embora o PDO seja a melhor escolha para a maioria dos usuários, pois é mais simples e mais versátil, enquanto o MySQLi é às vezes mais adequado para usuários avançados, devido a alguns dos recursos específicos do MySQL.

Muitas pessoas regurgitam que a principal vantagem do PDO é que ele é portátil de banco de dados para banco de dados. Este é um benefício extremamente exagerado e é essencialmente absurdo. O SQL não deve ser transferido dessa maneira, pois cada driver DB possui suas próprias nuances; Além disso, com que frequência você está realmente tomando decisões para trocar bancos de dados em um projeto específico, a menos que você seja pelo menos uma empresa de médio e grande porte?

A verdadeira vantagem do PDO é o fato de que você está usando uma interface idêntica para qualquer um dos 12 bancos de dados suportados por ele, portanto, você estará familiarizado com a API, independentemente de qual você usar. Os parâmetros nomeados também são, sem dúvida, uma grande vitória para o PDO, já que você pode reutilizar os mesmos valores em locais diferentes nas consultas. Infelizmente, você não pode usar os mesmos parâmetros nomeados mais de uma vez com o modo de emulação desativado, tornando-o inútil para o propósito deste tutorial.

Uma vantagem controversa do PDO é o fato de que você não precisa usar bindParam () ou bindValue () , pois você pode simplesmente passar os valores como matrizes diretamente para executar. Alguns podem argumentar que isso é considerado uma prática ruim, já que você não pode especificar o tipo (string, int, double, blob); tudo será tratado como uma string e será convertido para o tipo correto automaticamente. Na prática, isso não deve afetar seus ints ou doubles e está protegido contra injeção de SQL. Este tutorial irá ligar valores diretamente para executar. Semelhante a bindValue(), você pode usar valores e variáveis.

Se você gostaria de aprender como funciona a injeção de SQL, você pode ler sobre isso aqui .

Como funcionam as instruções preparadas para o PDO?

Em termos leigos, as declarações preparadas pelo PDO funcionam assim:

  1. Prepare uma consulta SQL com valores vazios como espaços reservados com um ponto de interrogação ou um nome de variável com dois pontos, precedendo-o para cada valor
  2. Vincular valores ou variáveis ??aos marcadores
  3. Executar consulta simultaneamente

Criando uma nova conexão PDO

Eu recomendo criar um arquivo chamado pdo_connect.phpe colocá-lo fora do seu diretório raiz (ex: html, public_html).

$dsn = "mysql:host=localhost;dbname=myDatabase;charset=utf8mb4";
$options = [
  PDO::ATTR_EMULATE_PREPARES   => false, // turn off emulation mode for "real" prepared statements
  PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION, //turn on errors in the form of exceptions
  PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, //make the default fetch be an associative array
];
try {
  $pdo = new PDO($dsn, "username", "password", $options);
} catch (Exception $e) {
  error_log($e->getMessage()); //In production
  exit('Something weird happened'); //something a user can understand
}

Então, o que está acontecendo aqui? A primeira linha é referida como DSN e possui três valores separados para preencher, seu nome de host, banco de dados e conjunto de caracteres. Esta é a maneira recomendada de fazer isso, e você pode obviamente definir seu charset para o que seu aplicativo precisar (embora o utf8mb4 seja bem padrão). Agora você pode passar suas informações de DSN, nome de usuário, senha e opções.

Como alternativa, você pode omitir o uso de um try/catchbloco criando um manipulador global de exceções personalizadas. Se isso estiver incluído em todas as suas páginas, ele usará esse manipulador personalizado, a menos que você restore_exception_handler () reverta para o manipulador de exceções PHP incorporado ou chame set_exception_handler () com uma nova função e mensagem customizada.

set_exception_handler(function($e) {
  error_log($e->getMessage());
  exit('Something weird happened'); //something a user can understand
});
$dsn = "mysql:host=localhost;dbname=myDatabase;charset=utf8mb4";
$options = [
  PDO::ATTR_EMULATE_PREPARES   => false, // turn off emulation mode for "real" prepared statements
  PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION, //turn on errors in the form of exceptions
  PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, //make the default fetch be an associative array
];
$pdo = new PDO($dsn, "username", "password", $options);

Isso é extremamente discutível, mas uma coisa que eu gosto sobre o MySQLi é que o relatório de erros está desativado por padrão. Isso é inteligente, então um iniciante não imprimiria acidentalmente sua senha. No PDO, mesmo que você tenha controle para silenciar erros, não é possível fazer isso para o construtor . Então, obviamente, você deve primeiro configurar seu php.ini para produção. Para evitar o vazamento de sua senha, veja como deve ser o arquivo php.ini em produção: faça as duas coisas display_errors = Offelog_errors = On . Agora, todos os erros no seu site serão acumulados apenas no seu log de erros, em vez de imprimi-los.

Ainda assim, não vejo um motivo para imprimir sua senha em seu log de erros, portanto, recomendo fazer try/catche error_log() $e->getMessage(), não $e, que ainda conterá suas informações confidenciais.

Parâmetros nomeados

Eu realmente amo esse recurso, e é uma grande vantagem para o PDO. Você especifica uma variável nomeada :ide atribui seu valor à execução. Embora, como afirmado anteriormente, sua única vantagem de ser usado várias vezes seja inutilizado se o modo de emulação estiver desativado.

$stmt = $pdo->prepare("UPDATE myTable SET name = ? WHERE id = :id");
$stmt->execute([':id' => $_SESSION['id']]);
$stmt = null;

Você tecnicamente não precisa dos dois pontos principais idpara a parte de execução, como dito aqui . No entanto, isso não é explicitamente declarado em nenhum lugar dos documentos, portanto, embora deva funcionar como alguns usuários concluíram astutamente ao procurar no código C, isso não é tecnicamente recomendado. Meu palpite é que o PHP vai documentar isso eventualmente, de qualquer forma, já que parece que há pessoas suficientes que omitem o cólon principal.

Eu dediquei uma seção ao uso de parâmetros nomeados, já que o resto da postagem estará sendo usado em seu ?lugar. Tenha em mente que você não pode misturar os dois juntos quando vincula valores.

Inserir, atualizar e excluir

Todos estes são extremamente semelhantes entre si, então eles serão combinados.

Inserir

$stmt = $pdo->prepare("INSERT INTO myTable (name, age) VALUES (?, ?)");
$stmt->execute([$_POST['name'], 29]);
$stmt = null;

Atualizar

Você pode até mesmo encadear prepare()e execute(). Embora você não possa usar nenhuma função, rowCount()é muito inútil na prática.

$stmt = $pdo->prepare("UPDATE myTable SET name = ? WHERE id = ?")->execute([$_POST['name'], $_SESSION['id']]);
$stmt = null;

Excluir

$stmt = $pdo->prepare("DELETE FROM myTable WHERE id = ?");
$stmt->execute([$_SESSION['id']]);
$stmt = null;

Obter o número de linhas afetadas

Obtendo o número de linhas afetadas é extremamente simples, como tudo que você precisa fazer é $stmt->rowCount(). Normalmente, se você atualizar sua tabela com os mesmos valores, ela retornará 0. Se você quiser alterar esse comportamento, a única maneira de fazer isso é adicionando globalmente essa opção ao criar uma nova conexão PDO::MYSQL_ATTR_FOUND_ROWS => true.

$stmt = $pdo->prepare("UPDATE myTable SET name = ? WHERE id = ?");
$stmt->execute([$_POST['name'], $_SESSION['id']]);
echo $stmt->rowCount();
$stmt = null;

Obter chave primária mais recente inserida

$stmt = $pdo->prepare("INSERT INTO myTable (name, age) VALUES (?, ?)");
$stmt->execute([$_POST['name'], 29]);
echo $pdo->lastInsertId();
$stmt = null;

Selecione

Para buscar resultados no PDO, você pode usar $stmt->fetch()e $stmt->fetchAll(). O primeiro é mais versátil, pois pode ser usado para buscar uma linha, ou todos, se usado em um loop. O último é basicamente o açúcar sintático, pois permite buscar todo o seu conjunto de resultados em um array com aquele comando. É preferível usar $stmt->fetch()em um loop se você estiver modificando esse array, já que ele evita que você tenha que "re-loop".

Os modos de busca no PDO são facilmente meu aspecto favorito. Eu vou misturá-los em meus exemplos, mas aqui estão algumas das constantes, eu acho que são as mais úteis.

Buscar modos

Tanto fetch () quanto fetchAll ()

  • PDO::FETCH_NUM - Buscar um array numérico
  • PDO::FETCH_ASSOC - Buscar um array associativo
  • PDO::FETCH_COLUMN- Buscar apenas uma coluna. Escalar se fetch()for usado; 1d array, se fetchAll()for usado. Se fetch(), então você pode usar fetchColumn () para açúcar sintático
  • PDO::FETCH_OBJ- Buscar como um objeto stdClass genérico. Se fetch(), então você pode usar fetchObject () para açúcar sintático em vez disso. Se você precisar especificar um nome de classe para uma única linha, ainda poderá usá-lo PDO::FETCH_CLASS, mas deverá defini-lo com setFetchMode(PDO::FETCH_CLASS, 'MyClass'), seguido por fetch(). Portanto, é preferível usar fetchObject('MyClass'), pois você economiza da linha extra

Apenas fetchAll ()

  • PDO::FETCH_CLASS- Buscar como um objeto stdClass genérico ou em uma classe existente, se especificado; o mesmo que usar PDO::FETCH_OBJse objeto anônimo. Como dito anteriormente, pode ser usado fetch()também, mas não é preferido
  • PDO::FETCH_KEY_PAIR - Buscar um par de chave / valor com a primeira coluna como uma chave única e a segunda como o valor único
  • PDO::FETCH_UNIQUE- O mesmo que PDO::FETCH_KEY_PAIR, apenas a parte do valor é um array
  • PDO::FETCH_GROUP - Buscar por um nome de coluna comum e agrupar todas as linhas para essa chave como uma matriz de matrizes associativas
  • PDO::FETCH_GROUP | PDO::FETCH_COLUMN- É o mesmo que PDO::FETCH_GROUP, apenas a parte do valor é uma matriz de matrizes numéricas 1d

Buscar Matriz Associativa

Como definimos o tipo de busca padrão como um array associativo, não especificamos nada ao buscar resultados.

$stmt = $pdo->prepare("SELECT * FROM myTable WHERE id <= ?");
$stmt->execute([5]);
$arr = $stmt->fetchAll();
if(!$arr) exit('No rows');
var_export($arr);
$stmt = null;

Saída:

[
  ['name' => 'Jerry', 'age' => 14, 'weight' => 129], 
  ['name' => 'Alexa', 'age' => 22, 'weight' => 108]
]

Há também a versão while loop ligeiramente mais longa, que às vezes é útil para manipulações.

$arr = [];
$stmt = $pdo->prepare("SELECT * FROM myTable WHERE name = ?");
$stmt->execute([$_POST['name']]);
while ($row = $stmt->fetch()) {
  $arr[] = $row;
}
if(!$arr) exit('No rows');
var_export($arr);
$stmt = null;

Buscar Matriz de Objetos

Semelhante a buscar uma matriz associativa, mas com objetos, para que você possa acessá-la como, $arr[0]->agepor exemplo.

$stmt = $pdo->prepare("SELECT name, age, weight FROM myTable WHERE name = ?");
$stmt->execute(['Joe']);
$arr = $stmt->fetchAll(PDO::FETCH_CLASS);
if(!$arr) exit('No rows');
var_export($arr);
$stmt = null;

Saída:

[
  stdClass Object ['name' => 'Jerry', 'age' => 14, 'weight' => 129], 
  stdClass Object ['name' => 'Alexa', 'age' => 22, 'weight' => 108]
]

Você pode até acrescentar valores de propriedade a uma classe já existente, como assim.

class myClass {}
$stmt = $pdo->prepare("SELECT name, age, weight FROM myTable WHERE name = ?");
$stmt->execute(['Joe']);
$arr = $stmt->fetchAll(PDO::FETCH_CLASS, 'myClass');
if(!$arr) exit('No rows');
var_export($arr);
$stmt = null;

Tenha em mente que isso tem um comportamento imprevisível de injetar o valor da propriedade antes de configurá-lo no construtor (se você tiver um). Isso significa que, se você já usou um dos nomes de variáveis ??no construtor, o valor da busca será substituído pelo valor padrão. Esse comportamento é anotado aqui . Para garantir que os valores sejam atribuídos após o construtor ser chamado, você deve fazer fetchAll(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'myClass').

Outro comportamento inesperado, mas potencialmente útil, é que você pode modificar variáveis ??privadas. Eu realmente não tenho certeza de como me sinto sobre isso, pois isso parece violar princípios de encapsulamento.

Buscar chave / valor par

Isso cria uma matriz associativa com o formato da primeira coluna como a chave e a segunda coluna como o valor. Portanto, sua primeira coluna precisa ser um valor único.

$stmt = $pdo->prepare("SELECT id, name FROM myTable WHERE age < ?");
$stmt->execute([25]);
$arr = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
if(!$arr) exit('No rows');
var_export($arr);
$stmt = null;

Saída:

[7 => 'Jerry', 10 => 'Bill', 29 => 'Bobby']

Buscar matriz de pares de chave / valor

Por falta de um termo melhor, obviamente. O que quero dizer com isso é que a chave será sua primeira coluna, que precisa ser um valor único, enquanto o valor será o restante das colunas como uma matriz associativa.

$stmt = $pdo->prepare("SELECT id, max_bench, max_squat FROM myTable WHERE weight < ?");
$stmt->execute([225]);
$arr = $stmt->fetchAll(PDO::FETCH_UNIQUE);
if(!$arr) exit('No rows');
var_export($arr);
$stmt = null;

Não sei por que tanto os docs PHP e este comentário lá estado que você deve bit a bit-lo e adicionar FETCH_GROUP, assim: $stmt->fetchAll(PDO::FETCH_UNIQUE | PDO::FETCH_GROUP). Tem o mesmo efeito das minhas provas.

Saída:

[
  17 => ['max_bench' => 230, 'max_squat' => 175],
  84 => ['max_bench' => 195, 'max_squat' => 235],
  136 => ['max_bench' => 135, 'max_squat' => 285]
]

Buscar em grupos

Digamos que você queira agrupar por cor dos olhos, por exemplo. Este prático modo de busca permite que você faça isso de maneira extremamente trivial.

$stmt = $pdo->prepare("SELECT eye_color, name, weight FROM myTable WHERE age < ?");
$stmt->execute([35]);
$arr = $stmt->fetchAll(PDO::FETCH_GROUP);
if(!$arr) exit('No rows');
var_export($arr);
$stmt = null;

Saída:

[
  'green' => [
    ['name' => 'Patrick', 'weight' => 178],
    ['name' => 'Olivia', 'weight' => 132]
  ],
  'blue' => [
    ['name' => 'Kyle', 'weight' => 128],
    ['name' => 'Ricky', 'weight' => 143]
  ],
  'brown' => [
    ['name' => 'Jordan', 'weight' => 173],
    ['name' => 'Eric', 'weight' => 198]
  ]
]

Buscar em grupos, uma coluna

A diferença entre este e o exemplo anterior, é essencialmente a mesma situação que FETCH_KEY_PAIRvs FETCH_UNIQUE. O exemplo anterior agrupa a primeira coluna, com uma matriz, enquanto esta agrupa a primeira coluna com todos os valores da segunda coluna.

$stmt = $pdo->prepare("SELECT eye_color, name FROM myTable WHERE age < ?");
$stmt->execute([35]);
$arr = $stmt->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_COLUMN);
if(!$arr) exit('No rows');
var_export($arr);
$stmt = null;

Saída:

[
  'green' => ['Patrick', 'Olivia'],
  'blue' => ['Kyle', 'Ricky'],
  'brown' => ['Jordan', 'Eric']
]

Buscar fileira única

$stmt = $pdo->prepare("SELECT id, name, age FROM myTable WHERE name = ?");
$stmt->execute([$_POST['name']]);
$arr = $stmt->fetch();
if(!$arr) exit('No rows');
var_export($arr);
$stmt = null;

Agora você acessa cada variável, como $arr['name']por exemplo.

Saída:

['id' => 645, 'name' => 'Joey', 'age' => 28]

Obtenha uma única linha como o MySQLi bind_result ()

$stmt = $pdo->prepare("SELECT id, name, age FROM myTable WHERE id = ?");
$stmt->execute([3]);
$arr = $stmt->fetch(PDO::FETCH_NUM); //FETCH_NUM must be used with list
if(!$arr) exit('no rows');
list($id, $name, $age) = $arr;
echo $name; //Output: 'Jeremy'
$stmt = null;

Isso é para imitar o comportamento (apenas benéfico) do bind_result()MySQLi, que é poder ligar valores a um nome de variável. Agora você pode acessar cada variável como assim: $name.

Busca Escalar (Valor Único)

Um caso de uso comum para isso é se você quiser apenas obter uma contagem de linha e armazená-la em uma variável. Há uma pegadinha com o uso de fetch(PDO::FETCH_COLUMN)um valor booleano, pois não há como distinguir entre nenhuma linha e um valor falso. O exemplo a seguir usa a COUNT()função MySQL , o que obviamente seria bom apenas para verificar a veracidade. No entanto, para todos os outros casos, se a própria coluna for um valor booleano, como 0, você deverá usar $stmt->rowCount() === 0ou $colVal === falsepara verificar se não há linhas.

Para ser claro, esse comportamento não ocorre quando você precisa buscar uma matriz fetchAll(PDO::FETCH_COLUMN). No entanto, se você fosse usar fetch(PDO::FETCH_COLUMN)em um loop para armazenar valores em sua matriz, esse comportamento inesperado ainda ocorre. Eu sinceramente não vejo por que alguém faria isso usando fetchAll(PDO::FETCH_COLUMN), mas deve ser notado.

$stmt = $pdo->prepare("SELECT COUNT(*) FROM myTable WHERE weight < ?");
$stmt->execute([185]);
$count = $stmt->fetch(PDO::FETCH_COLUMN);
//Syntactic sugar for previous line: $count = $stmt->fetchColumn();
if(!$count) exit('No rows');
echo $count; //Output: 1784
$stmt = null;

Agora $counté o valor literal da contagem de linhas.

Buscar várias colunas como variável de matriz separada

Isso pode ser útil, pois você pode facilmente separá-lo em um conjunto de matrizes 1D separadas, em vez de apenas um array multidimensional. Tenha em mente que eu costumava rowCount()verificar se há alguma linha. A maioria dos motoristas não têm capacidade de usar rowCount()em SELECTdeclarações, mas o MySQL faz. Se você estiver usando um driver diferente, poderá usar isset()em cada variável de matriz após o loop while.

$stmt = $pdo->prepare("SELECT id, age, height FROM myTable WHERE weight > ?");
$stmt->execute([120]);
if($stmt->rowCount() === 0) exit('No rows');
while ($row = $stmt->fetch()) {
  $ids[] = $row['id'];
  $ages[] = $row['age'];
  $names[] = $row['name']
}
var_export($ages);
$stmt = null;

Saída:

[8, 12, 28, 64, 43, 29]

Buscar Coluna Única como Variável de Matriz

O mesmo conceito do exemplo anterior, mas isso é útil, basta você obter uma matriz de apenas uma coluna.

$stmt = $pdo->prepare("SELECT height FROM myTable WHERE id < ?");
$stmt->execute([500]);
$heights = $stmt->fetchAll(PDO::FETCH_COLUMN);
if(!$heights) exit('No rows');
var_export($heights);
$stmt = null;

Saída:

[78, 64, 68, 54, 58]

Gostar

Você pode intuitivamente tentar fazer algo como o seguinte.

$stmt = $pdo->prepare("SELECT id, name, age FROM myTable WHERE name LIKE %?%"); 

No entanto, isso não funcionará. É assim que você faria do jeito certo.

$search = "%{$_POST['search']}%";
$stmt = $pdo->prepare("SELECT id, name, age FROM myTable WHERE name LIKE ?");
$stmt->execute([$search]);
$arr = $stmt->fetchAll();
if(!$arr) exit('No rows');
var_export($arr);
$stmt = null;

Onde na matriz

Como você pode ver, o PDO claramente se sobressai nisto também, já que o código é muito mais curto, devido a não especificar o tipo e não usar nem o bindValue()nem bindParam().

$inArr = [1, 3, 5];
$clause = implode(',', array_fill(0, count($inArr), '?')); //create 3 question marks
$stmt = $pdo->prepare("SELECT * FROM myTable WHERE id IN ($clause)");
$stmt->execute($inArr);
$resArr = $stmt->fetchAll();
if(!$resArr) exit('No rows');
var_export($resArr);
$stmt = null;

Com outros espaços reservados

$inArr = [1, 3, 5];
$clause = implode(',', array_fill(0, count($inArr), '?')); //create 3 question marks
$fullArr = array_merge($inArr, [5]); //merge WHERE IN array with other value(s)
$stmt = $pdo->prepare("SELECT * FROM myTable WHERE id IN ($clause) AND id < ?");
$stmt->execute($fullArr);
$resArr = $stmt->fetchAll();
if(!$resArr) exit('No rows');
var_export($resArr);
$stmt = null;

Múltiplas instruções preparadas em transações

Se você quiser garantir que várias chamadas SQL sejam simultâneas, use as transações. Isso garante que todas as suas operações ou nenhuma delas serão bem-sucedidas. Por exemplo, isso pode ser útil para transferir uma linha para uma tabela diferente. Você vai querer copiar a linha para a nova tabela e excluir a outra. Se uma das operações falhar, será necessário reverter para seu estado anterior.

try {
  $pdo->beginTransaction();
  $stmt1 = $pdo->prepare("INSERT INTO myTable (name, location) VALUES (?, ?)");
  if(!$stmt1->execute(["Rick", $_POST['location']])) {
    throw new Exception('Statement 1 Failed');
  }
  $stmt2 = $pdo->prepare("UPDATE myTable SET age = ? WHERE id = ?")
  if(!$stmt2->execute([$_POST['age'], 27])) {
    throw new Exception('Statement 2 Failed');
  }
  $stmt1 = null;
  $stmt2 = null;
  $pdo->commit();
} catch(Exception $e) {
  $pdo->rollback();
  error_log($e);
}

Reutilizar o mesmo modelo, valores diferentes

try {
  $pdo->beginTransaction();
  $stmt = $pdo->prepare("INSERT INTO myTable (name, age) VALUES (?, ?)");
  if(!$stmt->execute(["Joe", 19])) {
    throw new Exception('Statement 1 Failed');
  }
  if(!$stmt->execute(["Ryan", 44])) {
    throw new Exception('Statement 2 Failed');
  }
  $stmt = null;
  $pdo->commit();
} catch(Exception $e) {
  $pdo->rollback();
  error_log($e);
}

Tratamento de erros

Se você ativou erros e forçou-os a serem exceções, a maneira mais fácil de lidar com seus erros é colocando-os em um bloco try / catch. Você não deve envolver cada consulta de banco de dados em seu próprio try/catchbloco. Em vez disso, use apenas um único e global.

try {
  $stmt = $pdo->prepare("INSERT INTO myTable (name, age) VALUES (?, ?)");
  if(!$stmt->execute([$_POST['name'], $_POST['age']])) {
    throw new Exception('Execute Failed');
  }
  $stmt = null;
} catch(Exception $e) {
  error_log($e);
  exit('Error inserting');
}

Outra maneira de lidar com as exceções é criando um manipulador de exceções definido pelo usuário, que mencionei anteriormente . Você adicionaria o seguinte em cada página após ainclusão pdo_connect.php. Desta forma, você pode deixar de fora try/catchquase todas as suas consultas, exceto para transações, que você lançaria uma exceção depois de catchse algo deu errado

//include pdo_connect.php
set_exception_handler(function($e) {
  error_log($e);
  exit('Error inserting');
});
$stmt = $pdo->prepare("INSERT INTO myTable (name, age) VALUES (?, ?)");
if(!$stmt->execute([$_POST['name'], $_POST['age']])) {
  throw new Exception('Execute Failed');
}
$stmt = null;

Você deve ter notado que estou lançando uma exceção para executar se for fasly, o que parece redundante, pois  ativamos o tratamento de erros na forma de exceções. No entanto, notei um comportamento estranho, que é queexecute()só pode retornar false em alguns cenários se o modo de emulação estiver desativado, que é o único modo que este tutorial está discutindo. Pode ser específico do MySQL, mas estou deixando isso em consideração, já que eu pessoalmente experimentei isso quando há muitos parâmetros para executar. Ele simplesmente retornará falso e agirá como se nada desse errado. Isso daria um comportamento especialmente indesejável nas transações, uma vez que uma consulta falharia silenciosamente, enquanto as outras funcionariam, derrotando, portanto, seu propósito de ser linearizável. É por isso que você deve verificar a verdade, caso isso aconteça. Na verdade, não consegui encontrar muita informação sobre isso, mas este StackOverflow descreve o problema muito bem. Estranhamente, se você não ligar variáveis suficientes , irá lançar uma exceção corretamente.

Alguns extras

Eu preciso de $ stmt = null?

Isto é essencialmente o mesmo que usar $stmt->close()no MySQLi e o mesmo se aplica. Não, certamente não é obrigatório, mas é considerado uma boa prática de codificação por alguns (obviamente subjetiva). Eu prefiro ser explícito e também faço os dois $stmt = nulle $pdo = null. Se você estiver fechando a conexão PDO, também deverá fechar as instruções preparadas, conforme indicado aqui . Enquanto isso não é exatamente o mesmo que usar $mysqli->close(), é bem parecido. Uma função PDO para fechar a conexão é algo que foi solicitado há anos, e é duvidoso se alguma vez for implementada.

Fechar as instruções preparadas seria útil se você estivesse reutilizando o mesmo nome de variável. Ambos não são realmente necessários, pois eles serão fechados no final da execução do script.

Então, usando instruções preparadas significa que estou seguro contra invasores?

Enquanto você está seguro de injeção de SQL, você ainda precisa validar e higienizar seus dados inseridos pelo usuário. Você pode usar uma função como filter_var () para validar antes de inseri-lo no banco de dados e htmlspecialchars () para limpar depois de recuperá-lo.


Comentários



Faça o login para enviar uma mensagem