Почему мне не использовать функции mysql_ * в PHP?

avatar
Madara's Ghost
12 октября 2012 в 13:18
240393
14
2625

Каковы технические причины, по которым нельзя использовать функции mysql_*? (например, mysql_query(), mysql_connect() или mysql_real_escape_string())?

Зачем мне использовать что-то еще, даже если они работают на моем сайте?

Если они не работают на моем сайте, почему я получаю такие ошибки, как

Предупреждение: mysql_connect (): нет такого файла или каталога

Источник
Bimal Poudel
2 декабря 2017 в 08:04
2

Ошибка должна быть похожей на: Неустранимая ошибка: Неперехваченная ошибка: Вызов неопределенной функции mysql_connect () ...

Sasa1234
17 декабря 2017 в 05:43
34

Одно только устаревание - достаточная причина, чтобы их избегать

Ответы (14)

avatar
Quentin
21 июля 2019 в 10:15
2174

Расширение MySQL:

  • Не находится в активной разработке
  • официально устарел с PHP 5.5 (выпущен в июне 2013 г.).
  • был удален полностью с PHP 7.0 (выпущен в декабре 2015 г.)
    • Это означает, что по состоянию на 31 декабря 2018 года он не существует ни в одной поддерживаемой версии PHP. Если вы используете версию PHP, которая его поддерживает, значит, вы используете версию, в которой не устранены проблемы с безопасностью.
  • Отсутствует интерфейс OO
  • Не поддерживает:
    • Неблокирующие асинхронные запросы
    • Подготовленные отчеты или параметризованные запросы
    • Хранимые процедуры
    • Несколько операторов
    • Транзакции
    • «новый» метод аутентификации по паролю (включен по умолчанию в MySQL 5.6; требуется в 5.7)
    • Любая новая функциональность в MySQL 5.1 или более поздней версии

Поскольку он устарел, его использование делает ваш код менее надежным в будущем.

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

См. сравнение расширений SQL .

Tim Post
12 октября 2012 в 13:26
304

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

Lightness Races in Orbit
24 декабря 2012 в 14:29
122

Устаревание - это не волшебная палочка, которую все, кажется, думают. Сама PHP когда-нибудь там не будет, но мы полагаемся на инструменты, которые есть в нашем распоряжении сегодня. Когда нам нужно будет сменить инструменты, мы это сделаем.

Quentin
24 декабря 2012 в 17:43
144

@LightnessRacesinOrbit - Устаревание - это не волшебная палочка, это флаг, который говорит: «Мы понимаем, что это отстой, поэтому мы не собираемся поддерживать его надолго». Хотя лучшая проверка кода в будущем - хорошая причина отказаться от устаревших функций, но это не единственная (или даже основная). Меняйте инструменты, потому что есть инструменты получше, а не потому, что вы вынуждены это делать. (А смена инструментов до того, как вас заставят это сделать, означает, что вы не изучаете новые только потому, что ваш код перестал работать и нуждается в исправлении вчера… это наихудшее время для изучения новых инструментов).

avatar
Pratik Chheda
13 мая 2022 в 08:04
0

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

avatar
MuhammadAliDEV
19 октября 2021 в 04:10
1

Не используйте mysql, потому что он устарел, используйте вместо него Mysqli.

Что означает устаревшее:

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

​​При использовании устаревших функций может возникнуть несколько общих проблем:

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

2. Отображаются предупреждающие сообщения об устаревании : эти сообщения обычно не мешают работе сайта. Однако в некоторых случаях они могут нарушить процесс отправки заголовков сервером.

Например: это может вызвать проблемы со входом в систему (файлы cookie / сеансы не устанавливаются должным образом) или проблемы с пересылкой (перенаправления 301/302/303).

имейте в виду, что:

-Устаревшее программное обеспечение по-прежнему является частью программного обеспечения.

-Устаревший код - это просто статус (метка) кода.

Ключевые различия в MYSQL и MYSQLI mysql *

  • старый драйвер базы данных
  • MySQL можно использовать только процедурно
  • Нет защиты от атаки SQL-инъекции
  • Устарело в PHP 5.5.0 и было удалено в PHP 7

mysqli

  • новый драйвер базы данных
  • В настоящее время используется
  • подготовленные операторы защищают от атак
avatar
Pavel Tzonkov
9 июня 2017 в 06:24
9

Можно определить почти все функции mysql_* с помощью mysqli или PDO. Просто включите их поверх своего старого PHP-приложения, и оно будет работать на PHP7. Мое решение здесь.

<?php

define('MYSQL_LINK', 'dbl');
$GLOBALS[MYSQL_LINK] = null;

function mysql_link($link=null) {
    return ($link === null) ? $GLOBALS[MYSQL_LINK] : $link;
}

function mysql_connect($host, $user, $pass) {
    $GLOBALS[MYSQL_LINK] = mysqli_connect($host, $user, $pass);
    return $GLOBALS[MYSQL_LINK];
}

function mysql_pconnect($host, $user, $pass) {
    return mysql_connect($host, $user, $pass);
}

function mysql_select_db($db, $link=null) {
    $link = mysql_link($link);
    return mysqli_select_db($link, $db);
}

function mysql_close($link=null) {
    $link = mysql_link($link);
    return mysqli_close($link);
}

function mysql_error($link=null) {
    $link = mysql_link($link);
    return mysqli_error($link);
}

function mysql_errno($link=null) {
    $link = mysql_link($link);
    return mysqli_errno($link);
}

function mysql_ping($link=null) {
    $link = mysql_link($link);
    return mysqli_ping($link);
}

function mysql_stat($link=null) {
    $link = mysql_link($link);
    return mysqli_stat($link);
}

function mysql_affected_rows($link=null) {
    $link = mysql_link($link);
    return mysqli_affected_rows($link);
}

function mysql_client_encoding($link=null) {
    $link = mysql_link($link);
    return mysqli_character_set_name($link);
}

function mysql_thread_id($link=null) {
    $link = mysql_link($link);
    return mysqli_thread_id($link);
}

function mysql_escape_string($string) {
    return mysql_real_escape_string($string);
}

function mysql_real_escape_string($string, $link=null) {
    $link = mysql_link($link);
    return mysqli_real_escape_string($link, $string);
}

function mysql_query($sql, $link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, $sql);
}

function mysql_unbuffered_query($sql, $link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, $sql, MYSQLI_USE_RESULT);
}

function mysql_set_charset($charset, $link=null){
    $link = mysql_link($link);
    return mysqli_set_charset($link, $charset);
}

function mysql_get_host_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_host_info($link);
}

function mysql_get_proto_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_proto_info($link);
}
function mysql_get_server_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_server_info($link);
}

function mysql_info($link=null) {
    $link = mysql_link($link);
    return mysqli_info($link);
}

function mysql_get_client_info() {
    $link = mysql_link();
    return mysqli_get_client_info($link);
}

function mysql_create_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "CREATE DATABASE `$db`");
}

function mysql_drop_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "DROP DATABASE `$db`");
}

function mysql_list_dbs($link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, "SHOW DATABASES");
}

function mysql_list_fields($db, $table, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    $table = str_replace('`', '', mysqli_real_escape_string($link, $table));
    return mysqli_query($link, "SHOW COLUMNS FROM `$db`.`$table`");
}

function mysql_list_tables($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "SHOW TABLES FROM `$db`");
}

function mysql_db_query($db, $sql, $link=null) {
    $link = mysql_link($link);
    mysqli_select_db($link, $db);
    return mysqli_query($link, $sql);
}

function mysql_fetch_row($qlink) {
    return mysqli_fetch_row($qlink);
}

function mysql_fetch_assoc($qlink) {
    return mysqli_fetch_assoc($qlink);
}

