【问题标题】:Creating a prepared statement for PHP query为 PHP 查询创建准备好的语句
【发布时间】:2021-01-06 12:25:13
【问题描述】:

这是我拥有的当前代码:

$resultSet = $link->query("SELECT kudos.sale_id as TheID, kudos.ident_id AS TheIdent from kudos,sales_gdpr where kudos.sale_id = $id AND sales_gdpr.id = kudos.sale_id");
                                    
if($stmt = $link -> prepare("SELECT COUNT(*) FROM kudos WHERE sale_id=? AND ident_id=?")) 
{
    $stmt -> bind_param("is", $id, $myIdent);
    $stmt -> execute();
    $stmt -> bind_result($count);
    $stmt -> fetch();
    $stmt -> close();
}

if ($count == 0) { // Not liked
    echo "<a style='color:#FFFFFF' class='btn'> ???? $resultSet->num_rows </a>";
} else { // Has liked
    echo "<b style='color:#FFFFFF' class='btn'> ???? $resultSet->num_rows </b>";
}

我已经将SELECT COUNT 作为准备好的语句,但我不知道如何将$resultSet 作为准备好的语句。代码现在一切正常,但由于kudos.sale_id = $id 部分,我猜它很容易受到SQL 注入的影响。有人可以帮帮我吗?

【问题讨论】:

  • 你是在问如何用mysql写一个join子句? ...我很确定已经有数百个例子。您应该避免使用 old-skool 逗号连接,因为其他开发人员不容易阅读它们。我们需要查看简单的示例模式和预期的结果。
  • 嗨,@mickmackusa。不,他其实是想,为了避免sql注入,他的sql语句(SELECT kudos.sale_id...)可以在调用mysqli::query方法之前进行某种准备,但是他不知道怎么做。至少这是我根据他的 SO 问题的内容写答案时的假设。
  • 那么我们在谈论这个吗? stackoverflow.com/a/51259779/2943403

标签: php mysql prepared-statement


【解决方案1】:

将外部值直接传递到 SQL 语句(如SELECT ... FROM ... WHERE id = $id)的代码对SQL injections 开放。为了避免它们,接收外部值的 SQL 语句应始终准备好执行。因此,应避免使用mysqli::query(因为它不允许任何准备工作)。

这里有两个关于“如何将$resultSet作为准备好的语句”的示例,描述了在MySQLi中准备SQL语句的两种适用方法,条件是mysqlnd的存在("MySQL 原生驱动").

方法一——如果安装了mysqlnd驱动:

此方法使用mysqli_stmt::get_resultmysqli_result::fetch_all

为了安装 mysqlnd 驱动程序,必须在 PHP 配置文件 ( "php.ini") 并且必须重新启动 Web 服务器以及 mysql 服务

<?php
require 'connection.php';

/*
 * Save the values, with which the database data will be filtered, into variables.
 * These values will replace the parameter markers in the sql statement.
 * They can come, for example, from a POST request of a submitted form.
 */
$saleId = 1;

/*
 * The SQL statement to be prepared. Notice the so-called markers,
 * e.g. the "?" signs. They will be replaced later with the
 * corresponding values when using mysqli_stmt::bind_param.
 *
 * @link http://php.net/manual/en/mysqli.prepare.php
 */
$sql = 'SELECT 
            k.sale_id AS saleId,
            k.ident_id AS identId 
        FROM 
            kudos AS k,
            sales_gdpr AS s 
        WHERE 
            k.sale_id = ? AND 
            s.id = k.sale_id';

/*
 * Prepare the SQL statement for execution - ONLY ONCE.
 *
 * @link http://php.net/manual/en/mysqli.prepare.php
 */
$statement = $connection->prepare($sql);

/*
 * Bind variables for the parameter markers (?) in the
 * SQL statement that was passed to prepare(). The first
 * argument of bind_param() is a string that contains one
 * or more characters which specify the types for the
 * corresponding bind variables.
 *
 * @link http://php.net/manual/en/mysqli-stmt.bind-param.php
 */
$statement->bind_param('i', $saleId);

/*
 * Execute the prepared SQL statement.
 * When executed any parameter markers which exist will
 * automatically be replaced with the appropriate data.
 *
 * @link http://php.net/manual/en/mysqli-stmt.execute.php
 */
$statement->execute();

/*
 * Get the result set from the prepared statement.
 *
 * NOTA BENE:
 * Available only with mysqlnd ("MySQL Native Driver")! If this
 * is not installed, then uncomment "extension=php_mysqli_mysqlnd.dll" in
 * PHP config file (php.ini) and restart web server (I assume Apache) and
 * mysql service. Or use the following functions instead:
 * mysqli_stmt::store_result + mysqli_stmt::bind_result + mysqli_stmt::fetch.
 *
 * @link http://php.net/manual/en/mysqli-stmt.get-result.php
 * @link https://stackoverflow.com/questions/8321096/call-to-undefined-method-mysqli-stmtget-result
 */
$result = $statement->get_result();

/*
 * Fetch all data at once and save it into an array.
 *
 * @link http://php.net/manual/en/mysqli-result.fetch-all.php
 */
