php - 为什么我不应该使用mysql_*功能在PHP?

  显示原文与译文双语对照的内容

我不应该使用 mysql_* 函数的技术原因? ( 像 mysql_query()mysql_connect() 或者 mysql_real_escape_string() )?

如果他们在我的网站上工作,为什么我应该离开他们?

时间:

MySQL扩展是:

  • 未处于活动状态
  • 正式过时 ( 在 PHP 5.5之时。 将在 PHP 7中删除。)
  • 缺少面向对象接口
  • 不支持:
    • Non-blocking,异步查询
    • 预处理语句或者参数化查询
    • 存储过程
    • 多条语句
    • 交易
    • MySQL 5.1中的所有功能

因为它被否决了,使用它可以使你的代码更少的未来证明。

缺乏对准备语句的支持尤其重要,因为它们提供了一个更清晰,更容易出错的转义方法,而不是用单独的函数调用手动转义外部数据。

请参见 的比较SQL扩展

PHP提供了三个不同的api来连接到 MySQL 。 都有几个 mysqlmysqli 以及 PDO 扩展。

mysql_* 函数非常流行,但不鼓励使用它们。 文档团队正在讨论数据库安全情况,并指导用户离开常用的ext/MySQL扩展是这一部分( 检查 php.internals: 过时的ext/MySQL 。

后来的PHP开发人员团队决定在用户连接到MySQL时生成 E_DEPRECATED 错误,无论是通过 mysql_connect()mysql_pconnect() 还是内置的ext/mysql 连接功能。

ext/mysql 现已 正式弃用的钟表 PHP 5.5

查看红色框

当你进入任何mysql_*功能手册页时,你会看到一个红色的方框,解释它不应该再被使用。

为何


离开 ext/mysql 不仅仅是关于安全性,而且还涉及到访问MySQL数据库的所有特性。

ext/mysql 从而可以构建 MySQL 3.23 材料,加入只得到了很少的自那以后虽然几乎都保持兼容性,这种旧的版本,这样会使代码更努力地维护。 缺少 ext/mysql 所支持的特性包括:( 从PHP手动 )

理由不使用mysql_* 函数

  • 未处于活动状态
  • 在不推荐使用的过程中( 所以打算将它的从未来版本的PHP中删除)
  • 缺少面向对象接口
  • 不支持 non-blocking,异步查询
  • 不支持预准备语句或者参数化查询
  • 不支持存储过程
  • 不支持多个语句
  • 不支持事务
  • 不支持 MySQL 5.1中的所有功能

上的指向了 Quentin的答案

缺乏对准备语句的支持尤其重要,因为它们提供了一个更清晰,更容易出错的转义方法,而不是用单独的函数调用手动转义外部数据。

查看扩展的比较


抑制过时警告警告的

代码正在转换为 MySQL i/pdo时,可以通过将error_reporting设置为 php.ini 以排除 E_DEPRECATED: 来抑制 E_DEPRECATED 错误


 error_reporting = E_ALL ^ E_DEPRECATED

在PHP手册 ),注意这同时会隐藏其他否决警告 以外的事情,当然对它也可能是 root 。(

文章 vs MySQL i: 你用哪个由 Dejan Marjanovic 将有助于你选择的。

更好的方法是 PDO,我现在编写一个简单的PDO教程。


简单和简短的PDO教程


我心中的Q.First 问题是: 什么是 PDO?

。"数据对象是一个数据库访问层,提供对多个数据库的统一访问方法。"

alt text


连接到 MySQL

使用 mysql_* 函数或者我们可以说它是旧的方式( 在 PHP 5.5和上版本中不推荐使用)


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

使用 PDO: 你需要做的就是创建一个新的PDO对象。 构造函数接受用于指定pdo的数据库源构造函数的参数主要接受四个参数,即 DSN ( 数据源名称) 和可选的用户名,密码。

这里我认为你对除DSN之外的所有内容都很熟悉;这在PDO中是新的。 DSN基本上是一组选项,告诉PDO要使用哪个驱动程序以及连接细节。 有关进一步的参考,请检查 PDO的web 。


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

注意: 你还可以使用 charset=UTF-8,但有时也会产生错误,因此,最好使用 utf8

如果有任何连接错误,它将抛出PDOException对象,它可以被缓存以进一步处理异常。

你还可以将多个驱动程序选项作为数组传递给第四个参数。 我建议传递将PDO放入异常模式的参数。 因为一些PDO驱动程序不支持原生准备语句,所以PDO执行准备的模拟。 它还允许你手动启用这里仿真。 要使用预准备的server-side语句,应该显式地将它的设置为 false

另一个是关闭在默认情况下在MySQL驱动程序中启用的准备仿真,但是准备仿真应该关闭,以使用 PDO 。

稍后我将解释为什么要关闭模拟,以找出原因,请检查这篇博文。

只有使用旧版本的MySQL,我不推荐使用它。

下面我展示了你如何做到它。


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

我们可以在构造之后设置属性?

,我们还可以使用 setAttribute 方法在PDO构造之后设置一些属性:


<?php
 $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_* 时常见的做法是:


<?php
//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 。( Run-time警告( non-fatal错误) ) 。 执行脚本未停止。)
  • 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 中,如下所示:


<?php
try {
//Connect as appropriate as 上面
 $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内容的函数外部捕获它可能更有意义:


<?php
 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并只读取错误日志,可以隐藏生产中的危险错误消息。

现在,在阅读上面的所有内容之后,你可能想: 当我想开始简单的SELECTINSERTUPDATE 或者 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数据中,它通过 ->fetch() 获得,这是你的语句句柄的一种方法。 在调用fetch之前,最好的方法是告诉PDO如何获取数据。 在下面的小节中我将解释。

获取模式

注意 PDO::FETCH_ASSOC 在上面的fetch()fetchAll() 代码中的使用。 这告诉 PDO 将行作为一个关联数组返回,字段名称作为键。 还有很多其他的抓取模式,我将逐一解释。

首先,我解释如何选择提取模式:


 $stmt->fetch(PDO::FETCH_ASSOC)

在上面,我一直在使用 fetch() 。 你也可以使用:

现在我来获取模式:

  • PDO::FETCH_ASSOC: 返回按结果集返回的列名称索引的数组
  • PDO::FETCH_BOTH ( 默认): 返回按结果集返回的列名称和 0 -indexed列编号索引的数组

还有更多的选择 ! 在 PDOStatement 获取文档中阅读它们。

获取行计数

不使用mysql_num_rows获取返回行的数目,你可以获得一个 PDOStatement 并执行 rowCount();,例如:


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

获取最后插入的ID


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


插入and更新或者删除语句

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语句并返回受影响行的数目)

( 稍后将进行插入和删除。)

上方法仅在查询未使用变量时有用。 但当你需要使用一个变量在查询中,永远永远不要像以上和那里为 预准备语句或参数化的语句 是。


预准备语句

。准备好的语句是什么,为什么需要它们
preparedstatement是预编译的SQL语句,它可以多次执行仅通过发送到服务器的数据。

使用准备好的语句的典型工作流如下( 从维基百科引用的3点 ):

  1. 准备: 模板是由应用程序创建的语句并将它的发送到数据库管理系统( 数据库管理系统) 。 某些值没有指定,称为参数,占位符或者绑定变量( 标签""下方):?

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

  2. DBMS在语句模板上解析,编译和执行查询优化,并存储结果而不执行它。

  3. :稍后执行,应用程序用品( 或者绑定) ( 可能返回一个结果),而DBMS执行该语句的参数的值。 应用程序可以按照不同的值多次执行语句。 在本例中,它可能为第二个参数提供'面包',而为第二个参数提供'1.00'。

可以通过在SQL中包含占位符来使用准备好的语句。 基本上没有占位符( 不要用上面的变量来尝试它),一个带有未命名占位符,一个带有命名占位符。

Q 。 所以衣服吧命名的占位符,并如何使用它们?
命名占位符。 使用带冒号的描述性名称,而不是问号。 我们不关心名称位置持有者的值/顺序:


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

面向对象的另一个不错的特性是,命名占位符可以将对象直接插入到数据库中,前提是属性与命名字段匹配。 例如:


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

Q 。 所以衣服吧未命名占位符,并如何使用他们?
一磅 让我们举个例子:


<?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() 方法。


选择,插入,更新,删除prepaired查询

1 选择


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

2 插入


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

3 删除


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

4 更新


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


备注:

然而,pdo/MySQL i 不是完全安全的。 检查答案 是准备好的语句,足以防止 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/*"));

首先,让我们从我们给大家的标准评论开始:

在新代码中, 请,请不要使用 mysql_* 等功能。 它们不再维护 ,并且已经被正式否决。 查看 的红色框? 请查阅 预处理语句 相反,并使用 PDO 或者 1 i - 本文将帮助你确定哪种。 如果你选择了 PDO,这里是一个很好的教程。

让我们通过句子,句子,解释:

  • 他们不再维护,并都被视为过时

    这意味着PHP社区正在逐渐放弃对这些旧函数的支持。 它们可能不存在于未来的( 最近) 版本中 ! 继续使用这些函数可能会在( 不是这样) 远未来破坏你的代码。

    新- ext/root现在是 正式弃用在 PHP 5.5之时

    更新ext/MySQL 将被删除在 PHP 7

  • 准备的语句 相反,你应该学会的-

    mysql_* 扩展不支持预准备语句,这是在非常有效的对抗手段( 其中之一) SQL注入 。 在你 database, MySQL从属应用程序中它修正了一个非常严重的漏洞可以允许攻击者执行. 任何可能的查询,以获得你的脚本和

    有关更多信息,请参阅 如何在PHP中阻止 SQL-injection

  • 查看红色框

    当你进入任何 mysql 功能手册页时,你会看到一个红色的方框,解释它不应该再被使用。

  • 使用PDO或者 MySQL i

    有更好的。更健壮和建立良好替代方法,PDO - PHP数据库对象,它提供了完整的面向对象的数据库交互方式后,和 1 i,这是一个MySQL具体的改进。

易于使用

分析和综合原因已经提到。 对于新手来说,停止使用过时的mysql_函数有一个更重要的动机。

的当代数据库api只是更容易使用。

它主要是绑定参数,可以简化代码。 并且使用优秀的教程( 如上所见)PDO编辑器的转换并不是太辛苦。

一次重写一个较大的代码库需要时间。 于这里中间alternative,相关这个框架的存在'

等效的pdo_*函数取代了 mysql_*

使用 <pdo_mysql.php> 你可以从旧的mysql_函数与最小的努力 switch 。 它添加了 pdo_ 函数包装器,替换了它们的mysql_

  1. 在每个必须与数据库交互的调用脚本中简单的include_once("pdo_mysql.php");

  2. 删除 mysql_pdo_ 到处,函数前缀 然后替换上面,

    • 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]";
    }
    
    

等 voilà 。
使用 pdo,你的代码是 .
现在是时候让利用了。

绑定参数可以很容易使用

你只需要一个不那么笨重的API 。

pdo_query() 为绑定参数添加非常简单的支持。 转换旧代码非常简单:

将变量移出SQL字符串。

  • 将它们作为逗号分隔函数参数添加到 pdo_query()
  • 放置问号 ? 作为占位符,变量在前面。
  • 去掉以前封闭的字符串值/变量的' 单引号。

lengthier代码的优势变得更加明显。

通常字符串变量不只是插入到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))));
}

这里最引人注目的Bug 是缺乏文档。 更多的筛选顺序是完全错误的顺序。

  • 正确的顺序应该是: deprecatedly stripslashes 作为最里面的调用,然后是 trim,之后是 strip_tagshtmlentities 为输出上下文,最后是 _escape_string 作为它的应用程序直接 preceed SQL intersparsing 。

  • 但作为第一步只需摆脱 _real_escape_string 电话。

  • 你可能需要保持你的现在,如果你的数据库和应用程序流的期望 HTML-context-safe sanitize() 函数字符串的剩余部分。 添加仅适用于HTML转义的注释。

  • 字符串/值处理被委托给PDO及其参数化语句。

  • 如果你在web净化功能中提到了 stripslashes(),它可能表示更高级别的监督。

    历史 note. 特性是正确的不推荐使用的。 它通常被错误地描述为失败的安全特性。 但是magic_quotes是一个失败的安全特性,因为网球球作为营养源失败了。 这并不是他们的目的。

    php2/fi中的原始实现只使用"引号将被自动转义,以便直接将表单数据传递给 msql 。 查询"显式地介绍了它。 值得注意的是,accidentially安全地使用格式的,因为它只支持 ASCII 。
    于MySQL和misdocumented相关it,然后 php3/Zend 再引入 但最初它只是一个方便功能,而不是为了安全。

准备语句如何不同

当你将字符串变量置到SQL查询中时,它不会变得更加复杂。 对于MySQL来说,再次隔离代码和数据也是多余的。

SQL注入只是当数据进入代码上下文时。 数据库服务器不能在以后找到PHP在中间粘贴变量中间查询子句的位置。

使用绑定参数,你可以在PHP代码中分离SQL代码和SQL-context值。 但它不会在幕后被再次打乱( 除了 emulate_prepares:: ) 。 数据库接收不完整的SQL命令和 1: 1 变量值。

虽然这个回答强调你应该关注拖放的可读性优势 mysql_ 由于这种可见和技术的数据/代码分离,有时也会有性能优势。

请小心,参数绑定方法也不神奇的全方位的服务总是针对 SQL注入。 它处理数据/值的最常用的用法。 但是不能为列名称/表标识符,动态子句构造帮助,或者仅仅是普通数组值列表。

混合的PDO使用

这些 pdo_* 包装函数生成一个 coding-friendly stop-gap 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();

在大多数情况下,你会得到更多有用的警告,而mysql_或者通常在失败的查询之后提供。

其他选项

所以这个希望可视化实用几个理由,并一个worthwile途径落入 mysql_

只是切换到 pdo_query() 也只是一个前端的前端。

除非你还引入了参数绑定,或者可以使用更好的API中的其他内容,这是一个毫无意义的switch 。 我希望它被描述得足够简单,不会让discouragement对新的人更进一步。 ( 教育通常比禁止更好。)

虽然它适合simplest-thing-that-could-possibly-work类别,但它仍然是非常实验性的代码。 我只是在周末写的。 还有太多的替代方案。 仅适用于 PHP数据库抽象 web 和浏览。 这些任务总是有很多优秀的库。

如果你想进一步简化你的数据库交互,像 paris/Idiorm 这样的映射器就值得一试。 就像没有人再使用JavaScript中乏味的DOM一样,现在你就不必再关注原始的数据库界面了。

mysql_ 函数包括:

  1. 过期- 它们不再被维护
  2. 不允许你轻松移动到其他数据库后端
  3. 不支持预准备语句,因此
  4. 鼓励程序员使用连接构建查询,导致 SQL注入 漏洞

谈到技术,只有很少,特别具体,很少使用。 你很可能永远不会在你的生活中使用它们。
也许我太无知了,但我从来没有机会使用它们,比如

  • non-blocking,异步查询
  • 返回多个结果集的存储过程
  • 加密( SSL )
  • 压缩方式

在MySQL扩展向更时尚的东西。- 这些无疑都技术原因如果你需要它们来移动拿走了。

;也有许多学者也non-technical一些问题较为困难,所以会让你体验

  • 对现代PHP版本使用这些功能将提高deprecated-level的注意。 它们可以被关闭。
  • 在不久的将来,它们可能会从默认的PHP构建中删除。 没什么大不了的作为 mydsql ext将被移动到PECL和每个宿主会很乐意向 complie PHP的它,因为他们不想失去的客户的网站正在几十年来。
  • Stackoverflow社区的强大抵抗。 Еvery时间你提到这些诚实的函数,你被告知它们是严格的禁忌。
  • 作为一个普通的php用户,很可能你使用这些函数的想法是error-prone和错误的。 正是因为这些大量的教程和手册,你的错误。 不是函数本身- 我必须强调它- 但是它们使用的方式。

后一个问题是一个问题。
但在我看来,建议的解决方案并不是更好。
在once,在我看来太 idealistic 实现的梦所有那些PHP填置合适,用户可以学习如何处理SQL查询 最可能的是,它们只需要改变对 mysqli_* mysql_*机械方式离开该方法相同的 。 特别是因为 MySQL i 使预处理语句使用起来令人难以置信的痛苦和麻烦。
更不用说,预准备语句本机 是不足以保护免受SQL注入的影响,而且这两次1 i 也没有PDO提供了一个解决方案。

在右侧ways,是这样,而不是战斗这诚实的扩展,我更希望对抗错误的做法和教育 people.

也有一些错误或者non-significant原因,比如

  • 不支持存储过程( 我们使用 mysql_query("CALL my_proc");的年龄)
  • 不支持事务( 同上)
  • 不支持多个语句( 需要它们)?
  • 不在活动开发( 所以? 它是否会影响你的?
  • 缺少面向对象接口( 创建一个接口需要几个小时)
  • 不支持预准备语句或者参数化查询

后一个是有趣的点。 尽管 MySQL ext不支持的native原生,但它们并不是安全的。 我们可以使用手工处理的占位符( 就像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);

,,一切都是参数化和安全的。

但是,如果你不喜欢手册中的红色框,就会出现一个选择问题: MySQL i 或者 PDO?

答案应该如下:

  • 如果你理解的必要性,使用的是 数据库抽象层,并寻找一位开发人员可以通过创建一个,1 i 是一个非常好的选择,因为它确实支持许多mysql-specific特性。
  • 把最精彩的内容在mysqli,如果,就像网上浪费大部分PHP各位,你在使用原始的API调用应用程序代码( 这实际上是错误的实践) - PDO是唯一的选择,因为这个扩展装作不只是API而是一个与其中两个使得PDO批判地 distinguished: semi-DAL,仍不完整,而是提供许多重要的功能部件,

    • 不像1 i,PDO可以将占位符绑定通过值 没有几个屏幕,这使得动态构建查询可行,规格相当乱码。
    • MySQL i 不同,PDO总是可以在一个简单的数组中返回查询结果,而 MySQL i 只能在MySQL的安装中实现。

所以,如果你是一个普通PHP用户使用本机预处理语句时进行错误检查而且希望节约一大堆,PDO - 又一次- - 是唯一的选择。
然而,PDO并不是一个 silver,它也有困难。
PDO标记维基, 所以我编写的解决方案满足所有常见陷阱的杂件

然而,每个讨论扩展的人总是缺少关于 MySQL i 和PDO的 2 个重要事实:

  1. 准备好的语句不是silver项目符号。 有一些动态标识符不能用prepared语句绑定。 有未知参数的动态查询,使得查询构建成为一个困难的任务。

  2. 在应用程序 code, 既不mysqli_*也不应该 appeared. PDO功能
    应该在它们之间是一个 抽象层和应用程序代码,其中有一些会做所有肮脏的工作的绑定,循环,错误处理,等等 在里面,应用程序代码进行干燥,清洁。 尤其对于复杂的情况比如动态查询构建。

所以,切换到PDO或者 MySQL i 是不够的。 必须使用 ORM,查询生成器或者任何数据库抽象类,而不是在代码中调用原始API函数。
你的应用程序代码和MySQL之间,逆序- 如果你有一个抽象层 API - 它并没有真正起作用哪一个引擎使用的是 你可以使用 MySQL ext,直到它然后是否决,最后容易重新编写你的抽象类来另一个引擎,上所有应用程序代码不变。

下面是一些基于我的safemysql类的示例,以展示此类抽象类应该如何:


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

将这一行与 所需的代码的代码量进行比较。
然后与的疯狂代码相比,你将需要原始 MySQL i 准备语句。 注意,错误处理,分析,查询日志已经构建并运行。


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

你几乎找不到一个示例来处理这种实际案例。
而且太罗嗦了,很可能不安全。

是这样的,再一次的- 那不只是裸设备应该是你的问题,但对于愚蠢的例子从手册对初学者,但抽象类,不仅可用来解决,不管实际应用问题。

有很多原因,但最重要的可能是那些函数鼓励不安全的编程实践,因为它们不支持prepared语句。 预准备语句有助于防止 SQL注入 攻击。

使用 mysql_* 功能时,你必须记得运行user-supplied参数通过 mysql_real_escape_string() 。 如果你只在一个地方忘记了,或者你刚好逃脱了部分输入,你的数据库可能受到攻击。

PDO 或者 mysqli 中使用预准备语句将使这些编程错误变得更加困难。

因为( 其他原因) 很难确保输入数据被清除。 如果你使用参数化查询,就像对PDO或者 MySQL i 那样,你可以完全避免风险。

例如some-one可以使用"enhzflep ) ;将表用户"作为用户名。 旧的函数将允许执行每个查询的多个语句,所以像那个讨厌的混蛋可以删除整个表。

如果要使用 MySQL i的PDO,user-name将end-up作为"enhzflep ) ;除去表用户"

查看这里:http://bobby-tables.com/

于新用户提问,以提高他们的这个答案是为了展示多么微不足道的小报code,它是绕过写得不好 PHP user-validation代码。( 并使用) 这些攻击如何工作和如何使用一个安全的预编译语句- 而基本上,替换旧的MySQL函数为什么StackOverflow用户( 可能有很多代表) 是 barking.

首先,请随意创建这个测试MySQL数据库( 我已经把我的prep ):


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>

乍一看似乎足够合法。

用户必须输入登录名和密码?

Brilliant,不输入以下内容:


user: bob
pass: somePass

并提交它。

输出如下:


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

超级按预期的方式工作,现在试试实际的用户名和密码: !


user: Fluffeh
pass: mypass

令人吃惊Hi-fives所有回合都正确验证了管理员。 这是完美的 !

嗯,不是真的。假设用户是一个聪明的小人。 假设此人是我。

输入以下内容:


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

输出是:


The check passed. We have a verified admin!

恭喜,你只允许我输入你的super-protected管理员部分,输入一个假用户名和一个假密码。 严肃地说,如果你不相信我,用我提供的代码创建数据库,运行这个PHP代码- 它看起来确实验证了用户名和密码。

所以,答案是,这就是为什么你被吼。

那么,让我们看看什么是错误的,为什么我刚刚进入你的super-admin-only-bat-cave 。 我猜了一下,假设你对输入不小心,只是直接把它们传给数据库。 我以一种改变你实际运行的查询的方式构造输入。 那么它应该是什么,它最终是什么?


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对象完成的,其次,它使用了一个prepared语句。 现在,你要求的prepred语句是什么? 这是一种告诉数据库运行查询的方法,我们将要运行的查询。 在本例中,我们告诉数据库: "嗨,我将运行一个select语句,该语句需要标识,userid,并从用户用户那里传递用户标识符,并且传递也是变量。"。

然后,在execute语句中,我们通过一个数组传递一个包含所有变量的数组。

结果非常精彩。让我们再次尝试这些用户名和密码组合:


user: bob
pass: somePass

用户未被验证。Awesome 。

怎么办:


user: Fluffeh
pass: mypass

哦,我有点激动,它工作了: 检查已经通过。我们有一个经过验证的管理员 !

现在,让我们尝试一下一个聪明的chap将要输入的数据,以便通过我们的小验证系统:


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

这次,我们得到以下信息:


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

这就是为什么你在发布问题时被吼了- 这是因为人们可以看到你的代码可以绕过wihout甚至尝试。 请使用这里问题和答案来改进你的代码,使它的更加安全,并使用当前使用的函数。

最后,这不是说这是完美的代码。 还有更多的事情可以改进,使用哈希密码,确保,确保在数据库中存储sensetive信息,确保在数据库中存储信息,确保在编写网站和应用程序时,我刚才提到的是代码,而不是刚才提到的其他东西。 编写最好的代码,而不是最基本的代码。

...