提问人:Madara's Ghost 提问时间:10/12/2012 最后编辑:DharmanMadara's Ghost 更新时间:8/3/2023 访问量:257242
为什么我不应该在 PHP 中使用 mysql_* 函数?
Why shouldn't I use mysql_* functions in PHP?
问:
为什么不应该使用函数的技术原因是什么?(例如,或)?mysql_*
mysql_query()
mysql_connect()
mysql_real_escape_string()
为什么我应该使用其他东西,即使它们在我的网站上工作?
如果它们在我的网站上不起作用,为什么我会收到类似
警告:mysql_connect():没有这样的文件或目录
答:
功能:mysql_
- 已过时 - 不再维护
- 不允许你轻易移动到另一个数据库后端
- 因此不支持预准备语句
- 鼓励程序员使用串联来构建查询,导致SQL注入漏洞
评论
mysqli_
mysql_*
原因有很多,但也许最重要的一个原因是,这些函数鼓励了不安全的编程实践,因为它们不支持准备好的语句。预准备语句有助于防止 SQL 注入攻击。
使用函数时,必须记住通过 运行用户提供的参数。如果您只在一个地方忘记了,或者您碰巧只逃脱了部分输入,您的数据库可能会受到攻击。mysql_*
mysql_real_escape_string()
在 or 中使用预处理语句会使这类编程错误更难犯。PDO
mysqli
评论
MySQL扩展:
- 未在积极开发中
- 自 PHP 5.5(2013 年 6 月发布)起正式弃用。
- 自 PHP 7.0(2015 年 12 月发布)起已完全删除
- 这意味着截至 2018 年 12 月 31 日,它不存在于任何受支持的 PHP 版本中。如果您使用的是支持它的 PHP 版本,那么您使用的版本不会修复安全问题。
- 缺少 OO 接口
- 不支持:
- 非阻塞异步查询
- 预准备语句或参数化查询
- 存储过程
- 多个语句
- 交易
- “新”密码身份验证方法(在MySQL 5.6中默认打开;在5.7中需要)
- MySQL 5.1 或更高版本中的任何新功能
由于它已被弃用,因此使用它会使您的代码难以适应未来。
缺乏对预准备语句的支持尤为重要,因为它们提供了一种更清晰、更不容易出错的方法来转义和引用外部数据,而不是使用单独的函数调用手动转义数据。
请参阅 SQL 扩展的比较。
评论
Doesn't support: Non-blocking, asynchronous queries
- 这也是不使用 PDO 的一个原因,它不假设异步查询(与 mysqli 不同)
因为(除其他原因外)确保对输入数据进行清理要困难得多。如果您使用参数化查询,就像使用 PDO 或 mysqli 一样,您可以完全避免风险。
例如,有人可以用作用户名。旧函数将允许每个查询执行多个语句,因此像那个讨厌的窃听器这样的东西可以删除整个表。"enhzflep); drop table users"
如果要使用mysqli的PDO,则用户名最终将为。"enhzflep); drop table users"
请参阅 bobby-tables.com。
评论
The old functions will allow executing of multiple statements per query
- 不,他们不会。这种注入在 ext/mysql 中是不可能的 - 在 PHP 和 MySQL 中实现这种注入的唯一方法是使用 MySQLi 和函数时。ext/mysql 和未转义字符串可能的注入方式是从数据库中提取无法访问的数据。在某些情况下,可以注入子查询,但仍然无法以这种方式修改数据库。mysqli_multi_query()
' OR '1' = '1
首先,让我们从我们给大家的标准评论开始:
请不要在新代码中使用
mysql_*
函数。它们不再维护,并被正式弃用。看到红框了吗?请改用预准备语句,并使用 PDO 或 MySQLi - 本文将帮助您确定哪个语句。如果你选择PDO,这里有一个很好的教程。
让我们逐句浏览一下,并解释一下:
它们不再维护,并被正式弃用
这意味着 PHP 社区正在逐渐放弃对这些非常古老的函数的支持。它们很可能在PHP的未来(最新)版本中不存在!继续使用这些函数可能会在(不是)遥远的将来破坏您的代码。
新增功能!- ext/mysql 现已从 PHP 5.5 开始正式弃用!
新!ext/mysql 已在 PHP 7 中删除。
相反,你应该学习准备好的陈述
mysql_*
extension 不支持预处理语句,这是(除其他外)针对 SQL 注入的非常有效的对策。它修复了 MySQL 依赖应用程序中一个非常严重的漏洞,该漏洞允许攻击者访问您的脚本并对您的数据库执行任何可能的查询。有关更多信息,请参阅如何防止 PHP 中的 SQL 注入?
看到红框了吗?
当您转到任何功能手册页面时,您会看到一个红色框,说明不应再使用它。
mysql
使用 PDO 或 MySQLi
有更好、更健壮和构建良好的替代方案,PDO - PHP Database Object,它为数据库交互提供了完整的 OOP 方法,以及 MySQLi,这是 MySQL 特有的改进。
评论
IN (...) construct
PHP 提供了三种不同的 API 来连接到 MySQL。这些是 mysql
(从 PHP 7 中删除)、mysqli
和 PDO
扩展。
这些功能曾经非常流行,但不再鼓励使用它们。文档团队正在讨论数据库安全情况,教育用户放弃常用的 ext/mysql 扩展是其中的一部分(查看 php.internals:弃用 ext/mysql)。mysql_*
后来的PHP开发团队已经决定在用户连接到MySQL时生成E_DEPRECATED
错误,无论是通过 ,还是内置的隐式连接功能。mysql_connect()
mysql_pconnect()
ext/mysql
ext/mysql
从 PHP 5.5 开始被正式弃用,从 PHP 7 开始被删除。
看到红框了吗?
当您进入任何功能手册页面时,您会看到一个红色框,说明不应再使用它。mysql_*
为什么
远离不仅关乎安全性,还关乎可以访问 MySQL 数据库的所有功能。ext/mysql
ext/mysql
是为 MySQL 3.23 构建的,从那时起只增加了很少的内容,同时主要保持与这个旧版本的兼容性,这使得代码更难维护。缺少不支持的功能包括:(来自 PHP 手册)。ext/mysql
不使用 mysql_*
功能的原因:
缺乏对预准备语句的支持尤为重要,因为它们提供了一种更清晰、更不容易出错的转义和引用外部数据的方法,而不是使用单独的函数调用手动转义外部数据。
请参阅 SQL 扩展的比较。
禁止弃用警告
当代码转换为 / 时,可以通过在 php.ini 中设置为排除来抑制错误MySQLi
PDO
E_DEPRECATED
error_reporting
E_DEPRECATED:
error_reporting = E_ALL ^ E_DEPRECATED
请注意,这也将隐藏其他弃用警告,但是,这些警告可能适用于MySQL以外的其他内容。(摘自PHP手册)
Dejan Marjanovic 撰写的 PDO 与 MySQLi:您应该使用哪个?
更好的方法是,我现在正在写一个简单的教程。PDO
PDO
简单简短的PDO教程
Q.我脑海中的第一个问题是:什么是“PDO”?
答:“PDO(PHP数据对象)是一个数据库访问层,提供对多个数据库的统一访问方法。
连接到 MySQL
使用函数,或者我们可以用旧的方式说(在 PHP 5.5 及更高版本中已弃用)mysql_*
$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);
使用:您需要做的就是创建一个新对象。构造函数接受用于指定数据库源的参数,构造函数主要采用四个参数,分别是(数据源名称)和可选的 、.PDO
PDO
PDO
DSN
username
password
在这里,我想你对所有的东西都很熟悉,除了;这是 中的新功能。A 基本上是一串选项,用于告知要使用的驱动程序以及连接详细信息。有关进一步参考,请查看 PDO MySQL DSN。DSN
PDO
DSN
PDO
$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()
不是处理错误的好方法,因为我们无法处理 中的事情。它只会突然结束脚本,然后将错误回显到您通常不想显示给最终用户的屏幕上,并让血腥的黑客发现您的架构。或者,函数的返回值通常可以与 mysql_error() 结合使用来处理错误。die
mysql_*
PDO
提供了一个更好的解决方案:异常。我们所做的任何事情都应该包装在一个 - 块中。我们可以通过设置 error mode 属性来强制进入三种错误模式之一。下面有三种错误处理模式。PDO
try
catch
PDO
PDO::ERRMODE_SILENT
.它只是设置错误代码,其行为与您必须检查每个结果然后查看以获取错误详细信息的位置几乎相同。mysql_*
$db->errorInfo();
PDO::ERRMODE_WARNING
举。(运行时警告(非致命错误)。脚本的执行不会停止。E_WARNING
PDO::ERRMODE_EXCEPTION
:抛出异常。它表示 PDO 引发的错误。你不应该从你自己的代码中抛出一个。有关 PHP 中的异常的更多信息,请参阅异常。当它没有被抓住时,它的行为非常像。但与 不同的是,如果您选择这样做,可以优雅地捕获和处理。PDOException
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());
}
你不必处理 - 现在。你可以在任何合适的时间抓住它,但我强烈建议你使用-。此外,在调用 stuff 的函数外部捕获它可能更有意义: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
选择数据
所以你要做的是: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 之前,最好的方法是告诉 PDO 你希望如何提取数据。在下面的部分中,我将解释这一点。->fetch()
提取模式
请注意上面的 and 代码中的用法。这告诉将行作为关联数组返回,并将字段名称作为键。还有许多其他获取模式,我将一一解释。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
Fetch 文档中阅读它们。
获取行计数:
您可以获取 and do ,而不是用于获取返回的行数,例如: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();
插入和更新或删除语句
我们在功能上所做的是: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 语句并返回受影响的行数。
稍后将介绍插入和删除。
上述方法仅在查询中未使用变量时才有用。但是,当您需要在查询中使用变量时,永远不要尝试像上面那样的预处理语句或参数化语句。
准备好的发言
问。什么是准备好的声明,为什么我需要它们?
答:预准备语句是预编译的 SQL 语句,只需向服务器发送数据即可多次执行。
使用预准备语句的典型工作流程如下(引自维基百科三点 3 点):
- 准备:语句模板由应用程序创建并发送到数据库管理系统 (DBMS)。某些值未指定,称为参数、占位符或绑定变量(标记如下):
?
`INSERT INTO PRODUCT (name, price) VALUES (?, ?)`
- DBMS对语句模板进行解析、编译和查询优化,并将结果存储而不执行。
- 执行:稍后,应用程序提供(或绑定)参数值,DBMS 执行语句(可能返回结果)。应用程序可以根据需要使用不同的值多次执行该语句。在此示例中,它可能为第一个参数和第二个参数提供“Bread”。
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);
问。那么现在,什么是未命名的占位符,我该如何使用它们?
答:让我们举个例子:
<?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
选择
:$stmt = $db->prepare(“从表中选择 * 其中 id=:id 和 name=:name”); $stmt->执行(array(':name' => $name, ':id' => $id)); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
插入
:$stmt = $db->prepare(“插入表(field1,field2) 值(:field1,:field2)”); $stmt->执行(array(':field1' => $field 1, ':field2' => $field 2)); $affected_rows = $stmt->rowCount();
删除
:$stmt = $db->prepare(“从表中删除 WHERE id=:id”); $stmt->bindValue(':id', $id, PDO::P ARAM_STR); $stmt->执行(); $affected_rows = $stmt->rowCount();
更新
:$stmt = $db->prepare(“UPDATE table SET name=?其中 id=?“); $stmt->执行(数组($name, $id)); $affected_rows = $stmt->rowCount();
注意:
但是和/或并不完全安全。检查答案 PDO 准备的语句是否足以防止 SQL 注入? 作者:ircmaxell。另外,我引用了他回答中的部分内容:PDO
MySQLi
$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 /*"));
说到技术原因,只有少数几个,非常具体,很少使用。很可能你永远不会在你的生活中使用它们。
也许我太无知了,但我从来没有机会使用它们之类的东西
- 非阻塞异步查询
- 返回多个结果集的存储过程
- 加密 (SSL)
- 压缩
如果你需要它们 - 这些无疑是从mysql扩展转向更时尚和更现代的技术原因。
然而,也有一些非技术性的问题,这可能会使你的体验有点困难
- 在现代 PHP 版本中进一步使用这些函数将引发弃用级别的通知。它们可以简单地关闭。
- 在遥远的将来,它们可能会从默认的 PHP 构建中删除。也没什么大不了的,因为 mydsql ext 将被移动到 PECL 中,每个托管商都很乐意用它来编译 PHP,因为他们不想失去那些网站已经工作了几十年的客户。
- 来自 Stackoverflow 社区的强烈抵制。每当你提到这些诚实的功能时,你就会被告知它们受到严格的禁忌。
- 作为一个普通的PHP用户,你使用这些函数的想法很可能是容易出错和错误的。仅仅因为所有这些众多的教程和手册教你错误的方式。不是函数本身 - 我必须强调它 - 而是它们的使用方式。
后一个问题是一个问题。
但是,在我看来,提出的解决方案也好不到哪里去。
在我看来,所有PHP用户都能立即学会如何正确处理SQL查询,这似乎是一个过于理想化的梦想。最有可能的是,他们只是机械地将 mysql_* 更改为 mysqli_*,而方法保持不变。特别是因为 mysqli 使准备好的语句使用变得难以置信、痛苦和麻烦。
更不用说本机准备语句不足以防止 SQL 注入,而且 mysqli 和 PDO 都没有提供解决方案。
因此,与其与这种诚实的扩展作斗争,我宁愿与错误的做法作斗争,并以正确的方式教育人们。
此外,还有一些虚假或不重要的原因,例如
- 不支持存储过程(我们使用了很长时间)
mysql_query("CALL my_proc");
- 不支持交易(同上)
- 不支持多个语句(谁需要它们?
- 没有在积极开发中(那又怎样?它对你有什么实际影响吗?
- 缺少一个OO接口(创建一个接口需要几个小时)
- 不支持预准备语句或参数化查询
最后一个是一个有趣的观点。尽管 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 标签 wiki 中的所有常见陷阱和复杂情况编写了解决方案
然而,每个谈论扩展的人总是错过了关于 Mysqli 和 PDO 的 2 个重要事实:
准备好的声明不是灵丹妙药。有些动态标识符不能使用预准备语句进行绑定。存在参数数量未知的动态查询,这使得查询构建成为一项艰巨的任务。
应用程序代码中不应出现 mysqli_* 和 PDO 函数。
在它们和应用程序代码之间应该有一个抽象层,它将完成内部绑定、循环、错误处理等所有肮脏的工作,使应用程序代码变得干涸和干净。特别是对于动态查询构建等复杂情况。
因此,仅仅切换到PDO或mysqli是不够的。必须使用 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);
将这一行与PDO所需的代码量进行比较。
然后与原始 Mysqli 准备语句所需的大量代码进行比较。
请注意,错误处理、分析、查询日志记录已经内置并正在运行。
$insert = array('name' => 'John', 'surname' => "O'Hara");
$db->query("INSERT INTO users SET ?u", $insert);
将其与通常的 PDO 插入进行比较,当每个字段名称重复 6 到 10 次时 - 在所有这些众多命名占位符、绑定和查询定义中。
另一个例子:
$data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);
你很难找到一个PDO的例子来处理这种实际情况。
而且它太啰嗦了,而且很可能是不安全的。
所以,再一次 - 它不仅仅是原始驱动程序应该是你关心的问题,而是抽象类,不仅对初学者手册中的愚蠢例子有用,而且对解决任何现实生活中的问题都很有用。
评论
mysql_*
使漏洞非常容易产生。由于PHP被很多新手用户使用,因此在实践中是有害的,即使理论上它可以顺利使用。mysql_*
everything is parameterized and safe
- 它可能被参数化,但你的函数不使用真正的预准备语句。
Not under active development
deprecated
not under active development
no active development
deprecated
这个答案是为了说明绕过写得不好的PHP用户验证代码是多么微不足道,这些攻击是如何(以及使用什么)工作的,以及如何用安全的准备好的语句替换旧的MySQL函数 - 以及基本上,为什么Stack Overflow用户(可能有很多声誉点)对新用户咆哮,提出问题以改进他们的代码。
首先,请随时创建这个测试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
// Let's 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>
主要区别在于没有更多的功能。其次,这一切都是通过 PDO 对象完成的,并且它使用准备好的语句。现在,你问准备好的陈述是什么?这是一种在运行查询之前告诉数据库以及我们将要运行的查询是什么的方法。在本例中,我们告诉数据库:“嗨,我将运行一个 select 语句,想要 id、userid 并从表 users 中传递,其中 userid 是一个变量,并且传递也是一个变量。mysql_*
然后,在 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...
这就是为什么您在发布问题时被大喊大叫的原因。这是因为人们可以看到你的代码甚至可以被绕过,甚至不需要尝试。请使用此问题和答案来改进您的代码,使其更安全并使用最新的函数。
最后,这并不是说这是完美的代码。您可以做更多的事情来改进它。例如,使用散列密码,并确保在数据库中存储敏感信息时,不要将其存储为纯文本,进行多级验证 - 但实际上,如果您只是将旧的易注入代码更改为此代码,您将在编写好代码的道路上走得很好 - 事实上,您已经走到了这一步并且仍在阅读,这给了我一种希望,即您不会只有在编写网站和应用程序时才实现这种类型的代码,但您可能会出去研究我刚才提到的其他东西,甚至更多。尽可能编写最好的代码,而不是几乎无法正常工作的最基本代码。
评论
mysql_*
易用性
已经提到了分析和综合原因。对于新手来说,停止使用过时的mysql_功能有更重要的动机。
现代数据库 API 更易于使用。
主要是绑定的参数可以简化代码。有了优秀的教程(如上所示),向PDO的过渡并不过分艰巨。
但是,一次重写更大的代码库需要时间。这个中间替代方案存在的理由:
等效的 pdo_* 函数代替 mysql_*
使用 <pdo_mysql.php>,您可以毫不费力地从旧的mysql_功能切换。它添加了函数包装器,以取代其对应物。pdo_
mysql_
只需
“pdo_mysql.php”
每个必须与数据库交互的调用脚本即可。include_once(
);
删除所有位置的
函数前缀,并将其替换为mysql_
pdo_
。变得mysql_
pdo_
connect()
connect()
变得mysql_
pdo_
query()
query()
变得mysql_
pdo_
num_rows()
num_rows()
变得mysql_
pdo_
insert_id()
insert_id()
变得mysql_
pdo_
fetch_array()
fetch_array()
变得mysql_
pdo_
fetch_assoc()
fetch_assoc()
变得mysql_
pdo_
real_escape_string()
real_escape_string()
- 等等......
您的代码将以相似的方式工作,并且看起来仍然大致相同:
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]"; }
瞧。
您的代码正在使用 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_* 仍允许 OR 之一。
只是不要转义变量并将其绑定到同一个查询中。
- 占位符功能由其背后的实际 PDO 提供。
- 因此,以后也允许占位符列表。
:named
更重要的是,您可以在任何查询后面安全地传递 $_REQUEST[] 变量。当提交的字段与数据库结构完全匹配时,它甚至更短:<form>
pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);
如此简单。但是,让我们回到一些更多的重写建议和技术原因,说明为什么你可能想要摆脱和逃避。mysql_
修复或删除任何老式的 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))));
}
这里最明显的错误是缺乏文档。更重要的是,过滤的顺序完全错误。
正确的顺序应该是:弃用为最里面的调用,然后是 ,然后是 ,用于输出上下文,并且只有最后 as 其应用程序应直接在 SQL 内部稀疏之前。
stripslashes
trim
strip_tags
htmlentities
_escape_string
但作为第一步,只需摆脱
_real_escape_string
电话。如果数据库和应用程序流需要 HTML 上下文安全的字符串,则现在可能必须保留函数的其余部分。添加一条注释,说明它今后只应用 HTML 转义。
sanitize()
字符串/值处理委托给 PDO 及其参数化语句。
如果您的消毒功能中提到了任何内容,则可能表明存在更高级别的疏忽。
stripslashes()
这通常是为了撤消已弃用
magic_quotes
的伤害(双重转义)。然而,最好集中固定,而不是一串一串地固定。使用用户空间反转方法之一。然后删除函数中的。
stripslashes()
sanitize
关于magic_quotes的历史笔记。该功能已被正确弃用。然而,它经常被错误地描述为失败的安全功能。但是,magic_quotes是一个失败的安全功能,就像网球作为营养来源失败一样。那根本不是他们的目的。
PHP2/FI 中的原始实现明确地引入了它,只是“引号将自动转义,从而更容易将表单数据直接传递给 msql 查询”。值得注意的是,与 mSQL 一起使用是意外安全的,因为它仅支持 ASCII。
然后 PHP3/Zend 重新引入了 magic_quotes for MySQL,并错误地记录了它。但最初它只是一个便利功能,并不是为了安全。
准备好的语句有何不同
当您将字符串变量打乱到 SQL 查询中时,它不仅会变得更加复杂。对于MySQL来说,再次分离代码和数据也是多余的工作。
SQL注入只是当数据渗入代码上下文时。数据库服务器以后无法发现 PHP 最初在查询子句之间粘附变量的位置。
使用绑定参数,可以在 PHP 代码中分隔 SQL 代码和 SQL 上下文值。但它不会在幕后再次被洗牌(PDO::EMULATE_PREPARES除外)。数据库接收不可变的 SQL 命令和 1:1 变量值。
虽然这个答案强调你应该关心掉的可读性优势。由于这种可见的技术数据/代码分离,有时也存在性能优势(重复的 INSERT,只有不同的值)。mysql_
请注意,参数绑定仍然不是针对所有 SQL 注入的神奇的一站式解决方案。它处理数据/值的最常见用途。但不能将列名/表标识符列入白名单,不能帮助动态子句构造,或者不能只列出普通的数组值列表。
混合 PDO 使用
这些包装函数构成了编码友好的权宜之计 API。(如果不是因为特殊的函数签名偏移,这几乎是可能的情况)。它们在大多数情况下也会暴露真正的 PDO。
重写不必止步于使用新的pdo_函数名称。您可以将每个 pdo_query() 一个接一个地转换为普通的 $pdo->prepare()->execute() 调用。pdo_*
MYSQLI
但是,最好再次从简化开始。例如,常见的结果获取:
$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 中利用其他内容,否则这是一个毫无意义的切换。我希望它描绘得足够简单,不会让新人进一步气馁。(教育通常比禁止更有效。
虽然它符合最简单的事情可能工作的类别,但它也仍然是非常实验性的代码。我刚刚在周末写了它。然而,有很多选择。只需谷歌搜索PHP数据库抽象并浏览一下。一直以来,都会有很多优秀的库来完成这样的任务。
如果您想进一步简化数据库交互,像 Paris/Idiorm 这样的地图绘制工具值得一试。就像没有人再在 JavaScript 中使用平淡无奇的 DOM 一样,现在您不必照看原始数据库接口。
评论
pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);
pdo_query("INSERT INTO users VALUES (?, ?, ?), $_POST); $_POST = array( 'username' => 'lawl', 'password' => '123', 'is_admin' => 'true');
pdo_real_escape_string()
<- 这甚至是一个真正的功能,我找不到任何文档吗?请为此发布来源。
MySQL扩展是三者中最古老的,是开发人员用来与MySQL通信的原始方式。由于PHP和MySQL的较新版本进行了改进,此扩展现在已被弃用,以支持其他两个替代方案。
MySQLi是用于处理MySQL数据库的“改进”扩展。它利用了较新版本的MySQL服务器中可用的功能,向开发人员公开了面向函数和面向对象的接口,并做了一些其他漂亮的事情。
PDO 提供了一个 API,该 API 整合了以前分布在主要数据库访问扩展(即 MySQL、PostgreSQL、SQLite、MSSQL 等)中的大多数功能。该接口公开高级对象,供程序员使用数据库连接、查询和结果集,低级驱动程序执行与数据库服务器的通信和资源处理。PDO 正在进行大量讨论和工作,它被认为是在现代专业代码中使用数据库的适当方法。
评论
我发现上面的答案真的很长,所以总结一下:
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 中,但在以后的版本中不推荐使用。
评论
可以使用 mysqli 或 PDO 定义几乎所有函数。只需将它们包含在旧的 PHP 应用程序之上,它就可以在 PHP7 上运行。我的解决方案在这里。mysql_*
<?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;
}
不要使用 mysql,因为它已被弃用。请改用 Mysqli。
弃用的含义:
这意味着不要使用某些特定的功能/方法/软件特性/特定的软件实践。它只是意味着不应该使用它,因为该软件中有(或将有)更好的替代方案应该被使用。
使用已弃用的函数时可能会出现几个常见问题:
1. 函数完全停止工作:应用程序或脚本可能依赖于不再受支持的函数。因此,使用它们的改进版本或替代方案。
2. Warning messages display about deprecation: These messages don’t normally interfere with site functionality. However, in some cases, they might disrupt the process of the server sending headers.
For example: This can cause login issues (cookies/sessions don’t get set properly) or forwarding issues (301/302/303 redirects).
Keep in mind that:
-Deprecated software is still a part of the software.
-Deprecated code is just a status (label) of the code.
Key differences in MYSQL vs MYSQLI mysql*
- Old database driver
- MySQL can only be used procedurally
- No protection from SQL injection attack
- Was deprecated in PHP 5.5.0 and was removed in PHP 7
mysqli
- New database driver
- Currently under usage
- Prepared statements protect from attacks
评论
There isn't any need to update if you are sure you don't want to upgrade the PHP version. But at the same time, you won't get security updates either which will make your website more vulnerable to hackers. That's the main reason.
评论