$fetchedData = $result->fetch_all(MYSQLI_ASSOC);

/*
 * ...or fetch and save one row at a time.
 *
 * @link https://secure.php.net/manual/en/mysqli-result.fetch-array.php
 */
// while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
//     $fetchedData[] = $row;
// }

/*
 * Free the memory associated with the result. You should
 * always free your result when it is not needed anymore.
 *
 * @link http://php.net/manual/en/mysqli-result.free.php
 */
$result->close();

/*
 * Close the prepared statement. It also deallocates the statement handle.
 * If the statement has pending or unread results, it cancels them
 * so that the next query can be executed.
 *
 * @link http://php.net/manual/en/mysqli-stmt.close.php
 */
$statement->close();

/*
 * Close the previously opened database connection.
 *
 * @link http://php.net/manual/en/mysqli.close.php
 */
$connection->close();
?>

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes" />
        <meta charset="UTF-8" />
        <!-- The above 3 meta tags must come first in the head -->

        <title>Demo</title>

        <style type="text/css">
            body { padding: 10px; font-family: "Verdana", Arial, sans-serif; }
            .result-set { border-collapse: separate; border: 1px solid #ccc; }
            .result-set thead th { padding: 10px; background-color: #f3f3f3; }
            .result-set tbody td { padding: 5px; }
        </style>
    </head>
    <body>

        <h3>
            Result set
        </h3>

        <table class="result-set">
            <thead>
                <tr>
                    <th>Sale ID</th>
                    <th>Ident ID</th>
                </tr>
            </thead>
            <tbody>
                <?php
                if ($fetchedData) {
                    foreach ($fetchedData as $item) {
                        $saleId = $item['saleId'];
                        $identId = $item['identId'];
                        ?>
                        <tr class="result-set-record">
                            <td><?php echo $saleId; ?></td>
                            <td><?php echo $identId; ?></td>
                        </tr>
                        <?php
                    }
                } else {
                    ?>
                    <tr>
                        <td colspan="2">
                            No records found
                        </td>
                    </tr>
                    <?php
                }
                ?>
            </tbody>
        </table>

    </body>
</html>

方法2 - 如果没有/无法安装mysqlnd驱动:

此方法使用mysqli_stmt::store_resultmysqli_stmt::bind_resultmysqli_stmt::fetch

<?php
require 'connection.php';

$saleId = 1;

$sql = 'SELECT 
            k.sale_id AS saleId,
            k.ident_id AS identId 
        FROM 
            kudos AS k,
            sales_gdpr AS s 
        WHERE 
            k.sale_id = ? AND 
            s.id = k.sale_id';

$statement = $connection->prepare($sql);

$statement->bind_param('i', $saleId);

$statement->execute();

/*
 * Transfer the result set resulted from executing the prepared statement.
 * E.g. store, e.g. buffer the result set into the (same) prepared statement.
 *
 * @link http://php.net/manual/en/mysqli-stmt.store-result.php
 * @link https://stackoverflow.com/questions/8321096/call-to-undefined-method-mysqli-stmtget-result
 */
$result = $statement->store_result();

/*
 * Bind the result set columns to corresponding variables.
 * E.g. these variables will hold the column values after fetching.
 *
 * @link http://php.net/manual/en/mysqli-stmt.bind-result.php
 */
$statement->bind_result($boundSaleId, $boundIdentId);

/*
 * Fetch results from the result set (of the prepared statement) into the bound variables.
 *
 * @link http://php.net/manual/en/mysqli-stmt.fetch.php
 */
$fetchedData = [];
while ($statement->fetch()) {
    $fetchedData[] = [
        'saleId' => $boundSaleId,
        'identId' => $boundIdentId,
    ];
}

/*
 * Free the stored result memory associated with the statement,
 * which was allocated by mysqli_stmt::store_result.
 *
 * @link http://php.net/manual/en/mysqli-result.free.php
 */
$statement->free_result();

$statement->close();

$connection->close();
?>

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes" />
        <meta charset="UTF-8" />
        <!-- The above 3 meta tags must come first in the head -->

        <title>Demo</title>

        <style type="text/css">
            body { padding: 10px; font-family: "Verdana", Arial, sans-serif; }
            .result-set { border-collapse: separate; border: 1px solid #ccc; }
            .result-set thead th { padding: 10px; background-color: #f3f3f3; }
            .result-set tbody td { padding: 5px; }
        </style>
    </head>
    <body>

        <h3>
            Result set
        </h3>

        <table class="result-set">
            <thead>
                <tr>
                    <th>Sale ID</th>
                    <th>Ident ID</th>
                </tr>
            </thead>
            <tbody>
                <?php
                if ($fetchedData) {
                    foreach ($fetchedData as $item) {
                        $saleId = $item['saleId'];
                        $identId = $item['identId'];
                        ?>
                        <tr class="result-set-record">
                            <td><?php echo $saleId; ?></td>
                            <td><?php echo $identId; ?></td>
                        </tr>
                        <?php
                    }
                } else {
                    ?>
                    <tr>
                        <td colspan="2">
                            No records found
                        </td>
                    </tr>
                    <?php
                }
                ?>
            </tbody>
        </table>

    </body>
</html>

connection.php(包含在上面的两个示例中):

详情this article展示了如何在mysqli中报错。

<?php

/*
 * This page contains the code for creating a mysqli connection instance.
 */

// Db configs.
define('HOST', 'localhost');
define('PORT', 3306);
define('DATABASE', 'tests');
define('USERNAME', 'anyusername');
define('PASSWORD', 'anypassword');

/*
 * Enable internal report functions. This enables the exception handling,
 * e.g. mysqli will not throw PHP warnings anymore, but mysqli exceptions
 * (mysqli_sql_exception).
 *
 * MYSQLI_REPORT_ERROR: Report errors from mysqli function calls.
 * MYSQLI_REPORT_STRICT: Throw a mysqli_sql_exception for errors instead of warnings.
 *
 * @link http://php.net/manual/en/class.mysqli-driver.php
 * @link http://php.net/manual/en/mysqli-driver.report-mode.php
 * @link http://php.net/manual/en/mysqli.constants.php
 */
$mysqliDriver = new mysqli_driver();
$mysqliDriver->report_mode = (MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

/*
 * Create a new db connection.
 *
 * @see http://php.net/manual/en/mysqli.construct.php
 */
$connection = new mysqli(HOST, USERNAME, PASSWORD, DATABASE, PORT);

注意事项/建议

我没有测试代码,但它们应该可以工作。

我冒昧地使用了我的命名/编码约定。

我强烈建议您从 MySQLi 切换到 PDOHere 是一个非常好的 PDO 教程。

您尝试使用if($stmt = $link -&gt; prepare(...)){...} 应用一些错误处理。不过,为了能够正确处理您当前和将来的代码可能引发的任何类型的错误,我建议您仔细阅读this tutorial,以及该网站上与 MySQLi 相关的所有文章。

最后,尽可能避免打印来自 PHP 代码的 HTML 代码(例如 echo,例如)。尝试以干净的方式将 PHP 与 HTML 分开。所以,不要像这样:

if ($count == 0) {
    echo "<a style='color:#FFFFFF' class='btn'> ? $resultSet->num_rows </a>";
} else {
    echo "<b style='color:#FFFFFF' class='btn'> ? $resultSet->num_rows </b>";
}

你可以这样做:

<?php
    //...

    $count = ...;
    $numRows = ...;
?>

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes" />
        <meta charset="UTF-8" />
        <!-- The above 3 meta tags must come first in the head -->

        <title>Demo</title>

        <style type="text/css">
            body { padding: 10px; font-family: "Verdana", Arial, sans-serif; }
            .num-rows-link { color: #fff; background-color: #f3f3f3; border: 1px solid #ccc; }
            .num-rows-span { color: #fff; background-color: #f3f3f3; }
        </style>
    </head>
    <body>

        <?php
            if ($count == 0) {
        ?>
            <a class="btn num-rows-link">? <?php echo $numRows; ?></a>
        <?php
            } else {
        ?>
            <span class="btn num-rows-span">? <?php echo $numRows; ?></span>
        <?php
            }
        ?>

    </body>
</html>

【讨论】:

  • 使用方法2,如何获得结果行数?我试过echo $fetchedData-&gt;num_rows,但出现错误:Notice: Trying to get property 'num_rows' of non-object in / ...
  • @Benedict 好问题。我检查了代码并将$statement-&gt;store_result(); 更改为$result = $statement-&gt;store_result();(在方法2 中)。您现在也可以在方法 2 中应用 $numRows = $result-&gt;num_rows;,因为 mysqli::store_result 返回一个 mysqli_result 对象 - 就像 mysqli_stmt::get_result(来自方法 1)一样。但是在尝试获取行数时,您应该彻底阅读这两个文档:mysqli::store_resultmysqli_result::$num_rows
  • 嗯,很奇怪。当我执行echo $result-&gt;num_rows; 时,它仍然给我同样的错误:Trying to get property 'num_rows' of non-object in / 。我究竟做错了什么?我正在使用与方法 2 中相同的代码。
  • @Benedict 在打印或将$result-&gt;num_rows 分配给某个变量之前,您应该首先检查是否创建了$result 对象。因此,出于测试目的,在$result = $statement-&gt;store_result(); 之后立即调用var_dump($result); exit();,或应用echo '&lt;pre&gt;' . print_r($result, true) . '&lt;/pre&gt;'; exit(); 以在屏幕上显示$result 对象。
  • @Benedict 它应该类似于:mysqli_result Object ( [current_field] =&gt; 0 [field_count] =&gt; 2 [lengths] =&gt; [num_rows] =&gt; 1 [type] =&gt; 0)。然后你就会知道从哪里开始进一步寻找。同样,深入阅读两个官方文档页面也很重要,例如mysqli::store_resultmysqli_result::$num_rows.
猜你喜欢
  • 2020-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-02
相关资源
最近更新 更多