60 votes

Référence: Qu'est-ce qu'un exemple de code parfait utilisant l'extension MySQL?

C'est de créer une communauté de ressources d'apprentissage. Le but est d'avoir des exemples de bonnes code qui ne répètent pas les terribles erreurs que l'on retrouve si souvent dans les copier/coller le code PHP. J'ai demandé à être faite Wiki de la Communauté.

Ce n'est pas conçu comme une codification du concours. Il n'est pas sur de trouver le plus rapide ou le plus compact de façon à faire une requête, il est de fournir une bonne, lisible référence, en particulier pour les débutants.

Chaque jour, il y a un énorme afflux de questions avec vraiment mauvais fragments de code à l'aide de l' mysql_* famille de fonctions sur un Débordement de Pile. Alors qu'il est habituellement le meilleur pour diriger ces personnes vers PDO, il est parfois pas possible (par exemple, hérité des logiciels), ni une attente réaliste (les utilisateurs utilisent déjà dans leur projet).

Commune des problèmes avec le code à l'aide de l' mysql_* bibliothèque:

  • Injection SQL dans les valeurs
  • Injection SQL dans la LIMITE des clauses et dynamique des noms de table
  • Pas de rapport d'erreur ("Pourquoi cette requête ne fonctionne pas?")
  • Cassé les rapports d'erreurs (erreurs de toujours se produire même lorsque le code est mis en production)
  • Cross-site scripting (XSS) injection de la valeur de sortie

Voici un exemple de code PHP qui effectue les opérations suivantes à l'aide de la mySQL_* famille de fonctions:

  • Accepte deux valeurs POST, id (numérique) et name (une chaîne)
  • Faire une requête de mise à JOUR sur une table tablename, modification de l' name colonne dans la ligne avec l'ID id
  • En cas d'échec, la sortie gracieusement, mais de montrer l'erreur détaillé que dans le mode de production. trigger_error() suffira; vous pouvez également utiliser une méthode de votre choix
  • Sortie le message "$name mis à jour."

Et ne pas montrer les faiblesses énumérées ci-dessus.

Il devrait être aussi simple que possible. Idéalement ne contient pas de fonctions ou de classes. Le but n'est pas de créer un copier/pasteable de la bibliothèque, mais de montrer que le minimum de ce qui doit être fait pour rendre la base de données de l'interrogation de sécurité.

Des points Bonus pour les bons commentaires.

L'objectif est de faire de cette question une ressource que l'utilisateur peut associer lors de la rencontre d'une question de personne qui a un mauvais code (même si ce n'est pas l'objet de la question) ou est confronté à l'échec de la requête et ne sait pas comment le résoudre.

Pour anticiper AOP discussion:

Oui, il sera souvent préférable d'orienter les personnes en écrivant ces questions à l'AOP. Lorsque c'est possible, nous devrions le faire. Il est, cependant, pas toujours possible - parfois, la question asker est de travailler sur du code legacy, ou a déjà parcouru un long chemin avec cette bibliothèque, et il est peu probable de le changer maintenant. Aussi, l' mysql_* famille de fonctions est parfaitement sans danger s'il est utilisé correctement. Donc pas de "utiliser PDO" de réponses ici, s'il vous plaît.

12voto

Znarkus Points 5025

Mon coup de poignard à cela. Essayé de rester aussi simple que possible, tout en maintenant certaines commodités du monde réel.

Gère unicode et utilise des comparaisons lâches pour la lisibilité. Sois gentil ;-)

 <?php

header('Content-type: text/html; charset=utf-8');
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 1);
// display_errors can be changed to 0 in production mode to
// suppress PHP's error messages

/*
Can be used for testing
$_POST['id'] = 1;
$_POST['name'] = 'Markus';
*/

$config = array(
    'host' => '127.0.0.1', 
    'user' => 'my_user', 
    'pass' => 'my_pass', 
    'db' => 'my_database'
);

# Connect and disable mysql error output
$connection = @mysql_connect($config['host'], 
    $config['user'], $config['pass']);

if (!$connection) {
    trigger_error('Unable to connect to database: ' 
        . mysql_error(), E_USER_ERROR);
}

if (!mysql_select_db($config['db'])) {
    trigger_error('Unable to select db: ' . mysql_error(), 
        E_USER_ERROR);
}

if (!mysql_set_charset('utf8')) {
    trigger_error('Unable to set charset for db connection: ' 
        . mysql_error(), E_USER_ERROR);
}

$result = mysql_query(
    'UPDATE tablename SET name = "' 
    . mysql_real_escape_string($_POST['name']) 
    . '" WHERE id = "' 
    . mysql_real_escape_string($_POST['id']) . '"'
);

if ($result) {
    echo htmlentities($_POST['name'], ENT_COMPAT, 'utf-8') 
        . ' updated.';
} else {
    trigger_error('Unable to update db: ' 
        . mysql_error(), E_USER_ERROR);
}
 