function mysql_fetch_array($qlink, $result=MYSQLI_BOTH) {
    return mysqli_fetch_array($qlink, $result);
}

function mysql_fetch_lengths($qlink) {
    return mysqli_fetch_lengths($qlink);
}

function mysql_insert_id($qlink) {
    return mysqli_insert_id($qlink);
}

function mysql_num_rows($qlink) {
    return mysqli_num_rows($qlink);
}

function mysql_num_fields($qlink) {
    return mysqli_num_fields($qlink);
}

function mysql_data_seek($qlink, $row) {
    return mysqli_data_seek($qlink, $row);
}

function mysql_field_seek($qlink, $offset) {
    return mysqli_field_seek($qlink, $offset);
}

function mysql_fetch_object($qlink, $class="stdClass", array $params=null) {
    return ($params === null)
        ? mysqli_fetch_object($qlink, $class)
        : mysqli_fetch_object($qlink, $class, $params);
}

function mysql_db_name($qlink, $row, $field='Database') {
    mysqli_data_seek($qlink, $row);
    $db = mysqli_fetch_assoc($qlink);
    return $db[$field];
}

function mysql_fetch_field($qlink, $offset=null) {
    if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    return mysqli_fetch_field($qlink);
}

function mysql_result($qlink, $offset, $field=0) {
    if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    $row = mysqli_fetch_array($qlink);
    return (!is_array($row) || !isset($row[$field]))
        ? false
        : $row[$field];
}

function mysql_field_len($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->length : false;
}

function mysql_field_name($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field))
        return false;
    return empty($field->orgname) ? $field->name : $field->orgname;
}

function mysql_field_table($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field))
        return false;
    return empty($field->orgtable) ? $field->table : $field->orgtable;
}

function mysql_field_type($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->type : false;
}

function mysql_free_result($qlink) {
    try {
        mysqli_free_result($qlink);
    } catch (Exception $e) {
        return false;
    }
    return true;
}
amarnath
9 июня 2017 в 07:07
1

Вместо того, чтобы показывать ссылку на ваше решение, добавьте их здесь в качестве ответа.

avatar
Ani Menon
7 сентября 2016 в 15:06
24

Я считаю приведенные выше ответы очень длинными, поэтому резюмирую:

Расширение mysqli имеет ряд преимущества, ключевые улучшения за расширение mysql:

  • Объектно-ориентированный интерфейс
  • Поддержка подготовленных отчетов
  • Поддержка нескольких операторов
  • Поддержка транзакций
  • Расширенные возможности отладки
  • Поддержка встроенного сервера

Источник: Обзор MySQLi


Как объяснено в ответах выше, альтернативами mysql являются mysqli и PDO (объекты данных PHP).

  • API поддерживает подготовленные операторы на стороне сервера: поддерживается MYSQLi и PDO
  • API поддерживает подготовленные операторы на стороне клиента: поддерживается только PDO
  • API поддерживает хранимые процедуры: MySQLi и PDO
  • API поддерживает несколько выражений и все функции MySQL 4.1+ - поддерживается MySQLi и в основном PDO

MySQLi и PDO были представлены в PHP 5.0, тогда как MySQL был представлен до PHP 3.0. Следует отметить, что MySQL включен в PHP5.x, хотя в более поздних версиях не рекомендуется.

Your Common Sense
7 сентября 2016 в 15:13
2

Ваш ответ слишком длинный, а реальное резюме - «mysql ext больше нет». Это все

Ani Menon
7 сентября 2016 в 15:16
1

@YourCommonSense Мой ответ - почему mysqli заменил mysql. Дело не в том, чтобы сказать, что Mysqli существует сегодня, поэтому используйте его ... Все это знают!

Your Common Sense
7 сентября 2016 в 15:19
1

Ну, кроме того факта, что никто не спрашивал, почему mysqli заменил mysql, он также не дает ответа на этот вопрос. Он действительно отвечает, почему был введен mysqli. Но это не объясняет, почему mysql и mysqli не могли работать параллельно.

Ani Menon
7 сентября 2016 в 15:23
0

@YourCommonSense Также вопрос OP: "Почему я должен использовать что-то еще, даже если они работают на моем сайте?" и поэтому я указал на изменения и улучшения. Вы можете посмотреть все другие ответы, они длинные, поэтому я подумал, что мне нужно их резюмировать.

avatar
Alexander
2 сентября 2015 в 07:20
37

Расширение MySQL является самым старым из трех и было оригинальным способом, который разработчики использовали для взаимодействия с MySQL. Это расширение устарело в пользу других двух альтернатив из-за улучшений, внесенных в более новые версии PHP и MySQL. <94465>

  • MySQLi - это «улучшенное» расширение для работы с базами данных MySQL. Он использует преимущества функций, доступных в более новых версиях сервера MySQL, предоставляет разработчику как функционально-ориентированный, так и объектно-ориентированный интерфейс, а также делает несколько других отличных вещей.

  • PDO предлагает API, который объединяет большую часть функциональности, которая ранее была распространена на основные расширения доступа к базам данных, то есть MySQL, PostgreSQL, SQLite, MSSQL и т. Д. Интерфейс предоставляет высокоуровневый интерфейс. объекты для программиста для работы с подключениями к базе данных, запросами и наборами результатов, а низкоуровневые драйверы осуществляют связь и обработку ресурсов с сервером базы данных. В PDO ведется много обсуждений и работы, и он считается подходящим методом работы с базами данных в современном профессиональном коде.

avatar
mario
24 декабря 2013 в 23:30
225

Простота использования

Аналитические и синтетические причины уже упоминались. Для новичков есть более серьезный стимул отказаться от использования устаревших функций mysql_.

Современные API баз данных просто проще в использовании.

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

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

Эквивалентные функции pdo_ * вместо mysql_ *

Используя < pdo_mysql.php >, вы можете переключиться со старых функций mysql_ с минимальными усилиями . Он добавляет pdo_ оболочки функций, которые заменяют их mysql_ аналоги.

  1. Просто include_once( "pdo_mysql.php" ); в каждом скрипте вызова, который должен взаимодействовать с базой данных.

  2. Удалите префикс функции mysql_ везде везде и замените его на pdo_ <61594> .673503

    • mysql_ connect() становится pdo_ connect()
    • mysql_ query() становится pdo_ query()
    • mysql_ num_rows() становится pdo_ num_rows()
    • mysql_ insert_id() становится pdo_ insert_id()
    • mysql_ fetch_array() становится pdo_ fetch_array()
    • mysql_ fetch_assoc() становится pdo_ fetch_assoc()
    • mysql_ real_escape_string() становится pdo_ real_escape_string()
    • и так далее ...

  3. Ваш код будет работать одинаково и в основном выглядеть одинаково:

    include_once("pdo_mysql.php"); 
    
    pdo_connect("localhost", "usrABC", "pw1234567");
    pdo_select_db("test");
    
    $result = pdo_query("SELECT title, html FROM pages");  
    
    while ($row = pdo_fetch_assoc($result)) {
        print "$row[title] - $row[html]";
    }
    

Et voilà.
Ваш код с использованием PDO.
Теперь пора на самом деле использовать его .

Связанные параметры легко использовать

Вам просто нужен менее громоздкий API.

pdo_query() добавляет очень простую поддержку связанных параметров. Преобразовать старый код очень просто:

Переместите переменные из строки SQL.

  • Добавьте их как параметры функции с разделителями-запятыми в pdo_query().
  • Поместите вопросительные знаки ? в качестве заполнителей вместо переменных.
  • Избавьтесь от ' одинарных кавычек, которые ранее заключали строковые значения / переменные.

