1. 程式人生 > >記(Laravel)PDO 使用prepared statement 預處理LIMIT等欄位遇到的坑。

記(Laravel)PDO 使用prepared statement 預處理LIMIT等欄位遇到的坑。

以下說明基於PHP7.1版本,Laravel 5.2版本。

直接貼程式碼:

        $sql = " SELECT id
        from my_table
        LIMIT ?,?
        ";
        $tests = DB::select($sql, [$offset, $limit]);

Laravel 5.2版本,預設 ATTR_EMULATE_PREPARES設定為true.
在檔案 Illuminate/Database/Connectors/Connector.php 24行.

class Connector
{
    use DetectsLostConnections;

    /**
     * The default PDO connection options.
     *
     * @var
array */
protected $options = [ PDO::ATTR_CASE => PDO::CASE_NATURAL, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, PDO::ATTR_STRINGIFY_FETCHES => false, PDO::ATTR_EMULATE_PREPARES => true, ];

由於一些驅動不支援原生的預處理語句,PDO可以完全模擬預處理。
同時,PDO的模擬預處理是預設開啟的,並且,laravel對PDO的配置也是預設開啟的.

即便MYSQL驅動本身支援預處理,在預設開啟的狀態下,PDO是不會用到MYSQL本身提供的預處理功能。

坑在這裡:

PDO會把SQL語句進行模擬預處理之後會發送給MYSQL一個原始的SQL語句。

這種方式很詭異的是如果預處理的SQL語句中需要處理的欄位不是表中的欄位時,PDO會對繫結的引數

無腦新增單引號

無腦新增單引號

無腦新增單引號

因而導致了異常或查詢不到結果。
即:

limit '0','1'

解決這種問題的方法是設定PDO不去模擬預處理,而是交給MYSQL本身去做。方法是設定PDO的引數 ATTR_EMULATE_PREPARES 為 false
Laravel 中是在 config/database.php

        'mysql' => [
            'driver'    => 'mysql',
            'host'      => env('DB_HOST', 'localhost'),
            'database'  => env('DB_DATABASE', 'forge'),
            'username'  => env('DB_USERNAME', 'forge'),
            'password'  => env('DB_PASSWORD', ''),
            'charset'   => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix'    => '',
            'strict'    => false,
            'engine'    => null,
            'options'   =>[
                PDO::ATTR_EMULATE_PREPARES => false,
            ],