7voto

Aaron Points 463

J'ai décidé de sauter le pistolet et de mettre quelque chose en place. C'est quelque chose pour commencer. Lève une exception en cas d'erreur.

 function executeQuery($query, $args) {
    $cleaned = array_map('mysql_real_escape_string', $args);

    if($result = mysql_query(vsprintf($query, $cleaned))) {
        return $result;
    } else {
        throw new Exception('MySQL Query Error: ' . mysql_error());
    }
}

function updateTablenameName($id, $name) {
    $query = "UPDATE tablename SET name = '%s' WHERE id = %d";

    return executeQuery($query, array($name, $id));
}

try {
    updateTablenameName($_POST['id'], $_POST['name']);
} catch(Exception $e) {
    echo $e->getMessage();
    exit();
}
 

3voto

OZ_ Points 7398
/**
 * Rule #0: never trust users input!
 */

//sanitize integer value
$id = intval($_GET['id']);
//sanitize string value;
$name = mysql_real_escape_string($_POST['name']);
//1. using `dbname`. is better than using mysql_select_db()
//2. names of tables and columns should be quoted by "`" symbol
//3. each variable should be sanitized (even in LIMIT clause)
$q = mysql_query("UPDATE `dbname`.`tablename` SET `name`='".$name."' WHERE `id`='".$id."' LIMIT 0,1 ");
if ($q===false)
{
    trigger_error('Error in query: '.mysql_error(), E_USER_WARNING);
}
else
{
    //be careful! $name contains user's data, remember Rule #0
    //always use htmlspecialchars() to sanitize user's data in output
    print htmlspecialchars($name).' updated';
}

########################################################################
//Example, how easily is to use set_error_handler() and trigger_error()
//to control error reporting in production and dev-code
//Do NOT use error_reporting(0) or error_reporting(~E_ALL) - each error
//should be fixed, not muted
function err_handler($errno, $errstr, $errfile, $errline)
{
    $hanle_errors_print = E_ALL & ~E_NOTICE;

    //if we want to print this type of errors (other types we can just write in log-file)
    if ($errno & $hanle_errors_print)
    {
        //$errstr can contain user's data, so... Rule #0
        print PHP_EOL.'Error ['.$errno.'] in file '.$errfile.' in line '.$errline
              .': '.htmlspecialchars($errstr).PHP_EOL;
    }
    //here you can write error into log-file
}

set_error_handler('err_handler', E_ALL & ~E_NOTICE & E_USER_NOTICE & ~E_STRICT & ~E_DEPRECATED);

Et quelques explications de commentaires:

//1. using `dbname`. is better than using mysql_select_db()

Avec l'aide mysql_select_db vous pouvez créer des erreurs, et il ne sera pas si facile de les trouver et de les corriger.
Par exemple, dans certains script, vous allez définir db1 comme base de données, mais dans une fonction, vous devez définir db2 en tant que base de données.
Après l'appel de cette fonction, base de données sera allumé, et toutes les requêtes suivantes dans le script sera cassé ou va cassé quelques données dans la mauvaise base de données (si les noms de tables et de colonnes coïncidera).

//2. names of tables and columns should be quoted by "`" symbol 

Certains noms de colonnes peuvent être également SQL de mots-clés, et à l'aide de "`" symbole va vous aider.
Également, l'ensemble de la chaîne de valeurs, inséré à la requête, doit être cité par ' symbole.

//always use htmlspecialchars() to sanitize user's data in output
Il vous aide à éviter les attaques de type XSS-attaques.

2voto

 <?  
mysql_connect(); 
mysql_select_db("new"); 
$table = "test"; 
if($_SERVER['REQUEST_METHOD']=='POST') {
  $name = mysql_real_escape_string($_POST['name']); 
  if ($id = intval($_POST['id'])) { 
    $query="UPDATE $table SET name='$name' WHERE id=$id"; 
  } else { 
    $query="INSERT INTO $table SET name='$name'"; 
  } 
  mysql_query($query) or trigger_error(mysql_error()." in ".$query); 
  header("Location: http://".$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF']);  
  exit;  
}  
if (!isset($_GET['id'])) {
  $LIST=array(); 
  $query="SELECT * FROM $table";  
  $res=mysql_query($query); 
  while($row=mysql_fetch_assoc($res)) $LIST[]=$row; 
  include 'list.php'; 
} else {
  if ($id=intval($_GET['id'])) { 
    $query="SELECT * FROM $table WHERE id=$id";  
    $res=mysql_query($query); 
    $row=mysql_fetch_assoc($res); 
    foreach ($row as $k => $v) $row[$k]=htmlspecialchars($v); 
  } else { 
    $row['name']=''; 
    $row['id']=0; 
  } 
  include 'form.php'; 
}  
?>
 

form.php

 <? include 'tpl_top.php' ?>
<form method="POST">
<input type="text" name="name" value="<?=$row['name']?>"><br>
<input type="hidden" name="id" value="<?=$row['id']?>">
<input type="submit"><br>
<a href="?">Return to the list</a>
</form>
<? include 'tpl_bottom.php' ?>
 

list.php

 <? include 'tpl_top.php' ?>
<a href="?id=0">Add item</a>
<? foreach ($LIST as $row): ?>
<li><a href="?id=<?=$row['id']?>"><?=$row['name']?></a>
<? endforeach ?>
<? include 'tpl_bottom.php' ?>
 

0voto

Comme dirait ma réponse raté le but de la question.
(celui-ci ne répond pas à certaines exigences, mais comme on peut le voir, aucune solution ne peut être atteint sans la mise en œuvre d'une fonction pour traiter les espaces réservés, qui sont la pierre angulaire de la sécurité des requêtes).

Donc, voici une autre tentative de post concis solution pour faire des requêtes mysql en sécurité, et pourtant à portée de main.

Une fonction que j'ai écrit il y a longtemps et il m'a bien servi jusqu'à ce que je déménage à le corporative standard de la programmation orientée objet à base de solution.
Il y avait 2 buts à poursuivre en ce qui concerne: la sécurité et la facilité d'utilisation.

D'abord celui obtenu par la mise en œuvre des espaces réservés.
Deuxième réalisé par la mise en œuvre des espaces et des différents types de résultats.

La fonction sûrement pas l'idéal. Certains des inconvénients sont:

  • pas de % caractères doivent être placés dans la requête directement que c'est à l'aide de printf syntaxe.
  • pas de multiples connexions prises en charge.
  • pas d'espace réservé pour les identificateurs (ainsi que de nombreux autres pratique des espaces réservés).
  • encore une fois, aucun identifiant de l'espace réservé!. "ORDER BY $field" cas avoir à être manipulés manuellement!
  • bien sûr, un POO de la mise en œuvre serait beaucoup plus flexible, ayant soigné des méthodes distinctes plutôt laid "mode" variable ainsi que d'autres méthodes nécessaires.

Pourtant, il est bon, coffre-fort et concis, pas besoin d'installer une bibliothèque entière.

function dbget() {
  /*
  usage: dbget($mode, $query, $param1, $param2,...);
  $mode - "dimension" of result:
  0 - resource
  1 - scalar
  2 - row
  3 - array of rows
  */
  $args = func_get_args();
  if (count($args) < 2) {
    trigger_error("dbget: too few arguments");
    return false;
  }
  $mode  = array_shift($args);
  $query = array_shift($args);
  $query = str_replace("%s","'%s'",$query); 

  foreach ($args as $key => $val) {
    $args[$key] = mysql_real_escape_string($val);
  }

  $query = vsprintf($query, $args);
  if (!$query) return false;

  $res = mysql_query($query);
  if (!$res) {
    trigger_error("dbget: ".mysql_error()." in ".$query);
    return false;
  }

  if ($mode === 0) return $res;

  if ($mode === 1) {
    if ($row = mysql_fetch_row($res)) return $row[0];
    else return NULL;
  }

  $a = array();
  if ($mode === 2) {
    if ($row = mysql_fetch_assoc($res)) return $row;
  }
  if ($mode === 3) {
    while($row = mysql_fetch_assoc($res)) $a[]=$row;
  }
  return $a;
}
?>

exemples d'utilisation

$name = dbget(1,"SELECT name FROM users WHERE id=%d",$_GET['id']);
$news = dbget(3,"SELECT * FROM news WHERE title LIKE %s LIMIT %d,%d",
              "%$_GET[search]%",$start,$per_page);

Comme on peut le voir dans les exemples ci-dessus, la principale différence avec tous les codes affichés dans Stackoverflow, à la fois la sécurité et la récupération des données de routine sont encapsulées dans le code de la fonction. Donc, pas de manuel de liaison, échappant/citations ou de coulée, ainsi que l'absence de manuel de récupération de données.

combiné avec d'autres fonction d'assistance

function dbSet($fields,$source=array()) {
  $set = '';
  if (!$source) $source = &$_POST;
  foreach ($fields as $field) {
    if (isset($source[$field])) {
      $set.="`$field`='".mysql_real_escape_string($source[$field])."', ";
    }
  }
  return substr($set, 0, -2); 
}

utilisé comme ceci

$fields = explode(" ","name surname lastname address zip phone regdate");
$_POST['regdate'] = $_POST['y']."-".$_POST['m']."-".$_POST['d'];
$sql = "UPDATE $table SET ".dbSet($fields).", stamp=NOW() WHERE id=%d";
$res = dbget(0,$sql, $_POST['id']);
if (!$res) {
  _503;//calling generic 503 error function
}

il peut couvrir presque tous les besoins, y compris le cas de l'exemple de l'OP.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X