Преимущество становится более очевидным для более длинного кода.

Часто строковые переменные не просто интерполируются в SQL, но объединяются с экранирующими вызовами между ними.

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title='" . pdo_real_escape_string($title) . "' OR id='".
   pdo_real_escape_string($title) . "' AND user <> '" .
   pdo_real_escape_string($root) . "' ORDER BY date")

При применении ? заполнителей вам не нужно беспокоиться об этом:

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title=? OR id=? AND user<>? ORDER BY date", $title, $id, $root)

Помните, что pdo_ * по-прежнему позволяет либо .
Просто не избегайте переменной и связывайте ее в одном запросе.

  • Функция заполнителя обеспечивается реальным PDO, стоящим за ним.
  • Таким образом, также разрешены :named списки заполнителей позже.

Что еще более важно, вы можете безопасно передавать переменные $ _REQUEST [] за любым запросом. Когда отправленные поля <form> точно соответствуют структуре базы данных, она еще короче:

pdo_query("INSERT INTO pages VALUES (? ? ? ? ?)", $_POST);

Какая простота. Но давайте вернемся к некоторым советам по переписыванию и техническим причинам того, почему вы можете захотеть избавиться от mysql_ и экранирования.

Исправьте или удалите любую функцию oldschool sanitize()

После преобразования всех вызовов mysql_ в pdo_query с привязанными параметрами удалите все избыточные вызовы pdo_real_escape_string.

В частности, вам следует исправить любые функции sanitize, clean, filterThis или clean_data, как рекламируется в устаревших руководствах, в той или иной форме:

function sanitize($str) {
   return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}

Самая вопиющая ошибка здесь - отсутствие документации. Что еще более важно, порядок фильтрации был совершенно неправильным.

  • Правильный порядок был бы следующим: устаревший stripslashes как самый внутренний вызов, затем trim, затем strip_tags, htmlentities для контекста вывода приложения и только в последнюю очередь <2861594> как его непосредственно предшествует межспарсингу SQL.

  • Но в качестве первого шага просто избавьтесь от звонка _real_escape_string .

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

  • Обработка строк / значений делегируется PDO и его параметризованным операторам.

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

    Историческая справка о magic_quotes. Эта функция по праву считается устаревшей. Однако это часто неправильно изображается как сбойная функция безопасности . Но magic_quotes - такая же неудачная функция безопасности, как теннисные мячи как источник питания. Это просто не было их целью.

    Первоначальная реализация в PHP2 / FI представила это явно с помощью просто « кавычки будут автоматически экранированы, что упрощает передачу данных формы непосредственно в запросы msql ». Примечательно, что его можно было безопасно использовать с mSQL, поскольку он поддерживал только ASCII.
    Затем PHP3 / Zend повторно представили magic_quotes для MySQL и неправильно задокументировали его. Но изначально это была просто удобная функция, не предназначенная для обеспечения безопасности.

Чем отличаются подготовленные отчеты

Когда вы вставляете строковые переменные в запросы SQL, это не только усложняется для вас. Для MySQL также требуется лишнее усилие снова разделить код и данные.

SQL-инъекции - это просто, когда данные перетекают в контекст кода . Сервер базы данных не может позже определить, где PHP изначально вставлял переменные между предложениями запроса.

С помощью связанных параметров вы разделяете код SQL и значения контекста SQL в своем коде PHP. Но он не перетасовывается снова за кулисами (кроме PDO :: EMULATE_PREPARES). База данных получает неизменные команды SQL и значения переменных 1: 1.

Хотя в этом ответе подчеркивается, что вам следует позаботиться о преимуществах удобочитаемости при удалении mysql_ . Иногда также наблюдается преимущество в производительности (повторяющиеся вставки INSERT только с разными значениями) из-за этого видимого и технического разделения данных / кода.

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

Использование гибридного PDO

Эти функции-оболочки pdo_* составляют удобный для программирования временный API. (Это в значительной степени то, что могло бы быть MYSQLI, если бы не идиосинкразический сдвиг сигнатуры функции). Они также чаще всего раскрывают реальный PDO.
Переписывание не должно останавливаться на использовании новых имен функций pdo_. Вы можете один за другим переводить каждый pdo_query () в простой вызов $ pdo-> prepare () -> execute ().

Однако лучше начать с упрощения снова. Например, стандартный результат:

