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


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

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

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

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

  • SQL инъекция в значения
  • SQL-инъекция в предельные предложения и динамические имена таблиц
  • нет сообщений об ошибках ("почему этот запрос не работает?")
  • неработающие отчеты об ошибках (то есть ошибки всегда возникают даже при вводе кода в производство)
  • внедрение межсайтовых сценариев (XSS) в вывод значения

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

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

и не показать любую из перечисленных выше слабостей.

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

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

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

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

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

5 60

5 ответов:

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

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

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

Я решил прыгнуть пистолет и просто положить что-то вверх. Это что-то для начала. Выдает исключение при ошибке.

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();
}
/**
 * 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-атак.

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

форма.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' ?>

список.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' ?>

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

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

функция, которую я написал давно, и она служила мне хорошо, пока я не переехал в корпоративное стандартное решение на основе ООП.
Существует 2 цели для: безопасность и простота использования.

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

функция, конечно, не идеальным. Некоторые недостатки:

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

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

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
}

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