iT邦幫忙

0

laravel 中使用 pdo 去 bindParam 到一個 bool 欄位

背景

嘗試在 laravel 中引用一個過去寫的 class
該 class 是由 constructor 的參數綁定 pdo 物件
用來封裝一些對資料庫的操作

在除錯過程中,發現是 laravel 由 \DB::connection()->getPdo() 取得的 PDO 物件,只要遇到 $stmt->bindParam(略, 略, \PDO::PARAM_BOOL) 就會出錯,第三個參數改成 \PDO::PARAM_INT 就可以運作。(資料庫欄位以及PHP的變數確認都是 bool)

研究與測試

於是我做了下面的測試,但不知道是什麼原因導致這樣的結果:

環境

  • laravel 5.8
  • php 7.1.17
  • mariadb 10.2.14

測試內容

資料庫直接用命令列建立

CREATE TABLE test_table(bool_col BOOLEAN NOT NULL)

controller 裡面的內容這樣寫

public function test()
{
    $boolVal=rand(0,1)?false:true;
    
    $pdo=\DB::connection()->getPdo();
    $stmt=$pdo->prepare('INSERT INTO test_table(bool_col) VALUES(:bool_val)');
    $stmt->bindParam(':bool_val', $boolVal, \PDO::PARAM_BOOL);
    if($stmt->execute()!==false) {
        echo 'success.';
    } else {
        var_dump($stmt->errorInfo());
    }
}

測試結果

  1. 上面這個會發生錯誤,想找原因卻顯示 array(3) { [0]=> string(5) "00000" [1]=> NULL [2]=> NULL },但如果把 \PDO::PARAM_BOOL 改成 \PDO::PARAM_INT 則可正確執行。
  2. 如果是自己直接在 controller 裡面 new PDO,則允許使用 \PDO::PARAM_BOOL 不會發生錯誤。

問題

  1. 不知道 laravel 本身對 pdo 做了什麼設定產生這樣的結果?
  2. 除錯過程中 $stmt->errorInfo() 無法得知錯誤發生在哪邊,是不是有其他方式可以得到錯誤訊息?
firecold iT邦新手 3 級 ‧ 2019-10-31 14:41:11 檢舉
用try看看可不可以拿到error log
淺水員 iT邦研究生 4 級 ‧ 2019-10-31 15:21:51 檢舉
已測試,無 exception。

1 個回答

0
firecold
iT邦新手 3 級 ‧ 2019-10-31 14:45:59
最佳解答

雖然解決問題很重要

但你都用laravel了
除了一些特殊需求的sql

你要不要改用Eloquent來做做看

看更多先前的回應...收起先前的回應...
淺水員 iT邦研究生 4 級 ‧ 2019-10-31 14:57:35 檢舉

因為是引用舊的函式庫才發現這問題
如果是新寫的就會考慮直接用 Laravel 裡面的物件處理

firecold iT邦新手 3 級 ‧ 2019-10-31 17:10:34 檢舉

https://bugs.php.net/bug.php?id=38546
php 歷史錯誤
所以....就這樣吧xd

淺水員 iT邦研究生 4 級 ‧ 2019-10-31 19:26:33 檢舉

因為直接自己用 new 建立 PDO 物件的話
並不會發生這樣的錯誤(可以使用 \PDO::PARAM_BOOL)
所以想知道差別在哪

淺水員 iT邦研究生 4 級 ‧ 2019-10-31 20:07:35 檢舉

我去找 laravel 內部的程式碼
在 Illuminate/Database/Connectors/Connector.php 裡面找到他 new PDO 時有給一個 $options 參數

return new PDO($dsn, $username, $password, $options);

而那個 $options 的內容為

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 => false,
];

PDO::ATTR_EMULATE_PREPARES => false 改成 true 後就可以正常使用 \PDO::PARAM_BOOL
這個設定的解釋可參考:https://www.php.net/manual/en/pdo.setattribute.php
主要是跟當 DB 不支援 prepare 某些方法時是否讓 driver 去模擬

PS. 在實際應用時不會直接改 laravel 原始碼,而是去設定 database/config.php 的 options,例如

'mysql' => [
    //略
    'options' => [
        PDO::ATTR_EMULATE_PREPARES => true,
    ]
];
firecold iT邦新手 3 級 ‧ 2019-11-01 10:24:10 檢舉

感謝補充
總感覺跟強型態有些關係

firecold iT邦新手 3 級 ‧ 2019-11-01 10:30:06 檢舉

true之後就要小心sql injection了
https://www.cnblogs.com/alazalazalaz/p/6056393.html

淺水員 iT邦研究生 4 級 ‧ 2019-11-01 13:11:58 檢舉

謝謝提供資料,這塊我之前的確不知道。
剛剛另外找了一篇:
Are PDO prepared statements sufficient to prevent SQL injection?

根據這篇的敘述,主要是因為有些字串編碼會有問題
(utf8 是安全的,如果有正確設定連線的話)

laravel 連線時會根據 config/database.php 檔案的設定去指定 charset,如果是 utf8_mb4 的話應該可以使用 PDO::ATTR_EMULATE_PREPARES

當然可以的話直接不使用 PDO::ATTR_EMULATE_PREPARES 應該是最好的,以上是針對盡量不動到舊有程式碼的狀況下。

如果有錯誤之處再麻煩指正,感謝。

我要發表回答

立即登入回答