Referenz: Was ist ein perfektes Codebeispiel mit der MySQL-Erweiterung? [closed]

Lesezeit: 12 Minuten

Dies ist eine zu erstellen Community-Lernressource. Das Ziel ist es, Beispiele für guten Code zu haben, die nicht die schrecklichen Fehler wiederholen, die so oft in kopiertem/eingefügtem PHP-Code gefunden werden können. Ich habe darum gebeten, dass es Community Wiki erstellt wird.

Das ist nicht als Programmierwettbewerb gedacht. Es geht nicht darum, den schnellsten oder kompaktesten Weg für eine Abfrage zu finden – es geht darum, eine gute, lesbare Referenz zu bieten, insbesondere für Neulinge.

Jeden Tag gibt es einen riesigen Zustrom von Fragen mit wirklich schlecht Codeschnipsel mit der mysql_* Funktionsfamilie auf Stack Overflow. Während es normalerweise am besten ist, diese Leute auf PDO umzuleiten, ist dies manchmal weder möglich (z. B. bei geerbter Legacy-Software) noch eine realistische Erwartung (Benutzer verwenden es bereits in ihrem Projekt).

Häufige Probleme mit Code, der die mysql_* Bibliothek umfasst:

  • SQL-Injection in Werten
  • SQL-Injection in LIMIT-Klauseln und dynamischen Tabellennamen
  • Keine Fehlermeldung (“Warum funktioniert diese Abfrage nicht?”)
  • Defekte Fehlerberichterstattung (d. h. Fehler treten immer auf, selbst wenn der Code in Produktion geht)
  • Cross-Site-Scripting (XSS)-Injektion in die Wertausgabe

Lassen Sie uns ein PHP-Codebeispiel schreiben, das Folgendes unter Verwendung von tut mySQL_* Funktionsfamilie:

  • Akzeptiere zwei POST-Werte, id (numerisch) und name (ein Faden)
  • Führen Sie eine UPDATE-Abfrage für eine Tabelle durch tablenamewechseln name Spalte in der Zeile mit der ID id
  • Bei einem Fehler gnädig beenden, aber den detaillierten Fehler nur im Produktionsmodus anzeigen. trigger_error() wird genügen; Verwenden Sie alternativ eine Methode Ihrer Wahl
  • Ausgabe der Nachricht “$name Aktualisiert.”

Und tut nicht eine der oben aufgeführten Schwächen aufweisen.

Es sollte sein so einfach wie möglich. Es enthält idealerweise keine Funktionen oder Klassen. Das Ziel ist nicht, eine kopier-/einfügbare Bibliothek zu erstellen, sondern zeigen das Minimum dessen, was getan werden muss, um Datenbankabfragen sicher zu machen.

Bonuspunkte für gute Kommentare.

Das Ziel ist, diese Frage zu einer Ressource zu machen, auf die ein Benutzer verlinken kann, wenn er auf einen Fragesteller stößt, der schlechten Code hat (obwohl er überhaupt nicht im Mittelpunkt der Frage steht) oder mit einer fehlgeschlagenen Abfrage konfrontiert wird und dies nicht tut wissen, wie man es repariert.

Um einer PDO-Diskussion zuvorzukommen:

Ja, es ist oft vorzuziehen, die Personen, die diese Fragen schreiben, an PDO zu verweisen. Wenn es eine Option ist, sollten wir dies tun. Es ist jedoch nicht immer möglich – manchmal arbeitet der Fragesteller an Legacy-Code oder hat mit dieser Bibliothek bereits einen langen Weg zurückgelegt und wird ihn wahrscheinlich jetzt nicht ändern. Auch der mysql_* Funktionsfamilie ist bei sachgemäßer Anwendung absolut sicher. Also bitte keine “Use PDO”-Antworten hier.

Mein Stich darauf. Ich habe versucht, es so einfach wie möglich zu halten, aber dennoch einige Annehmlichkeiten aus der realen Welt beizubehalten.

Behandelt Unicode und verwendet losen Vergleich für die Lesbarkeit. Sei nett 😉

