参考:啥是使用 MySQL 扩展的完美代码示例? [关闭]
Posted
技术标签:
【中文标题】参考:啥是使用 MySQL 扩展的完美代码示例? [关闭]【英文标题】:Reference: What is a perfect code sample using the MySQL extension? [closed]参考:什么是使用 MySQL 扩展的完美代码示例? [关闭] 【发布时间】:2011-09-06 02:05:38 【问题描述】:这是为了创建一个社区学习资源。目标是提供不会重复在复制/粘贴 php 代码中经常出现的可怕错误的良好代码示例。我已请求将其制作为社区 Wiki。
这并不是一场编码竞赛。这不是为了找到最快或最紧凑的查询方式 - 它是为了提供一个很好的、易读的参考资料,尤其是对于新手。
每天,使用 Stack Overflow 上的 mysql_*
系列函数的 非常糟糕 代码 sn-ps 的问题大量涌入。虽然通常最好将这些人引向 PDO,但有时这既不可能(例如继承的遗留软件)也不是现实的期望(用户已经在他们的项目中使用它)。
使用mysql_*
库的代码的常见问题包括:
让我们编写一个 PHP 代码示例,使用 mySQL_* family of functions 执行以下操作:
接受两个 POST 值,id
(数字)和 name
(字符串)
对表 tablename
执行 UPDATE 查询,更改 ID 为 id
的行中的 name
列
失败时,优雅退出,但仅在生产模式下显示详细错误。 trigger_error()
就足够了;或者使用您选择的方法
输出消息“$name
更新。”
并且没有显示出上面列出的任何弱点。
应该尽可能简单。理想情况下,它不包含任何函数或类。目标不是创建一个可复制/可粘贴的库,而是展示为确保数据库查询安全而需要做的最少工作。
优秀 cmets 的奖励积分。
我们的目标是让这个问题成为用户在遇到问题代码错误(即使它根本不是问题的焦点)或遇到失败的查询时可以链接到的资源,并且不知道怎么解决。
抢占 PDO 讨论:
是的,通常最好将写这些问题的个人指导给 PDO。当它是一个选项时,我们应该这样做。然而,这并不总是可能的——有时,提问者正在处理遗留代码,或者已经在这个库中取得了长足的进步,现在不太可能改变它。此外,如果使用得当,mysql_*
系列函数是完全安全的。所以请不要在这里回答“使用 PDO”。
【问题讨论】:
【参考方案1】:我的尝试。试图让它尽可能简单,同时仍然保持一些现实世界的便利。
处理 unicode 并使用松散比较以提高可读性。做个好人;-)
<?php
header('Content-type: text/html; charset=utf-8');
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 1);
// display_errors can be changed to 0 in production mode to
// suppress PHP's error messages
/*
Can be used for testing
$_POST['id'] = 1;
$_POST['name'] = 'Markus';
*/
$config = array(
'host' => '127.0.0.1',
'user' => 'my_user',
'pass' => 'my_pass',
'db' => 'my_database'
);
# Connect and disable mysql error output
$connection = @mysql_connect($config['host'],
$config['user'], $config['pass']);
if (!$connection)
trigger_error('Unable to connect to database: '
. mysql_error(), E_USER_ERROR);
if (!mysql_select_db($config['db']))
trigger_error('Unable to select db: ' . mysql_error(),
E_USER_ERROR);
if (!mysql_set_charset('utf8'))
trigger_error('Unable to set charset for db connection: '
. mysql_error(), E_USER_ERROR);
$result = mysql_query(
'UPDATE tablename SET name = "'
. mysql_real_escape_string($_POST['name'])
. '" WHERE id = "'
. mysql_real_escape_string($_POST['id']) . '"'
);
if ($result)
echo htmlentities($_POST['name'], ENT_COMPAT, 'utf-8')
. ' updated.';
else
trigger_error('Unable to update db: '
. mysql_error(), E_USER_ERROR);
【讨论】:
@Pekka Aw 废话,修复它。谢谢! -1 for#error_reporting(~E_ALL); ... to disable error output
- 不好的建议。不能再投反对票了,否则我会为 @
运营商 -1。
@OZ_ 我明白你关于@ 的观点,但是为什么在生产模式下关闭错误报告不好?
@OZ_:为什么这是个问题?在生产模式下,禁用错误输出并没有什么坏处,只要您将其记录在其他地方即可。使用 @ 是隐藏来自 mysql_connect
的讨厌输出的唯一方法。请重新考虑。
@Pekka, @Znarkus, error_reporting(0);
这是一个非常糟糕的建议,因为错误处理程序并不总是会处理错误,因为这个处理程序并不总是会被编码。 ini_set('display_errors',0)
- 此变体可用于生产代码,隐藏错误文本,并且仅在处理错误时使用。【参考方案2】:
我决定急于求成,放点东西。这是开始的事情。出错时抛出异常。
function executeQuery($query, $args)
$cleaned = array_map('mysql_real_escape_string', $args);
if($result = mysql_query(vsprintf($query, $cleaned)))
return $result;
else
throw new Exception('MySQL Query Error: ' . mysql_error());
function updateTablenameName($id, $name)
$query = "UPDATE tablename SET name = '%s' WHERE id = %d";
return executeQuery($query, array($name, $id));
try
updateTablenameName($_POST['id'], $_POST['name']);
catch(Exception $e)
echo $e->getMessage();
exit();
【讨论】:
虽然可行,但对于新手和复制粘贴人员来说太复杂了。 @Aaron - 你读过问题和 cmets 吗?正确阅读它们,只是复制粘贴不是这个问题的解决方案。 关于:失败时,优雅退出,但仅在生产模式下显示详细错误。也许在echo $e->getMessage();
周围添加一些条件?
@Aaron - 我不想为您的回答提及它,而是希望其他人也能效仿。我能理解你自己的答案。
Err,这不是将 $id 值分配给 name 并将 $name 值分配给 id 吗?除了您在调用函数时调用 updateTableName 的事实 updateTablenameName :p。【参考方案3】:
/**
* Rule #0: never trust users input!
*/
//sanitize integer value
$id = intval($_GET['id']);
//sanitize string value;
$name = mysql_real_escape_string($_POST['name']);
//1. using `dbname`. is better than using mysql_select_db()
//2. names of tables and columns should be quoted by "`" symbol
//3. each variable should be sanitized (even in LIMIT clause)
$q = mysql_query("UPDATE `dbname`.`tablename` SET `name`='".$name."' WHERE `id`='".$id."' LIMIT 0,1 ");
if ($q===false)
trigger_error('Error in query: '.mysql_error(), E_USER_WARNING);
else
//be careful! $name contains user's data, remember Rule #0
//always use htmlspecialchars() to sanitize user's data in output
print htmlspecialchars($name).' updated';
########################################################################
//Example, how easily is to use set_error_handler() and trigger_error()
//to control error reporting in production and dev-code
//Do NOT use error_reporting(0) or error_reporting(~E_ALL) - each error
//should be fixed, not muted
function err_handler($errno, $errstr, $errfile, $errline)
$hanle_errors_print = E_ALL & ~E_NOTICE;
//if we want to print this type of errors (other types we can just write in log-file)
if ($errno & $hanle_errors_print)
//$errstr can contain user's data, so... Rule #0
print PHP_EOL.'Error ['.$errno.'] in file '.$errfile.' in line '.$errline
.': '.htmlspecialchars($errstr).PHP_EOL;
//here you can write error into log-file
set_error_handler('err_handler', E_ALL & ~E_NOTICE & E_USER_NOTICE & ~E_STRICT & ~E_DEPRECATED);
以及对cmets的一些解释:
//1. using `dbname`. is better than using mysql_select_db()
使用 mysql_select_db 可以创建错误,但要查找和修复它们并不容易。 例如,在某些脚本中您将 db1 设置为数据库,但在某些函数中您需要将 db2 设置为数据库。 调用该函数后会切换数据库,脚本中的所有后续查询都将被破坏或破坏错误数据库中的某些数据(如果表名和列名重合)。
//2. names of tables and columns should be quoted by "`" symbol
某些列名也可以是 SQL 关键字,使用“`”符号会有所帮助。 此外,插入到查询中的所有字符串值都应该用 ' 符号引用。
//always use htmlspecialchars() to sanitize user's data in output
它将帮助您防止XSS-attacks。
【讨论】:
不错,我喜欢!我也喜欢错误处理程序,但对于新手来说,在这里掌握它可能太多了 - 现在可以选择将其删除,还是将其作为“可选”移动到底部? (我把mysqli
中的i
删掉了)
随意编辑我蹩脚的英语中的错误:)
@Pekka 我将尝试更多地关注 mysql_,现在将进行编辑。
我会将字符串连接替换为sprintf
。这使得内容更具可读性,并且可能的语法错误(缺少引号和内容)更加明显。
@Yoshi 我觉得是个人喜好,肯定不会更简单。【参考方案4】:
<?
mysql_connect();
mysql_select_db("new");
$table = "test";
if($_SERVER['REQUEST_METHOD']=='POST')
$name = mysql_real_escape_string($_POST['name']);
if ($id = intval($_POST['id']))
$query="UPDATE $table SET name='$name' WHERE id=$id";
else
$query="INSERT INTO $table SET name='$name'";
mysql_query($query) or trigger_error(mysql_error()." in ".$query);
header("Location: http://".$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF']);
exit;
if (!isset($_GET['id']))
$LIST=array();
$query="SELECT * FROM $table";
$res=mysql_query($query);
while($row=mysql_fetch_assoc($res)) $LIST[]=$row;
include 'list.php';
else
if ($id=intval($_GET['id']))
$query="SELECT * FROM $table WHERE id=$id";
$res=mysql_query($query);
$row=mysql_fetch_assoc($res);
foreach ($row as $k => $v) $row[$k]=htmlspecialchars($v);
else
$row['name']='';
$row['id']=0;
include 'form.php';
?>
form.php
<? include 'tpl_top.php' ?>
<form method="POST">
<input type="text" name="name" value="<?=$row['name']?>"><br>
<input type="hidden" name="id" value="<?=$row['id']?>">
<input type="submit"><br>
<a href="?">Return to the list</a>
</form>
<? include 'tpl_bottom.php' ?>
list.php
<? include 'tpl_top.php' ?>
<a href="?id=0">Add item</a>
<? foreach ($LIST as $row): ?>
<li><a href="?id=<?=$row['id']?>"><?=$row['name']?></a>
<? endforeach ?>
<? include 'tpl_bottom.php' ?>
【讨论】:
【参考方案5】:看起来我的其他答案没有达到问题的目的。 (这个也不满足一些要求,但可以看出,如果不实现处理占位符的功能,就无法实现安全的解决方案,占位符是安全查询的基石)
因此,这是另一个尝试发布简洁的解决方案以使 mysql 查询安全且方便。
我很久以前编写的一个函数,在我转向基于公司标准 OOP 的解决方案之前,它对我很有用。 有 2 个目标要追求:安全和易用性。
第一个通过实现占位符实现。 第二个是通过实现占位符和不同的结果类型来实现的。
功能肯定不理想。一些缺点是:
没有%
字符必须直接放在查询中,因为它使用 printf 语法。
不支持多个连接。
没有标识符的占位符(以及许多其他方便的占位符)。
同样,没有标识符占位符!。 "ORDER BY $field"
案件需人工处理!
当然,OOP 实现会更加灵活,具有简洁的不同方法,而不是丑陋的“模式”变量以及其他必要的方法。
但它很好,安全简洁,无需安装整个库。
function dbget()
/*
usage: dbget($mode, $query, $param1, $param2,...);
$mode - "dimension" of result:
0 - resource
1 - scalar
2 - row
3 - array of rows
*/
$args = func_get_args();
if (count($args) < 2)
trigger_error("dbget: too few arguments");
return false;
$mode = array_shift($args);
$query = array_shift($args);
$query = str_replace("%s","'%s'",$query);
foreach ($args as $key => $val)
$args[$key] = mysql_real_escape_string($val);
$query = vsprintf($query, $args);
if (!$query) return false;
$res = mysql_query($query);
if (!$res)
trigger_error("dbget: ".mysql_error()." in ".$query);
return false;
if ($mode === 0) return $res;
if ($mode === 1)
if ($row = mysql_fetch_row($res)) return $row[0];
else return NULL;
$a = array();
if ($mode === 2)
if ($row = mysql_fetch_assoc($res)) return $row;
if ($mode === 3)
while($row = mysql_fetch_assoc($res)) $a[]=$row;
return $a;
?>
使用示例
$name = dbget(1,"SELECT name FROM users WHERE id=%d",$_GET['id']);
$news = dbget(3,"SELECT * FROM news WHERE title LIKE %s LIMIT %d,%d",
"%$_GET[search]%",$start,$per_page);
从上面的例子可以看出,与***中发布的所有代码的主要区别在于,安全和数据检索例程都封装在函数代码中。因此,无需手动绑定、转义/引用或强制转换,也无需手动数据检索。
结合其他辅助功能
function dbSet($fields,$source=array())
$set = '';
if (!$source) $source = &$_POST;
foreach ($fields as $field)
if (isset($source[$field]))
$set.="`$field`='".mysql_real_escape_string($source[$field])."', ";
return substr($set, 0, -2);
这样使用
$fields = explode(" ","name surname lastname address zip phone regdate");
$_POST['regdate'] = $_POST['y']."-".$_POST['m']."-".$_POST['d'];
$sql = "UPDATE $table SET ".dbSet($fields).", stamp=NOW() WHERE id=%d";
$res = dbget(0,$sql, $_POST['id']);
if (!$res)
_503;//calling generic 503 error function
它可能涵盖几乎所有需求,包括来自 OP 的示例案例。
【讨论】:
以上是关于参考:啥是使用 MySQL 扩展的完美代码示例? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章