$result = pdo_query("SELECT * FROM tbl");
while ($row = pdo_fetch_assoc($result)) {

Можно заменить простой итерацией foreach:

foreach ($result as $row) {

Или, еще лучше, прямое и полное извлечение массива:

$result->fetchAll();

В большинстве случаев вы получите более полезные предупреждения, чем обычно выдает PDO или mysql_ после неудачных запросов.

Другие варианты

Итак, мы надеемся, что это наглядно продемонстрировало некоторые практические причины и достойный путь к падению mysql_ .

Простое переключение на pdo не совсем подходит. pdo_query() также является его внешним интерфейсом.

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

Хотя он соответствует категории простейшей вещи, которая могла бы работать, это все еще очень экспериментальный код. Я просто написал это на выходных. Однако есть множество альтернатив. Просто введите в Google абстракцию базы данных PHP и просмотрите немного. Всегда было и будет много отличных библиотек для таких задач.

Если вы хотите еще больше упростить взаимодействие с базой данных, стоит попробовать такие средства отображения, как Paris / Idiorm. Точно так же, как никто больше не использует мягкий DOM в JavaScript, в настоящее время вам не нужно присматривать за необработанным интерфейсом базы данных.

rickyduck
22 января 2014 в 16:35
9

Будьте осторожны с функцией pdo_query("INSERT INTO pages VALUES (? ? ? ? ?)", $_POST);, например: pdo_query("INSERT INTO users VALUES (? ? ?), $_POST); $_POST = array( 'username' => 'lawl', 'password' => '123', 'is_admin' => 'true');

mario
8 марта 2017 в 14:34
0

@Tom Конечно, хотя он и не поддерживается (0.9.2 был последним), вы можете создать учетную запись ископаемых, добавить в вики или отправить отчет об ошибке (без регистрации IIRC ).

Ryan Stone
23 февраля 2020 в 11:11
0

pdo_real_escape_string() <- Это вообще настоящая функция, я не могу найти к ней документации? Пожалуйста, опубликуйте источник для этого.

avatar
Fluffeh
18 сентября 2013 в 12:28
69

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

Во-первых, не стесняйтесь создавать эту тестовую базу данных mysql (я назвал свою подготовку):

mysql> create table users(
    -> id int(2) primary key auto_increment,
    -> userid tinytext,
    -> pass tinytext);
Query OK, 0 rows affected (0.05 sec)

mysql> insert into users values(null, 'Fluffeh', 'mypass');
Query OK, 1 row affected (0.04 sec)

mysql> create user 'prepared'@'localhost' identified by 'example';
Query OK, 0 rows affected (0.01 sec)

mysql> grant all privileges on prep.* to 'prepared'@'localhost' with grant option;
Query OK, 0 rows affected (0.00 sec)

После этого мы можем перейти к нашему PHP-коду.

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

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }

    $database='prep';
    $link=mysql_connect('localhost', 'prepared', 'example');
    mysql_select_db($database) or die( "Unable to select database");

    $sql="select id, userid, pass from users where userid='$user' and pass='$pass'";
    //echo $sql."<br><br>";
    $result=mysql_query($sql);
    $isAdmin=false;
    while ($row = mysql_fetch_assoc($result)) {
        echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
        $isAdmin=true;
        // We have correctly matched the Username and Password
        // Lets give this person full access
    }
    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }
    mysql_close($link);

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

На первый взгляд кажется вполне законным.

Пользователь должен ввести логин и пароль, верно?

Великолепно, не входите в следующее:

user: bob
pass: somePass

и отправьте его.

​​Вывод будет следующим:

You could not be verified. Please try again...

Супер! Работая, как ожидалось, теперь давайте попробуем фактическое имя пользователя и пароль:

user: Fluffeh
pass: mypass

Удивительно! Всем привет, код корректно проверил админ. Это прекрасно!

Ну, не совсем. Допустим, пользователь - умный маленький человек. Допустим, это я.

Введите следующее:

user: bob
pass: n' or 1=1 or 'm=m

И вывод:

The check passed. We have a verified admin!

Поздравляю, вы только что разрешили мне войти в ваш суперзащищенный раздел только для администраторов, где я ввел ложное имя пользователя и ложный пароль. Серьезно, если вы мне не верите, создайте базу данных с кодом, который я предоставил, и запустите этот PHP-код, который, на первый взгляд, ДЕЙСТВИТЕЛЬНО, кажется, довольно хорошо проверяет имя пользователя и пароль.

Итак, в ответ, ПОЧЕМУ ВАС КРИЧИТ НА.

Итак, давайте посмотрим, что пошло не так, и почему я только что попал в вашу пещеру для суперадминистраторов. Я сделал предположение и предположил, что вы не были осторожны со своими входными данными, и просто передали их в базу данных напрямую. Я создал ввод таким образом, чтобы ИЗМЕНИТЬ запрос, который вы действительно выполняли. Итак, что это должно было быть и чем оно стало в итоге?

select id, userid, pass from users where userid='$user' and pass='$pass'

Это запрос, но когда мы заменяем переменные фактическими входными данными, которые мы использовали, мы получаем следующее:

select id, userid, pass from users where userid='bob' and pass='n' or 1=1 or 'm=m'

Посмотрите, как я построил свой «пароль» так, чтобы он сначала закрывал одинарную кавычку вокруг пароля, а затем вводил совершенно новое сравнение? Затем в целях безопасности я добавил еще одну «строку», чтобы одинарная кавычка закрылась, как и ожидалось, в исходном коде.

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

Итак, что пошло не так, и как мы можем это исправить?

Это классическая атака с использованием SQL-инъекции. Один из самых простых в этом отношении. По шкале векторов атаки это малыш, атакующий танк - и побеждающий.

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

Теперь у вас есть лучшие варианты использования mysqli_ или PDO. Я лично большой поклонник PDO, поэтому в оставшейся части этого ответа я буду использовать PDO. Есть плюсы и минусы, но лично я считаю, что плюсы намного перевешивают минусы. Он переносится на несколько движков баз данных - используете ли вы MySQL или Oracle или что-нибудь еще, - просто изменив строку подключения, у него есть все причудливые функции, которые мы хотим использовать, и он приятный и чистый. Я люблю чистоту.

Теперь давайте снова посмотрим на этот код, на этот раз написанный с использованием объекта PDO:

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }
    $isAdmin=false;

    $database='prep';
    $pdo=new PDO ('mysql:host=localhost;dbname=prep', 'prepared', 'example');
    $sql="select id, userid, pass from users where userid=:user and pass=:password";
    $myPDO = $pdo->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
    if($myPDO->execute(array(':user' => $user, ':password' => $pass)))
    {
        while($row=$myPDO->fetch(PDO::FETCH_ASSOC))
        {
            echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
            $isAdmin=true;
            // We have correctly matched the Username and Password
            // Lets give this person full access
        }
    }

    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

Основные отличия заключаются в том, что больше нет функций mysql_*. Все это делается с помощью объекта PDO, во-вторых, с помощью подготовленного оператора. А что вы спросите? Это способ сообщить базе данных перед запуском запроса, какой запрос мы собираемся выполнить. В этом случае мы сообщаем базе данных: «Привет, я собираюсь запустить оператор select, требующий идентификатора, идентификатора пользователя и перехода от пользователей таблицы, где идентификатор пользователя является переменной, а проход также является переменной».

Затем в операторе execute мы передаем базе данных массив со всеми ожидаемыми переменными.

Результаты фантастические. Давайте попробуем еще раз те комбинации имени пользователя и пароля, которые были ранее:

user: bob
pass: somePass

Пользователь не подтвержден. Замечательно.

Как насчет:

user: Fluffeh
pass: mypass

О, я просто немного поволновался, это сработало: проверка прошла. У нас есть проверенный админ!

Теперь давайте попробуем данные, которые умный парень мог бы ввести, чтобы попытаться обойти нашу маленькую систему проверки:

user: bob
pass: n' or 1=1 or 'm=m

На этот раз мы получаем следующее:

You could not be verified. Please try again...

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

Наконец, это не означает, что это ИДЕАЛЬНЫЙ код. Есть еще много вещей, которые вы могли бы сделать для его улучшения, например, использовать хешированные пароли, гарантировать, что, когда вы храните важную информацию в базе данных, вы не сохраняете ее в виде простого текста, имеете несколько уровней проверки - но на самом деле, если вы просто измените свой старый код, подверженный инъекциям, на это, вы будете ХОРОШО на пути к написанию хорошего кода - и тот факт, что вы зашли так далеко и все еще читаете, дает мне чувство надежды, что вы не только реализуете этот тип кода при написании ваших веб-сайтов и приложений, но вы можете пойти и изучить те другие вещи, которые я только что упомянул, и многое другое. Пишите лучший код, который вы можете, а не самый простой код, который практически не работает.

Madara's Ghost
18 сентября 2013 в 12:31
3

Спасибо за ваш ответ! Получите мой +1! Стоит отметить, что mysql_* сам по себе не является небезопасным, но он способствует развитию небезопасного кода через плохие руководства и отсутствие надлежащего API подготовки операторов.

avatar
Your Common Sense
1 января 2013 в 17:42
113

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

  • неблокирующие асинхронные запросы
  • хранимые процедуры, возвращающие несколько наборов результатов
  • Шифрование (SSL)
  • Сжатие

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

Тем не менее, есть и некоторые нетехнические проблемы, которые могут немного усложнить ваш опыт

  • дальнейшее использование этих функций с современными версиями PHP вызовет уведомления об устаревшем уровне. Их просто можно отключить.
  • в далеком будущем они могут быть удалены из стандартной сборки PHP. Это тоже не имеет большого значения, поскольку mydsql ext будет перемещен в PECL, и каждый хостер будет счастлив скомпилировать PHP с ним, поскольку они не хотят терять клиентов, чьи сайты работали десятилетиями.
  • сильное сопротивление со стороны сообщества Stackoverflow. Каждый раз, когда вы упоминаете эти честные функции, вам говорят, что они находятся под строгим табу.
  • средний пользователь PHP, скорее всего, ваше представление об использовании этих функций подвержено ошибкам и ошибочно. Просто из-за всех этих многочисленных руководств и руководств, которые учат вас неправильному пути. Не сами функции - я должен это подчеркнуть - а то, как они используются.

Последняя проблема является проблемой.
Но, на мой взгляд, и предлагаемое решение ничем не лучше.
Мне кажется слишком идеалистическим мечтой о том, что все эти пользователи PHP сразу научатся правильно обрабатывать SQL-запросы. Скорее всего, они просто механически изменили бы mysql_ * на mysqli_ *, оставив подход таким же . Тем более, что mysqli делает использование подготовленных операторов невероятно болезненным и хлопотным.
Не говоря уже о том, что родной подготовленных операторов недостаточно для защиты от инъекций SQL, и ни mysqli, ни PDO не предлагают решения.

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

