简体   繁体   English

为 PHP 查询创建准备好的语句

[英]Creating a prepared statement for PHP query

The is the current code I have:这是我目前的代码:

$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>";
}

The SELECT COUNT I have made as a prepared statement, but I can't figure out how to make the $resultSet as a prepared statement.我已经将SELECT COUNT作为准备好的语句,但我不知道如何将$resultSet作为准备好的语句。 The code works all fine as it is now, but because of the kudos.sale_id = $id part, I guess it it vulnerably to SQL Injection.代码现在一切正常,但由于kudos.sale_id = $id部分,我猜它很容易受到 SQL 注入的攻击。 Can someone help me out please?有人可以帮我吗?

A code in which external values are directly passed into an SQL statement (like SELECT ... FROM ... WHERE id = $id ) is open to SQL injections .将外部值直接传递到 SQL 语句中的代码(如SELECT ... FROM ... WHERE id = $id )对SQL 注入是开放的。 In order to avoid them, an SQL statement receiving external values should always be prepared for execution.为了避免它们,应始终准备好接收外部值的 SQL 语句以供执行。 Terefore, the use of mysqli::query should be avoided (since it doesn't allow any preparation).因此,应避免使用mysqli::query (因为它不允许任何准备)。

Here are two examples on "how to make the $resultSet as a prepared statement" , depicting the two appliable methods for preparing an SQL statement in MySQLi, conditioned by the presence of mysqlnd ("MySQL Native Driver") .这里有两个关于“如何使$resultSet作为准备好的语句”的示例,描述了在 MySQLi 中准备 SQL 语句的两种适用方法,条件是mysqlnd ("MySQL Native Driver") 的存在

Method 1 - If mysqlnd driver is installed:方法 1 - 如果安装了mysqlnd驱动程序:

This method uses mysqli_stmt::get_result and mysqli_result::fetch_all .此方法使用mysqli_stmt::get_resultmysqli_result::fetch_all

In order to install the mysqlnd driver, the entry "extension=php_mysqli_mysqlnd.dll" (or similar) must be uncommented in the PHP config file ( "php.ini" ) and the web server must be restarted, along with the mysql service .为了安装mysqlnd驱动程序,必须在 PHP 配置文件( “php.ini” )中取消注释条目“extension=php_mysqli_mysqlnd.dll” (或类似的),并且必须重新启动 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>

Method 2 - If mysqlnd driver is not/can not be installed:方法 2 - 如果没有/无法安装mysqlnd驱动程序:

This method uses mysqli_stmt::store_result , mysqli_stmt::bind_result and mysqli_stmt::fetch .此方法使用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 (included in both examples above): connection.php(包含在上面的两个示例中):

For details, this article shows how to report errors in mysqli.有关详细信息,本文将展示如何在 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);

Notes / Suggestions :注意事项/建议

I didn't test the codes, but they should work.我没有测试代码,但它们应该可以工作。

I took the liberty to use my naming/coding conventions.我冒昧地使用我的命名/编码约定。

I strongly suggest you to switch from MySQLi to PDO .我强烈建议你从 MySQLi 切换到PDO Here is a very good PDO tutorial.是一个非常好的 PDO 教程。

You tried to apply some error handling by using if($stmt = $link -> prepare(...)){...} .您尝试使用if($stmt = $link -> prepare(...)){...}应用一些错误处理 Though, in order to be able to properly handle any kind of errors that may be raised by your present and future codes, I suggest you to read this tutorial thoroughly, along with all articles related to MySQLi found on that website.尽管如此,为了能够正确处理您当前和未来的代码可能引发的任何类型的错误,我建议您仔细阅读本教程以及该网站上与 MySQLi 相关的所有文章。

And, at last, avoid printing (like with echo , for ex.) HTML code from PHP code, as much as possible.最后,尽可能避免从 PHP 代码打印 HTML 代码(例如使用echo )。 Try to separate PHP from HTML in a clean way.尝试以一种干净的方式将 PHP 与 HTML 分开。 So, instead of something like this:所以,而不是这样的:

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

you could do this:你可以这样做:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM