Ссылка: Что такое идеальный пример кода с использованием расширения MySQL? [закрыто]


Это делается для создания учебного ресурса сообщества . Цель состоит в том, чтобы иметь примеры хорошего кода, которые не повторяют ужасных ошибок, которые так часто можно найти в копируемом/вставляемом PHP-коде. Я попросил сделать это вики-сайтом сообщества.

Это не подразумевается как соревнование по кодированию. Речь идет не о том, чтобы найти самый быстрый или компактный способ выполнения запроса, а о том, чтобы предоставить хорошую, читаемую ссылку, особенно для новичков.

Каждый день, есть огромный поток вопросов с действительно плохими фрагментами кода, использующими семейство функций mysql_* при переполнении стека. Хотя обычно лучше всего направлять этих людей на PDO, иногда это невозможно (например, унаследованное устаревшее программное обеспечение) или реалистично ожидать (пользователи уже используют его в своем проекте).

Общие проблемы с кодом, использующим библиотеку mysql_*, включают:

  • SQL-инъекция в значения
  • SQL-инъекция в ПРЕДЕЛЬНЫЕ предложения и имена динамических таблиц
  • Нет сообщений об ошибках ("Почему этот запрос не работает?")
  • Неработающие отчеты об ошибках (то есть ошибки всегда возникают, даже когда код запущен в производство)
  • Внедрение межсайтового скриптинга (XSS) в вывод значений

Давайте напишем пример кода PHP, который выполняет следующее, используя семейство функций mysql_*:

  • Примите два значения POST, id (числовое) и name (строка)
  • Выполните запрос на ОБНОВЛЕНИЕ в таблице tablename, изменив столбец name в строке с идентификатором id
  • При сбое любезно завершите работу, но покажите подробную ошибку только в рабочем режиме. trigger_error() будет достаточно; в качестве альтернативы используйте метод по вашему выбору
  • Выведите сообщение "$name обновлено".

И не показывает какие-либо из перечисленных выше недостатков.

Это должно быть как можно проще. В идеале он не содержит никаких функций или классов. Цель состоит не в том, чтобы создайте библиотеку для копирования/вставки, но чтобы показать минимум того, что нужно сделать, чтобы сделать запросы к базе данных безопасными.

Бонусные баллы за хорошие комментарии.

Цель состоит в том, чтобы сделать этот вопрос ресурсом, на который пользователь может ссылаться при встрече с задающим вопрос, у которого плохой код (даже если он вообще не в центре внимания вопроса) или столкнулся с неудачным запросом и не знает, как его исправить.

Чтобы упредить PDO обсуждение:

Да, часто предпочтительнее направлять людей, пишущих эти вопросы, в PDO. Когда это возможно, мы должны это сделать. Это, однако, не всегда возможно - иногда спрашивающий работает над устаревшим кодом или уже прошел долгий путь с этой библиотекой и вряд ли изменит ее сейчас. Кроме того, семейство функций mysql_* совершенно безопасно при правильном использовании. Так что, пожалуйста, никаких ответов "использовать PDO" здесь.

Author: Pekka 웃, 2011-06-01

5 answers

Мой удар по нему. Старался сделать это как можно проще, сохраняя при этом некоторые удобства реального мира.

Обрабатывает юникод и использует свободное сравнение для удобства чтения. Будь милым;-)

<?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);
}
 12
Author: Znarkus, 2012-02-20 10:10:40

Я решил поторопиться и просто что-нибудь придумать. Это то, с чего стоит начать. Создает исключение при ошибке.

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();
}
 7
Author: Aaron, 2011-06-01 21:29:01
/**
 * 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);

И некоторые пояснения к комментариям:

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

С помощью mysql_select_db вы можете создавать ошибки, и их будет не так просто найти и исправить.
Например, в каком-то скрипте вы установите db1 в качестве базы данных, но в какой-то функции вам нужно установить db2 в качестве базы данных.
После вызова этой функции база данных будет переключена, и все последующие запросы в скрипте будут нарушены или будут нарушены некоторые данные в неправильной базе данных (если имена таблиц и столбцов совпадет).

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

Некоторые имена столбцов также могут быть SQL-ключевыми словами и использовать "`" символ поможет в этом.
Кроме того, все строковые значения, вставленные в запрос, должны быть заключены в кавычки ' символ.

//always use htmlspecialchars() to sanitize user's data in output
Это поможет вам предотвратить XSS-атаки.

 3
Author: OZ_, 2011-06-02 08:51:41
<?  
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' ?>
 2
Author: Your Common Sense, 2012-01-15 16:49:50

Похоже, что мой другой ответ не достиг цели вопроса.
(это тоже не соответствует некоторым требованиям, но, как видно, ни одно безопасное решение не может быть достигнуто без реализации функции обработки заполнителей, которые являются краеугольным камнем безопасных запросов)

Итак, вот еще одна попытка опубликовать краткое решение, чтобы сделать запросы mysql безопасными, но удобными.

Функция, которую я написал давным-давно, и она хорошо мне служила, пока я не перешел в корпоративный стандартное решение на основе ООП.
Необходимо было достичь 2 целей: безопасность и простота использования.

Первый, достигнутый путем внедрения заполнителей.
Второй достигается за счет внедрения заполнителей и различных типов результатов.

Функция, безусловно, не идеальная. Некоторые недостатки заключаются в следующем:

  • никакие % символы не должны быть помещены непосредственно в запрос, так как он использует синтаксис printf.
  • несколько подключений не поддерживаются.
  • нет заполнитель для идентификаторов (а также множество других удобных заполнителей).
  • опять же, нет заполнителя идентификатора!. "ORDER BY $field" дело должно быть рассмотрено вручную!
  • конечно, реализация ООП была бы намного более гибкой, имея четкие отдельные методы вместо уродливой переменной "режим", а также другие необходимые методы.

Тем не менее, это хорошо, безопасно и лаконично, нет необходимости устанавливать целую библиотеку.

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

Примеры использования

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

Как можно видеть из приведенных выше примеров видно, что основное отличие от всех кодов, когда-либо опубликованных в Stackoverflow, заключается в том, что процедуры обеспечения безопасности и извлечения данных инкапсулированы в коде функции. Таким образом, никакой ручной привязки, экранирования/цитирования или приведения, а также никакого ручного извлечения данных.

В сочетании с другой вспомогательной функцией

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

Используется так

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

Он может охватывать практически все потребности, включая пример из ОП.

 0
Author: Your Common Sense, 2012-03-27 11:30:45