Также есть несколько ложных или несущественных причин, например

  • Не поддерживает хранимые процедуры (мы давно использовали mysql_query("CALL my_proc");)
  • Не поддерживает транзакции (как указано выше)
  • Не поддерживает несколько операторов (кому они нужны?)
  • Не находится в активной разработке (и что? Это влияет на на вас, каким-либо образом?)
  • Отсутствует объектно-ориентированный интерфейс (для его создания требуется несколько часов)
  • Не поддерживает подготовленные операторы или параметризованные запросы

Последний интересный момент. Хотя mysql ext не поддерживает собственные подготовленные операторы , они не требуются для обеспечения безопасности. Мы можем легко подделать подготовленные операторы, используя ручные заполнители (как это делает PDO):

function paraQuery()
{
    $args  = func_get_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);
    $result = mysql_query($query);
    if (!$result)
    {
        throw new Exception(mysql_error()." [$query]");
    }
    return $result;
}

$query  = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d";
$result = paraQuery($query, $a, "%$b%", $limit);

вуаля , все параметризовано и безопасно.

Но ладно, если вам не нравится красное поле в руководстве, возникает проблема выбора: mysqli или PDO?

Ответ будет таким:

  • Если вы понимаете необходимость использования уровня абстракции базы данных и ищете API для его создания, mysqli - очень хороший выбор, поскольку он действительно поддерживает многие специфичные для MySQL. особенности.
  • Если, как и подавляющее большинство разработчиков PHP, вы используете необработанные вызовы API прямо в коде приложения (что, по сути, является неправильной практикой) - PDO - единственный выбор , поскольку это расширение претендует на то, чтобы не просто API, а скорее полу-DAL, все еще неполный, но предлагающий множество важных функций, две из которых резко отличают PDO от mysqli:

    • в отличие от mysqli, PDO может связывать заполнители по значению , что делает динамически построенные запросы выполнимыми без нескольких экранов с довольно беспорядочным кодом.
    • в отличие от mysqli, PDO всегда может возвращать результат запроса в виде простого обычного массива, тогда как mysqli может делать это только на установках mysqlnd.

Итак, если вы средний пользователь PHP и хотите избавить себя от множества головных болей при использовании собственных подготовленных операторов, PDO - опять же - единственный выбор.
Однако PDO - тоже не серебряная пуля, и у него есть свои трудности.
Итак, я написал решения для всех распространенных ошибок и сложных случаев в вики тега PDO

Тем не менее, все, кто говорит о расширениях, всегда упускают 2 важных факта о Mysqli и PDO:

  1. Подготовленный отчет не является серебряной пулей . Есть динамические идентификаторы, которые нельзя связать с помощью подготовленных операторов. Существуют динамические запросы с неизвестным количеством параметров, что затрудняет построение запроса.

  2. Ни mysqli_ *, ни функции PDO не должны появляться в коде приложения.
    Между ними и кодом приложения должен быть уровень абстракции , который будет выполнять всю грязную работу по привязке, циклу, обработке ошибок и т. Д. Внутри, делая код приложения СУХИМ и чистым. Особенно для сложных случаев, таких как динамическое построение запросов.

Итак, просто переключиться на PDO или mysqli недостаточно. Вместо вызова необработанных функций API в их коде необходимо использовать ORM, построитель запросов или любой другой класс абстракции базы данных.
И наоборот - если у вас есть уровень абстракции между кодом вашего приложения и mysql API - , на самом деле не имеет значения, какой движок используется. Вы можете использовать mysql ext до тех пор, пока он не станет устаревшим, а затем легко переписать свой класс абстракции на другой движок, с неповрежденным кодом всего приложения.

Вот несколько примеров, основанных на моем классе safemysql, чтобы показать, каким должен быть такой класс абстракции:

$city_ids = array(1,2,3);
$cities   = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);

Сравните эту одну строку с объемом кода, который вам понадобится с PDO.
Затем сравните с сумасшедшим количеством кода, который вам понадобится, с необработанными подготовленными операторами Mysqli. Обратите внимание, что обработка ошибок, профилирование и ведение журнала запросов уже встроены и работают.

$insert = array('name' => 'John', 'surname' => "O'Hara");
$db->query("INSERT INTO users SET ?u", $insert);

Сравните это с обычными вставками PDO, когда каждое отдельное имя поля повторяется от шести до десяти раз - во всех этих многочисленных именованных заполнителях, привязках и определениях запросов.

Другой пример:

$data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);

Вы вряд ли найдете пример для PDO, чтобы справиться с таким практическим случаем.
И это будет слишком многословно и, скорее всего, небезопасно.

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

Madara's Ghost
1 января 2013 в 17:48
21

mysql_* упрощает поиск уязвимостей. Поскольку PHP используется большим количеством начинающих пользователей, mysql_* активно вреден на практике, даже если теоретически его можно использовать без проблем.

uınbɐɥs
3 января 2013 в 06:07
4

everything is parameterized and safe - он может быть параметризован, но ваша функция не использует настоящие подготовленные операторы.

Nanne
1 февраля 2013 в 10:21
6

Как Not under active development только для этого «0,01%»? Если вы создадите что-то с этой функцией ожидания, обновите свою версию mysql через год и получите неработающую систему, я уверен, что внезапно появилось очень много людей с этими «0,01%». Я бы сказал, что deprecated и not under active development тесно связаны. Вы можете сказать, что для этого нет «[достойной] причины», но факт в том, что когда предлагается выбор между вариантами, no active development почти так же плох, как deprecated, я бы сказал?

ircmaxell
4 февраля 2013 в 12:42
1

@MadaraUchiha: Вы можете объяснить, как легко найти уязвимости? Особенно в тех случаях, когда те же самые уязвимости не влияют на PDO или MySQLi ... Потому что я не знаю ни одной из тех, о которых вы говорите.

ircmaxell
4 февраля 2013 в 12:44
4

@ShaquinTrifonoff: конечно, он не использует подготовленные операторы. Но не поддерживает PDO, который большинство людей рекомендует вместо MySQLi. Так что я не уверен, что это окажет здесь существенное влияние. Приведенный выше код (с немного большим анализом) - это то, что делает PDO, когда вы по умолчанию готовите оператор ...

Madara's Ghost
4 февраля 2013 в 13:13
0

@ircmaxell Я имею в виду образовательную часть. Безопасность не рассматривается в большинстве учебных пособий, которые просматривают новые пользователи. Я не говорю, что этих уязвимостей нет в новых расширениях, просто по какой-то причине это происходит гораздо чаще в расширении ext / mysql.

ircmaxell
4 февраля 2013 в 13:23
1

@MadaraUchiha: одна и та же проблема существует во всех формах. Вы не думаете, что после того, как ext / mysql уйдет, кто-то сделает то же самое с ext / mysqli или PDO? Глупо винить в этом ext / mysql. И отказ от него не повлияет на этот код стиля или учебник ...

avatar
NullPoiиteя
1 января 2013 в 11:52
1333

PHP предлагает три различных API для подключения к MySQL. Это mysql (удалено с PHP 7), mysqli и PDO <6542675675671323> Расширения

Функции mysql_* раньше были очень популярны, но их использование больше не приветствуется. Команда документации обсуждает ситуацию с безопасностью базы данных, и обучение пользователей отказу от широко используемого расширения ext / mysql является частью этого (проверьте php.internals: отказ от ext / mysql ).

И более поздняя команда разработчиков PHP приняла решение генерировать E_DEPRECATED ошибки, когда пользователи подключаются к MySQL через mysql_connect(), mysql_pconnect() функциональность, встроенная в ext/mysql.

ext/mysql был официально объявлен устаревшим с PHP 5.5 и удален как и удален как <567567567567567567567567 >.

Видите красную рамку?

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

Почему


Отказ от ext/mysql касается не только безопасности, но и доступа ко всем функциям базы данных MySQL.

ext/mysql был построен для MySQL 3.23 и с тех пор получил очень мало дополнений, в основном сохраняя совместимость с этой старой версией, что затрудняет поддержку кода. Отсутствующие функции, которые не поддерживаются ext/mysql, включают: ( из руководства PHP ).

Причина отказа от использования функции mysql_* :

  • Не в активной разработке
  • Удалено с PHP 7
  • Отсутствует интерфейс OO
  • Не поддерживает неблокирующие асинхронные запросы
  • Не поддерживает подготовленные операторы или параметризованные запросы
  • Не поддерживает хранимые процедуры
  • Не поддерживает несколько операторов
  • Не поддерживает транзакции
  • Не поддерживает все функции MySQL 5.1

Выше цитата из ответа Квентина

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

См. сравнение расширений SQL.


Подавление предупреждений об устаревании

Пока код преобразуется в MySQLi / PDO, ошибки E_DEPRECATED можно подавить, установив error_reporting в php.ini , чтобы исключить <65146756712567>

error_reporting = E_ALL ^ E_DEPRECATED

Обратите внимание, что это также скроет другие предупреждения об устаревании , которые, однако, могут относиться к другим вещам, кроме MySQL. ( из руководства по PHP )

Статья PDO против MySQLi: что вам следует использовать? >

И лучший способ - PDO, и сейчас я пишу простой PDO учебник.


Простое и короткое руководство по PDO


В. Первый вопрос в моей голове был: что такое `PDO`?

А. « PDO - объекты данных PHP - это уровень доступа к базе данных, обеспечивающий единый метод доступа к нескольким базам данных».

alt text


Подключение к MySQL

С функцией mysql_* или мы можем сказать это по-старому (устарело в PHP 5.5 и выше)

$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);

С PDO: Все, что вам нужно сделать, это создать новый объект PDO. Конструктор принимает параметры для указания источника базы данных PDO. Конструктор в основном принимает четыре параметра: DSN (имя источника данных) и, возможно, username, password.

.

Здесь, я думаю, вы знакомы со всем, кроме DSN; это новое в PDO. DSN - это в основном строка параметров, которые сообщают PDO, какой драйвер использовать, и сведения о подключении. Для получения дополнительной информации см. PDO MySQL DSN.

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');

Примечание: вы также можете использовать charset=UTF-8, но иногда это вызывает ошибку, поэтому лучше использовать utf8.

Если есть какая-либо ошибка соединения, будет выдан объект PDOException, который можно перехватить для дальнейшей обработки Exception.

Хорошее чтение : Подключения и управление подключениями ¶

Вы также можете передать несколько параметров драйвера в виде массива четвертому параметру. Я рекомендую передать параметр, который переводит PDO в режим исключения. Поскольку некоторые драйверы PDO не поддерживают собственные подготовленные операторы, PDO выполняет эмуляцию подготовки. Он также позволяет вручную включить эту эмуляцию. Чтобы использовать собственные подготовленные операторы на стороне сервера, вы должны явно установить его false.

Другой - отключить эмуляцию подготовки, которая включена в драйвере MySQL по умолчанию, но эмуляция подготовки должна быть отключена для безопасного использования PDO.

Позже я объясню, почему подготовительную эмуляцию следует отключить. Чтобы узнать причину, проверьте этот пост.

Его можно использовать только в том случае, если вы используете старую версию MySQL, которую я не рекомендую.

Ниже приведен пример того, как это можно сделать:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password',
              array(PDO::ATTR_EMULATE_PREPARES => false,
              PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

Можно ли установить атрибуты после построения PDO?

Да , мы также можем установить некоторые атрибуты после построения PDO с помощью метода setAttribute:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Обработка ошибок


Обработка ошибок в PDO намного проще, чем в mysql_*.

Обычная практика использования mysql_*:

//Connected to MySQL
$result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));

OR die() - не лучший способ справиться с ошибкой, поскольку мы не можем справиться с проблемой в die. Он просто внезапно завершит скрипт, а затем отобразит ошибку на экране, который вы обычно НЕ хотите показывать своим конечным пользователям, и позволит кровавым хакерам обнаружить вашу схему. В качестве альтернативы, возвращаемые значения функций mysql_* часто можно использовать вместе с mysql_error () для обработки ошибок.

PDO предлагает лучшее решение: исключения. Все, что мы делаем с PDO, должно быть заключено в блок try - catch. Мы можем принудительно переключить PDO в один из трех режимов ошибки, установив атрибут режима ошибки. Ниже приведены три режима обработки ошибок.

  • PDO::ERRMODE_SILENT. Он просто устанавливает коды ошибок и действует почти так же, как mysql_*, где вы должны проверять каждый результат, а затем смотреть на $db->errorInfo();, чтобы получить подробную информацию об ошибке.
  • PDO::ERRMODE_WARNING Поднять E_WARNING. (Предупреждения во время выполнения (нефатальные ошибки). Выполнение сценария не останавливается.)
  • PDO::ERRMODE_EXCEPTION: выбросить исключения. Он представляет собой ошибку, вызванную PDO. Вы не должны выбрасывать PDOException из вашего собственного кода. См. Исключения для получения дополнительной информации об исключениях в PHP. Он действует очень похоже на or die(mysql_error());, когда его не ловят. Но в отличие от or die(), PDOException можно поймать и аккуратно обработать, если вы захотите это сделать.

Хорошее чтение :

Нравится:

$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

И вы можете обернуть его в try - catch, как показано ниже:

try {
    //Connect as appropriate as above
    $db->query('hi'); //Invalid query!
} 
catch (PDOException $ex) {
    echo "An Error occured!"; //User friendly message/message you want to show to user
    some_logging_function($ex->getMessage());
}

Вам не нужно иметь дело с try - catch прямо сейчас. Вы можете поймать его в любое удобное время, но я настоятельно рекомендую использовать try - catch. Также имеет смысл поймать его за пределами функции, которая вызывает материал PDO:

function data_fun($db) {
    $stmt = $db->query("SELECT * FROM table");
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

//Then later
try {
    data_fun($db);
}
catch(PDOException $ex) {
    //Here you can handle error and show message/perform action you want.
}

Кроме того, вы можете обрабатывать с помощью or die() или, можно сказать, mysql_*, но это будет действительно по-разному. Вы можете скрыть опасные сообщения об ошибках в производственной среде, повернув display_errors off и просто прочитав журнал ошибок.

Теперь, после прочтения всего вышеперечисленного, вы, вероятно, думаете: какого черта, когда я просто хочу начать использовать простые утверждения SELECT, INSERT, UPDATE или DELETE? Не волнуйтесь, мы идем:


Выбор данных

PDO select image

Итак, в mysql_* вы делаете следующее:

<?php
$result = mysql_query('SELECT * from table') or die(mysql_error());

$num_rows = mysql_num_rows($result);

while($row = mysql_fetch_assoc($result)) {
    echo $row['field1'];
}

Теперь в PDO вы можете сделать это так:

<?php
$stmt = $db->query('SELECT * FROM table');

while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo $row['field1'];
}

или

<?php
$stmt = $db->query('SELECT * FROM table');
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);

//Use $results

Примечание : если вы используете метод, как показано ниже (query()), этот метод возвращает объект PDOStatement. Поэтому, если вы хотите получить результат, используйте его, как указано выше.

<?php
foreach($db->query('SELECT * FROM table') as $row) {
    echo $row['field1'];
}

В PDO Data он получается с помощью ->fetch(), метода дескриптора вашего оператора. Перед вызовом fetch лучшим подходом было бы сообщить PDO, как вы хотите получать данные. В следующем разделе я объясняю это.

Режимы выборки

Обратите внимание на использование PDO::FETCH_ASSOC в приведенных выше кодах fetch() и fetchAll(). Это указывает PDO возвращать строки в виде ассоциативного массива с именами полей в качестве ключей. Также есть много других режимов выборки, которые я объясню один за другим.

Прежде всего, я объясню, как выбрать режим выборки:

 $stmt->fetch(PDO::FETCH_ASSOC)

Выше я использовал fetch(). Вы также можете использовать:

  • PDOStatement::fetchAll() - возвращает массив, содержащий все строки набора результатов
  • PDOStatement::fetchColumn() - возвращает один столбец из следующей строки набора результатов
  • PDOStatement::fetchObject() - выбирает следующую строку и возвращает ее как объект.
  • PDOStatement::setFetchMode() - Установите режим выборки по умолчанию для этого оператора

Теперь я перехожу в режим выборки:

  • PDO::FETCH_ASSOC: возвращает массив, проиндексированный по имени столбца, как возвращено в вашем наборе результатов
  • PDO::FETCH_BOTH (по умолчанию): возвращает массив, проиндексированный как по имени столбца, так и по номеру столбца с индексом 0, как возвращено в вашем наборе результатов

Есть еще больше вариантов! Прочтите обо всех них в PDOStatement Получить документацию..

Получение количества строк :

Вместо использования mysql_num_rows для получения количества возвращенных строк вы можете получить PDOStatement и выполнить rowCount(), например:

<?php
$stmt = $db->query('SELECT * FROM table');
$row_count = $stmt->rowCount();
echo $row_count.' rows selected';

Получение последнего вставленного идентификатора

<?php
$result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')");
$insertId = $db->lastInsertId();

Вставить и обновить или удалить операторы

Insert and update PDO image

В функции mysql_* мы делаем следующее:

<?php
$results = mysql_query("UPDATE table SET field='value'") or die(mysql_error());
echo mysql_affected_rows($result);

А в pdo то же самое можно сделать с помощью:

<?php
$affected_rows = $db->exec("UPDATE table SET field='value'");
echo $affected_rows;

В приведенном выше запросе PDO::exec выполнить инструкцию SQL и вернуть количество затронутых строк.

Вставка и удаление будут рассмотрены позже.

Вышеупомянутый метод полезен только тогда, когда вы не используете переменную в запросе. Но когда вам нужно использовать переменную в запросе, никогда не пытайтесь сделать что-то подобное, и там для подготовленный оператор или параметризованный оператор is.


Подготовленные отчеты

В. Что такое подготовленный оператор и зачем он мне нужен?
A. Подготовленный оператор - это предварительно скомпилированный оператор SQL, который может выполняться несколько раз отправив на сервер только данные.

Типичный рабочий процесс использования подготовленного оператора выглядит следующим образом (цитата из Википедии, три 3 точки):

  1. Подготовить : шаблон оператора создается приложением и отправляется в систему управления базами данных (СУБД). Некоторые значения остаются неопределенными, они называются параметрами, заполнителями или связанными переменными (помечены ниже ?):

    INSERT INTO PRODUCT (name, price) VALUES (? ?)

  2. СУБД анализирует, компилирует и выполняет оптимизацию запроса в шаблоне оператора и сохраняет результат, не выполняя его.

  3. Выполнить : позже приложение предоставляет (или связывает) значения для параметров, и СУБД выполняет оператор (возможно, возвращая результат). Приложение может выполнять оператор сколько угодно раз с разными значениями. В этом примере он может предоставить "хлеб" для первого параметра и 1.00 для второго параметра.

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

В. Итак, что такое именованные заполнители и как их использовать?
A. Именованные заполнители. Вместо вопросительных знаков используйте описательные имена, которым предшествует двоеточие. Нас не заботит позиция / порядок значений в заполнителе имени:

 $stmt->bindParam(':bla', $bla);

bindParam(parameter,variable,data_type,length,driver_options)

Вы также можете выполнить привязку, используя массив выполнения:

<?php
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->execute(array(':name' => $name, ':id' => $id));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

Еще одна приятная особенность для друзей OOP состоит в том, что именованные заполнители имеют возможность вставлять объекты непосредственно в вашу базу данных, предполагая, что свойства соответствуют именованным полям. Например:

class person {
    public $name;
    public $add;
    function __construct($a,$b) {
        $this->name = $a;
        $this->add = $b;
    }

}
$demo = new person('john','29 bla district');
$stmt = $db->prepare("INSERT INTO table (name, add) value (:name, :add)");
$stmt->execute((array)$demo);

В. Итак, что такое безымянные заполнители и как их использовать?
A. Давайте рассмотрим пример:

<?php
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?  ?)");
$stmt->bindValue(1, $name, PDO::PARAM_STR);
$stmt->bindValue(2, $add, PDO::PARAM_STR);
$stmt->execute();

и

$stmt = $db->prepare("INSERT INTO folks (name, add) values (?  ?)");
$stmt->execute(array('john', '29 bla district'));

В приведенном выше примере вы можете увидеть эти ? вместо имени, например, в заполнителе имени. Теперь в первом примере мы назначаем переменные различным заполнителям ($stmt->bindValue(1, $name, PDO::PARAM_STR);). Затем мы присваиваем значения этим заполнителям и выполняем инструкцию. Во втором примере первый элемент массива переходит к первому ?, а второй - ко второму ?.

ПРИМЕЧАНИЕ : В безымянных заполнителях мы должны позаботиться о правильном порядке элементов в массиве, который мы передаем методу PDOStatement::execute().


SELECT, INSERT, UPDATE, DELETE подготовленные запросы

  1. SELECT :

    $stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
    $stmt->execute(array(':name' => $name, ':id' => $id));
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
    
  2. INSERT :

    $stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)");
    $stmt->execute(array(':field1' => $field1, ':field2' => $field2));
    $affected_rows = $stmt->rowCount();
    
  3. DELETE :

    $stmt = $db->prepare("DELETE FROM table WHERE id=:id");
    $stmt->bindValue(':id', $id, PDO::PARAM_STR);
    $stmt->execute();
    $affected_rows = $stmt->rowCount();
    
  4. UPDATE :

    $stmt = $db->prepare("UPDATE table SET name=? WHERE id=?");
    $stmt->execute(array($name, $id));
    $affected_rows = $stmt->rowCount();
    

ПРИМЕЧАНИЕ: ​​

Однако PDO и / или MySQLi не полностью безопасны. Проверьте ответ Достаточно ли подготовленных операторов PDO для предотвращения внедрения SQL? от ircmaxell. Также я цитирую часть его ответа:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES GBK');
$stmt = $pdo->prepare("SELECT * FROM test WHERE name = ? LIMIT 1");
$stmt->execute(array(chr(0xbf) . chr(0x27) . " OR 1=1 /*"));

Eugen Rieck
14 декабря 2013 в 19:35
17

То, что, вероятно, следует упомянуть в хорошем прочтении выше: подготовленный оператор устраняет любое значимое использование IN (...) construct.

Alex McMillan
19 ноября 2014 в 22:33
29

Возник вопрос: «Почему я не должен использовать функции mysql_ * в PHP». Этот ответ, хотя и впечатляющий и полный полезной информации, выходит за рамки возможного, и, как говорит @trejder, 8 из 10 человек упустят эту информацию просто потому, что у них нет 4 часов, чтобы потратить их, пытаясь проработать. Это. Было бы гораздо более ценно разбить его и использовать в качестве ответов на несколько, более точных, вопросов.

kuldeep.kamboj
1 марта 2016 в 07:02
0