<?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);
}

  • @Pekka Aw Mist, behoben. Danke!

    – Znarkus

    1. Juni 2011 um 9:14 Uhr

  • -1 für #error_reporting(~E_ALL); ... to disable error output – schlechter Rat. Kann nicht mehr abstimmen, sonst würde ich -1 dafür @ Operator.

    – OZ_

    1. Juni 2011 um 9:14 Uhr

  • @OZ_ Ich verstehe Ihren Standpunkt zu @, aber warum ist es schlecht, die Fehlerberichterstattung im Produktionsmodus auszuschalten?

    – Pekka

    1. Juni 2011 um 9:17 Uhr

  • @OZ_: Warum ist das ein Problem? Im Produktionsmodus schadet es nicht, die Fehlerausgabe zu deaktivieren, solange Sie sie an anderer Stelle protokollieren. Und die Verwendung von @ ist die einzige Möglichkeit, unangenehme Ausgaben zu verbergen mysql_connect. Bitte ueberlege es Dir nochmal.

    – Znarkus

    1. Juni 2011 um 9:18 Uhr

  • @Pekka, @Znarkus, error_reporting(0); es ist ein sehr schlechter Rat, weil nicht immer Fehler von der Fehlerbehandlungsroutine behandelt werden, da diese Behandlungsroutine nicht immer sogar codiert sein wird. ini_set('display_errors',0) – Diese Variante kann im Produktionscode verwendet werden, um Text von Fehlern auszublenden, und nur dann, wenn Fehler behandelt werden.

    – OZ_

    1. Juni 2011 um 9:26 Uhr

Ich beschloss, die Waffe zu springen und einfach etwas aufzustellen. Es ist etwas, womit man anfangen kann. Löst bei einem Fehler eine Ausnahme aus.

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();
}

  • Obwohl es funktioniert, ist es für Neulinge und Copy-Paste-Leute viel zu komplex.

    – Carlos Campderros

    1. Juni 2011 um 8:40 Uhr

  • @Aaron – hast du die Frage und Kommentare gelesen? Lesen Sie sie richtig durch, nur Kopieren und Einfügen ist nicht die Lösung für diese Frage.

    – Sujit Agarwal

    1. Juni 2011 um 8:41 Uhr


  • Bezüglich: Bei einem Fehler wird es gnädig beendet, zeigt aber den detaillierten Fehler an nur im Produktionsmodus. Fügen Sie vielleicht eine Bedingung um die hinzu echo $e->getMessage();?

    – Yoshi

    1. Juni 2011 um 8:45 Uhr


  • @Aaron – Ich habe es nicht für deine Antwort erwähnt, sondern wollte, dass andere ihm folgen. Ich kann seine eigene Antwort verstehen.

    – Sujit Agarwal

    1. Juni 2011 um 8:49 Uhr

  • Ähm, würde dies nicht den $id-Wert name und $name der id zuweisen? Abgesehen von der Tatsache, dass Sie updateTableName aufrufen, wenn Ihre Funktion updateTablenameName :p heißt.

    – wimvds

    1. Juni 2011 um 11:02 Uhr


/**
 * 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);

Und einige Erläuterungen zu den Kommentaren:

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

Durch die Verwendung von mysql_select_db können Sie Fehler erzeugen, und es wird nicht so einfach sein, sie zu finden und zu beheben.
Beispielsweise werden Sie in einigen Skripten db1 als Datenbank festlegen, aber in einigen Funktionen müssen Sie db2 als Datenbank festlegen.
Nach dem Aufruf dieser Funktion wird die Datenbank umgeschaltet, und alle folgenden Abfragen im Skript werden beschädigt oder einige Daten in der falschen Datenbank beschädigt (wenn die Namen von Tabellen und Spalten übereinstimmen).

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

Einige Spaltennamen können auch SQL-Schlüsselwörter sein, und die Verwendung von “`” Symbol hilft dabei.
Außerdem sollten alle Zeichenfolgenwerte, die in die Abfrage eingefügt werden, in Anführungszeichen gesetzt werden Symbol.

//always use htmlspecialchars() to sanitize user's data in output

Es wird Ihnen helfen, zu verhindern XSS-Angriffe.

  • Schön, ich mag es! Ich mag auch den Fehlerbehandler, aber vielleicht ist es für einen Neuling zu viel, um ihn hier zu verstehen – wäre es eine Option, ihn vorerst zu entfernen oder als “optional” nach unten zu verschieben? (Ich habe die i in mysqli)

    – Pekka

    1. Juni 2011 um 8:57 Uhr


  • Fühlen Sie sich frei, Fehler in meinem gebrochenen Englisch zu korrigieren 🙂

    – OZ_

    1. Juni 2011 um 8:57 Uhr

  • @Pekka Ich werde versuchen, mich mehr auf mysql_ zu konzentrieren, werde es jetzt bearbeiten.

    – OZ_

    1. Juni 2011 um 8:59 Uhr

  • Ich würde die Zeichenfolgenverkettung durch ersetzen sprintf. Dadurch wird das Zeug viel besser lesbar und mögliche Syntaxfehler (fehlende Anführungszeichen und so weiter) besser sichtbar.

    – Yoshi

    1. Juni 2011 um 9:04 Uhr

  • @Yoshi Ich denke, es sind persönliche Vorlieben und es ist definitiv nicht einfacher.

    – OZ_

    1. Juni 2011 um 9:07 Uhr

<?  
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="https://stackoverflow.com/questions/6198104/?">Return to the list</a>
</form>
<? include 'tpl_bottom.php' ?>

list.php

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

Sieht so aus, als hätte meine andere Antwort das Ziel der Frage verfehlt.
(Dieser erfüllt auch einige Anforderungen nicht, aber wie man sieht, kann keine sichere Lösung erreicht werden, ohne eine Funktion zum Verarbeiten von Platzhaltern zu implementieren, die der Eckpfeiler der sicheren Abfragen sind.)

Also, hier ist ein weiterer Versuch, eine prägnante Lösung zu posten, um MySQL-Abfragen sicher und dennoch praktisch zu machen.

Eine Funktion, die ich vor langer Zeit geschrieben habe und die mir gute Dienste geleistet hat, bis ich zur unternehmensweiten Standard-OOP-basierten Lösung gewechselt bin.
Es galt 2 Ziele zu verfolgen: Sicherheit und Benutzerfreundlichkeit.

Die erste wurde durch die Implementierung von Platzhaltern erreicht.
Die zweite wird durch die Implementierung von Platzhaltern und verschiedenen Ergebnistypen erreicht.

Die Funktion sicher nicht optimal. Einige Nachteile sind:

  • Nein % Zeichen müssen direkt in die Abfrage eingefügt werden, da sie die printf-Syntax verwendet.
  • keine Mehrfachverbindungen unterstützt.
  • kein Platzhalter für die Bezeichner (sowie viele andere praktische Platzhalter).
  • aufs Neue, kein Bezeichnerplatzhalter!. "ORDER BY $field" Fall müssen manuell bearbeitet werden!
  • Natürlich wäre eine OOP-Implementierung viel flexibler, da sie statt hässlicher “Modus”-Variablen sowie anderer notwendiger Methoden ordentliche, unterschiedliche Methoden hätte.

Dennoch ist es gut, sicher und übersichtlich, ohne dass eine ganze Bibliothek installiert werden muss.

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;
}
?>

Anwendungsbeispiele

$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);

Wie aus den obigen Beispielen ersichtlich ist, besteht der Hauptunterschied zu allen Codes, die jemals in Stackoverflow veröffentlicht wurden, darin, dass sowohl Sicherheits- als auch Datenabrufroutinen im Funktionscode gekapselt sind. Also kein manuelles Binding, Escaping/Quote oder Casting, sowie kein manueller Datenabruf.

kombiniert mit anderen Hilfsfunktionen

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); 
}

so verwendet

$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
}

Es kann fast jeden Bedarf abdecken, einschließlich des Beispielfalls aus dem OP.

990260cookie-checkReferenz: Was ist ein perfektes Codebeispiel mit der MySQL-Erweiterung? [closed]

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy