Я начал понимать, как подготовленный оператор работает при использовании MySQLi и PDO, для первого шага я включил мониторинг запросов MySQL, как упоминалось здесь: как просмотреть текущие запросы MySQL? , Затем я создал следующий тест:
Использование mysqli:
$stmt = $mysqli->prepare("SELECT * FROM users WHERE username =?")) { $stmt->bind_param("i", $user); $user = "''1''";
серверные журналы:
130802 23:39:39 175 Connect ****@localhost on testdb 175 Prepare SELECT * FROM users WHERE username =? 175 Execute SELECT * FROM users WHERE username =0 175 Quit
Использование PDO:
$user = "''1''"; $sql = 'SELECT * FROM user WHERE uid =?'; $sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); $sth->bindParam(1, $user, PDO::PARAM_INT);
Журналы сервера:
130802 23:41:42 176 Connect ****@localhost on testdb 176 Query SELECT * FROM user WHERE uid ='\'\'1\'\'' 176 Quit
Однако оба обеспечивают одинаковый результат:
uid: 0 username: admin role: admin
Примечание: uid = 0
правильно, потому что intval("''1''") = 0
Что здесь важно:
SELECT * FROM user WHERE uid ='\'\'1\'\''
Я нашел только одно указание из руководства PHP: http://www.php.net/manual/en/pdo.prepare.php
Заметка:
Эмулированные подготовленные операторы не взаимодействуют с сервером базы данных, поэтому PDO :: prepare () не проверяет инструкцию.
Но я не уверен, как MySQL справляется с этим запросом и заменяет '\'\'1\'\''
на 0
. В этом случае запросы мониторинга не будут точными при использовании PDO, в то же время использование PDO лучше знать точные запросы, отправляемые в MySQL, но не MySQLi.
Обновление: после изменения типа параметра frm integer для строки:
Журнал MySQLi:
188 Prepare SELECT * FROM awa_user WHERE username =? 188 Execute SELECT * FROM awa_user WHERE username ='\'\'1\'\'' 188 Quit
Журнал PDO:
189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\'' 189 Quit
Это означает, что MySQLi и PDO избегают данных перед отправкой в MySQL при использовании строки, в то время как для целых чисел mysqli применяет intval () или что-то подобное перед отправкой запроса, что также отвечает Биллу, который является правильным.
Ваш PDO настроен для эмуляции подготовленных запросов, тогда как mysqli использует истинные подготовленные запросы.
Подготовленный запрос связывает строку ''1''
как целочисленное значение параметра. PHP принуждает его к целому числу, используя что-то вроде intval()
. Любая строка с нечисловыми ведущими символами интерпретируется как 0 PHP, поэтому значение параметра, отправленное после подготовки, является значением 0.
Поддельный подготовленный запрос использует строчную интерполяцию (вместо привязки), чтобы добавить строку ''1''
в SQL-запрос до того, как MySQL проанализирует его. Но результат схож, потому что SQL также обрабатывает строку с нечисловыми ведущими символами в целочисленном контексте как значение 0.
Единственное различие заключается в том, что заканчивается в общем журнале запросов, когда параметр привязан перед подготовкой и после подготовки.
Вы также можете заставить PDO использовать реальные подготовленные запросы, поэтому в этом случае он должен действовать так же, как mysqli:
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
PS: Это может показаться хорошей причиной, по которой принято начинать значения id в 1 вместо 0.