Лично я предпочитаю mysqli и PDO. Но для обработки кристаллов я попробовал альтернативу исключения function throwEx() { throw new Exception("You did selected not existng db"); } mysql_select_db("nonexistdb") or throwEx();. Она работает для выдачи исключений.

hanshenrik
25 ноября 2018 в 17:40
1

вы указываете Doesn't support non-blocking, asynchronous queries как причину не использовать mysql_ - вы также должны указать это как причину не использовать PDO, потому что PDO также не поддерживает это. (но MySQLi поддерживает это)

Ryan Stone
23 февраля 2020 в 11:01
0

Можно ли использовать Charset utf8mb4_unicode_ci, поскольку у меня есть база данных, которая использует это?

Alex Popov
21 июня 2021 в 21:02
0

Doesn't support non-blocking, asynchronous queries Зачем они мне вообще нужны?

avatar
Madara's Ghost
12 октября 2012 в 13:28
309

Во-первых, давайте начнем со стандартного комментария, который мы даем всем:

Пожалуйста, не используйте функции mysql_* в новом коде . Они больше не поддерживаются и официально признаны устаревшими. Смотрите красную рамку ? Узнайте о подготовленных операторах вместо этого и используйте PDO или MySQLi - - поможет вам решить, какая статья5 Если вы выберете PDO, вот хороший учебник.

Давайте рассмотрим это предложение за предложением и объясним:

  • Они больше не обслуживаются и официально не поддерживаются

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

    НОВИНКА! - ext / mysql теперь официально устарел с PHP 5.5!

    Новее! ext / mysql был удален в PHP 7 .

  • Вместо этого вы должны узнать о подготовленных операторах

    Расширение

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

    Для получения дополнительной информации см. Как я могу предотвратить SQL-инъекцию в PHP?

  • Видите красную рамку?

    Когда вы переходите на любую страницу руководства по функциям mysql, вы видите красную рамку, поясняющую, что ее больше не следует использовать.

  • Используйте PDO или MySQLi

    Существуют лучшие, более надежные и хорошо продуманные альтернативы, PDO - объект базы данных PHP , который предлагает полный подход ООП к взаимодействию с базой данных, и MySQLi , который является улучшением, специфичным для MySQL.

Kamil
13 ноября 2012 в 11:49
6

Есть еще одна вещь: я думаю, что эта функция все еще существует в PHP только по одной причине - совместимость со старыми, устаревшими, но все еще работающими системами CMS, электронной коммерцией, досками объявлений и т. Д. Наконец, она будет удалена, и вам придется переписать свой заявление...

Madara's Ghost
13 ноября 2012 в 11:50
4

@Kamil: Это правда, но на самом деле это не причина, по которой вы не должны его использовать. Причина не использовать его в том, что он древний, небезопасный и т. Д. :)

SDC
10 декабря 2012 в 15:00
4

@Mario - у разработчиков PHP есть процесс, и они только что проголосовали за формальное прекращение поддержки ext / mysql с версии 5.5. Это уже не гипотетическая проблема.

FredTheWebGuy
31 декабря 2012 в 17:28
2

Добавление пары дополнительных строк с помощью проверенной техники, такой как PDO или MySQLi, по-прежнему обеспечивает простоту использования, которую всегда предлагал PHP. Я надеюсь, что ради разработчика он / она знает, что просмотр этих ужасных функций mysql_ * в любом учебнике на самом деле отвлекает от урока, и должен сказать OP, что этот вид кода был оооочень 10 лет назад - и должен подвергнуть сомнению Актуальность учебника тоже!

Eugen Rieck
14 декабря 2013 в 19:36
1

Что, вероятно, следует упомянуть в ответе: подготовленный оператор исключает любое значимое использование IN (...) construct.

user1239087
19 января 2017 в 03:44
0

Еще один комментарий, который упоминался в другом месте на этом сайте, заключается не в том, чтобы просто преобразовывать все операторы mysql_ в mysqli_. Между ними есть различия.

Asad kamran
8 сентября 2021 в 11:52
0

@ Madara's Ghost Интересно, почему бы им не переписать mysql_ * современным, более безопасным кодом

Madara's Ghost
14 сентября 2021 в 07:35
0

@Asadkamran проблема не в реализации mysql_ *, а в том, что они поощряют небезопасные методы программирования, такие как интерполяция строк в ваших запросах вместо подготовленных операторов.

avatar
enhzflep
12 октября 2012 в 13:24
80

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

Например, кто-то может использовать "enhzflep); drop table users" в качестве имени пользователя. Старые функции позволяют выполнять несколько операторов для каждого запроса, так что что-то вроде этого мерзкого баггера может удалить всю таблицу.

Если бы кто-то использовал PDO mysqli, имя пользователя было бы "enhzflep); drop table users".

См. bobby-tables.com.

DaveRandom
30 декабря 2012 в 20:58
10

The old functions will allow executing of multiple statements per query - нет, не будут. Такая инъекция невозможна с ext / mysql - единственный способ такой инъекции возможен с PHP и MySQL - это использование MySQLi и функции mysqli_multi_query(). Типичная инъекция, которая возможна с ext / mysql и неэкранированными строками, - это такие вещи, как ' OR '1' = '1 для извлечения данных из базы данных, которые не должны были быть доступны. В определенных ситуациях можно вводить подзапросы, однако по-прежнему невозможно изменить базу данных таким образом.

avatar
Trott
12 октября 2012 в 13:23
102

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

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

Использование подготовленных операторов в PDO или mysqli сделает такие ошибки программирования более сложными.

Kickstart
27 июня 2013 в 09:31
3

К сожалению, слабая поддержка в MySQLi_ * передачи переменного числа параметров (например, когда вы хотите передать список значений для проверки в предложении IN) поощряет отказ от использования параметров, поощряя использование точно таких же конкатенированных запросов, которые оставьте вызовы MySQL_ * уязвимыми.

Agamemnus
2 февраля 2014 в 05:29
5

Но, опять же, небезопасность - это не проблема, присущая функциям mysql_ *, а проблема неправильного использования.

Trott
2 февраля 2014 в 16:33
2

@Agamemnus Проблема в том, что mysql_ * упрощает реализацию этого «неправильного использования», особенно для неопытных программистов. Библиотеки, которые реализуют подготовленные операторы, затрудняют выполнение такого типа ошибок.

avatar
Alnitak
12 октября 2012 в 13:22
156

Функции mysql_:

  1. устарели - они больше не обслуживаются
  2. не позволяют легко перейти на другой сервер базы данных
  3. не поддерживает подготовленные операторы, поэтому
  4. поощрять программистов использовать конкатенацию для построения запросов, что приводит к уязвимостям SQL-инъекций
eggyal
12 октября 2012 в 13:35
20

# 2 в равной степени верно и для mysqli_

SDC
23 октября 2012 в 14:55
17

честно говоря, учитывая различия в диалекте SQL, даже PDO не дает вам № 2 с какой-либо степенью уверенности. Для этого вам понадобится подходящая оболочка ORM.

hakre
31 мая 2013 в 14:25
0

функция mysql_* - это оболочка функций mysqlnd для новых версий PHP. Таким образом, даже если старая клиентская библиотека больше не поддерживается, mysqlnd сохраняется :)

Raju yourPepe
17 сентября 2013 в 08:25
0

Проблема в том, что не многие провайдеры веб-хостинга могут поддерживать такой объектно-ориентированный стиль дизайна из-за устаревшей версии php.

Alnitak
17 сентября 2013 в 09:15
0

@RajuGujarati, так что найдите веб-хостинг, который может. Если ваш веб-хостинг этого не делает, очень высоки шансы, что они уязвимы для атак на свои серверы.