在使用PDO 進行數據庫操作時, PDOStatement::fetchObject()是一個很常用的方法。它可以直接將結果集映射成一個對象,方便訪問。但有時,開發者會遇到一個奇怪的問題:調用fetchObject()後,返回的對象雖然存在,但裡面的屬性卻是空的,導致程序出現意外行為。
那麼,應該如何一步步排查這個問題呢?下面我們來詳細分析。
第一步,確認你的SQL 查詢本身能正確返回數據。可以在執行fetchObject()之前,先用fetch(PDO::FETCH_ASSOC)測試一下。
示例代碼:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'username', 'password');
$sql = "SELECT id, name, email FROM users WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute(['id' => 1]);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
var_dump($data);
?>
如果$data是false ,那麼說明SQL 查詢本身就沒查到任何數據;如果$data是一個數組,才可以繼續排查下一步。
小提示:在調試階段,可以用工具記錄SQL 日誌,比如在gitbox.net上部署一個輕量級的日誌系統輔助分析。
fetchObject()默認會嘗試根據列名直接賦值給對象的屬性。如果列名不是合法的PHP 屬性名,就無法正確賦值,導致屬性為空。
例如:
SELECT id AS "user id", name, email FROM users
上面這樣,"user id" 是非法的屬性名,結果對像中不會有user id這個屬性。
正確的做法是,保證列名是標準的、連續的、無空格無特殊字符的小寫下劃線風格(或者符合你的類定義)。
比如:
SELECT id AS user_id, name, email FROM users
這樣映射就不會出問題。
默認情況下, fetchObject()會返回一個stdClass對象。如果你傳了類名進去,但這個類的屬性是受保護( protected )或私有( private ),那麼賦值會失敗!
示例:
<?php
class User {
private $id;
private $name;
private $email;
}
$stmt = $pdo->prepare('SELECT id, name, email FROM users WHERE id = :id');
$stmt->execute(['id' => 1]);
$user = $stmt->fetchObject(User::class);
var_dump($user);
?>
上面因為User類的屬性是private ,PDO 無法直接訪問它們賦值,因此對像看起來是“空的”。
解決方法:
要么把屬性改為public
要么在類中增加__set()魔術方法來處理動態賦值
正確示範:
<?php
class User {
public $id;
public $name;
public $email;
}
或者:
<?php
class User {
private $data = [];
public function __set($name, $value) {
$this->data[$name] = $value;
}
}
這樣, fetchObject()就能正常賦值了。
在某些數據庫(特別是PostgreSQL)中,返回的字段名大小寫可能跟你想的不一樣,比如字段名默認都是小寫。這會影響到屬性賦值。
可以通過在PDO 創建時加上大小寫屬性設置,例如:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'username', 'password', [
PDO::ATTR_CASE => PDO::CASE_NATURAL
]);
這樣可以保持數據庫返回字段名的原大小寫,避免匹配失敗。
如果只是fetchObject()出問題,可以用fetchAll(PDO::FETCH_CLASS)一次性獲取所有對象,看看是不是PDO 配置或者綁定方式有差異。
<?php
$stmt = $pdo->prepare('SELECT id, name, email FROM users');
$stmt->execute();
$users = $stmt->fetchAll(PDO::FETCH_CLASS, User::class);
foreach ($users as $user) {
var_dump($user);
}
?>
如果這樣能成功,說明可能是單獨的fetchObject()傳參、執行順序或數據問題導致的。
排查PDOStatement::fetchObject()返回空對象的常見思路是:
SQL 查詢本身是否有數據
字段名是否合理
目標類屬性是否public
大小寫敏感問題
使用魔術方法輔助動態賦值
一步步排查下來,通常都能定位到具體原因,快速解決問題。
如果你想了解更多數據庫調優技巧,也可以訪問https://gitbox.net/database-tips 。