首頁  >  文章  >  後端開發  >  三分鐘帶你了解PHP中的初始化PDO及原始SQL語句操作

三分鐘帶你了解PHP中的初始化PDO及原始SQL語句操作

醉折花枝作酒筹
醉折花枝作酒筹轉載
2021-06-21 15:26:062018瀏覽

這篇文章跟大家介紹一下PHP中的初始化PDO及原始SQL語句操作。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。

三分鐘帶你了解PHP中的初始化PDO及原始SQL語句操作

PDO 已經是 PHP 中操作資料庫事實上的標準。包括現在的框架和各種類別庫,都是以 PDO 作為資料庫的連結方式。基本上只有我們自己在寫簡單的測試程式碼或小的功能時會使用 mysqli 來操作資料庫。注意,普通的 mysql 擴充已經過時了哦!

PDO 實例

首先來看看一個 PDO 實例是如何初始化的。

$dns = 'mysql:host=localhost;dbname=blog_test;port=3306;charset=utf8';
$pdo = new PDO($dns, 'root', '');

普通情況下,我們直接實例化的時候傳遞建構參數就可以得到一個 PDO 物件。這樣,我們就和資料庫建立了連線。如果連線失敗,也就是參數寫得有問題的時候,在實例化時直接就會報異常。

PDO 物件的參數包括 DNS 資訊、使用者名稱、密碼,另外還有一個參數就是可以設定 PDO 連線的一些屬性,我們將在後面看到它的使用。

dns 參數

PDO 建構參數的第一個參數是一個 DNS 字串。在這個字串中使用分號 ; 分隔不同的參數內容。它裡面可以定義的內容包括:

  • DSN prefix,也就是我們要連接的資料庫類型,MySQL 資料庫一般都是直接使用 mysql: 這樣來定義即可。

  • host,連接的位址,在這裡我們連接的是本地資料庫localhost

  • port,連接埠號,MySQL 預設為3306 ,可以不寫入

  • dbname,要連接的資料庫名稱

  • unix_socket,可以指定MySQL 的Unix Socket 檔案

  • #charset,連接的字元集

我們可以透過一個函數來查看目前PHP 環境中所支援的資料庫擴充有哪些:

print_r(PDO::getAvailableDrivers());exit;
// Array
// (
//     [0] => dblib
//     [1] => mysql
//     [2] => odbc
//     [3] => pgsql
//     [4] => sqlite
// )

PDO 物件屬性

PDO 建構參數的最後一個參數可以設定連接的一些屬性,例如:

$pdo = new PDO($dns, 'root', '', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
showPdoAttribute($pdo);
// ……
// PDO::ATTR_ERRMODE: 2
// ……

showPdoAttribute() 方法是我們自己封裝的一個展示所有連接屬性的函數。

// 显示pdo连接属性
function showPdoAttribute($pdo){
    $attributes = array(
        "DRIVER_NAME", "AUTOCOMMIT", "ERRMODE", "CASE", "CLIENT_VERSION", "CONNECTION_STATUS",
        "ORACLE_NULLS", "PERSISTENT", "SERVER_INFO", "SERVER_VERSION"
    );
    
    foreach ($attributes as $val) {
        echo "PDO::ATTR_$val: ";
        echo $pdo->getAttribute(constant("PDO::ATTR_$val")) . "\n";
    }
}

在這個函數中,我們使用 PDO 實例的 getAttribute() 方法來取得對應的屬性值。在沒有設定 PDO::ATTR_ERRMODE 時,它的預設值為 0 ,也就是 PDO::ERRMODE_SILENT 常數所對應的值。在上述程式碼中,我們將它設定為了 PDO::ERRMODE_EXCEPTION ,查看屬性輸出的結果就變成了 2 。

除了在建構子的參數中設定屬性外,我們也可以使用 PDO 實例的 setAttribute() 方法來設定 PDO 的屬性值。

pdo2 = new PDO($dns, 'root', '', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);

echo $pdo2->getAttribute(PDO::ATTR_DEFAULT_FETCH_MODE), PHP_EOL;
// 4

// 设置属性
$pdo2->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
echo $pdo2->getAttribute(PDO::ATTR_DEFAULT_FETCH_MODE), PHP_EOL;
// 2

在這段程式碼中,我們設定 PDO::ATTR_DEFAULT_FETCH_MODE 為 PDO::FETCH_ASSOC 。這樣,在使用這個 $pdo2 的連線進行查詢時,輸出的結果都會是以數組鍵值對形式傳回的內容。我們馬上就進入查詢方面相關函數的學習。

查詢語句

大多數情況下,使用PDO 我們都會用它的預處理能力來寫SQL 語句,一來是效能更好,二來是更加安全。不過我們今天先不講預處理方面的問題,還是以最原始的直接操作 SQL 語句的方式學習相關的一些函數。

普通查詢及遍歷

// 普通查询 - 遍历1
$stmt = $pdo->query('select * from zyblog_test_user limit 5');
foreach ($stmt as $row) {
    var_dump($row);
}

// array(8) {
//     ["id"]=>
//     string(3) "204"
//     [0]=>
//     string(3) "204"
//     ["username"]=>
//     string(5) "three"
//     [1]=>
//     string(5) "three"
//     ["password"]=>
//     string(6) "123123"
//     [2]=>
//     string(6) "123123"
//     ["salt"]=>
//     string(3) "ccc"
//     [3]=>
//     string(3) "ccc"
//   }
//   ……

// 普通查询 - 遍历2
$stmt = $pdo->query('select * from zyblog_test_user limit 5');

while ($row = $stmt->fetch()) {
    var_dump($row);
}

// array(8) {
//     ["id"]=>
//     string(3) "204"
//     [0]=>
//     string(3) "204"
//     ["username"]=>
//     string(5) "three"
//     [1]=>
//     string(5) "three"
//     ["password"]=>
//     string(6) "123123"
//     [2]=>
//     string(6) "123123"
//     ["salt"]=>
//     string(3) "ccc"
//     [3]=>
//     string(3) "ccc"
//   }
//   ……

PDO 實例的 query() 方法就是執行一條查詢語句,並傳回一個 PDOStatement 物件。透過遍歷這個對象,就可以獲得查詢出來的資料結果集。

在程式碼中,我們使用了兩種方式來遍歷,其實它們的效果都是一樣的。在這裡,我們要關注的是返回的資料格式。可以看出,資料是以數組格式傳回的,並且是以兩種形式,一個是資料庫定義的鍵名,一個是以下標形式。

查詢結果集(陣列、物件)

其實大部分情況下,我們只需要資料庫鍵名的那種鍵值對形式的資料就可以了。這個有兩種方式,一是直接使用上文中我們定義好預設 PDO::ATTR_DEFAULT_FETCH_MODE 屬性的 $pdo2 連接,另一個就是在查詢的時候為 query() 方法指定屬性。

$stmt = $pdo2->query('select * from zyblog_test_user limit 5');
foreach ($stmt as $row) {
    var_dump($row);
}
// array(4) {
//     ["id"]=>
//     string(1) "5"
//     ["username"]=>
//     string(3) "two"
//     ["password"]=>
//     string(6) "123123"
//     ["salt"]=>
//     string(3) "bbb"
//   }
//   ……

$stmt = $pdo->query('select * from zyblog_test_user limit 5', PDO::FETCH_ASSOC);
foreach ($stmt as $row) {
    var_dump($row);
}
// array(4) {
//     ["id"]=>
//     string(1) "5"
//     ["username"]=>
//     string(3) "two"
//     ["password"]=>
//     string(6) "123123"
//     ["salt"]=>
//     string(3) "bbb"
//   }
//   ……

當然,我們也可以直接讓資料回傳成物件的格式,同樣的也是使用預先定義的常數來指定 query() 或 PDO 實例連接的屬性就可以了。

$stmt = $pdo->query('select * from zyblog_test_user limit 5', PDO::FETCH_OBJ);
foreach ($stmt as $row) {
    var_dump($row);
}
// object(stdClass)#4 (4) {
//     ["id"]=>
//     string(1) "5"
//     ["username"]=>
//     string(3) "two"
//     ["password"]=>
//     string(6) "123123"
//     ["salt"]=>
//     string(3) "bbb"
//   }
//   ……

查詢結果集(類別)

上面傳回物件形式的結果集中的物件是 stdClass 類型,也就是 PHP 的預設類別類型。那我們是否可以自己定義一個類,然後在查詢完成後直接產生它的結果集呢?就是像是 ORM 框架一樣,完成資料到物件的對應。既然這麼說了,那當然是可以的啦,直接看程式碼。

class user
{
    public $id;
    public $username;
    public $password;
    public $salt;

    public function __construct()
    {
        echo 'func_num_args: ' . func_num_args(), PHP_EOL;
        echo 'func_get_args: ';
        var_dump(func_get_args());
    }
}

class user2
{

}
// 返回指定对象
$u = new user;
$stmt = $pdo->query('select * from zyblog_test_user limit 5', PDO::FETCH_INTO, $u);
foreach ($stmt as $row) {
    var_dump($row);
}
// object(user)#3 (4) {
//     ["id"]=>
//     string(1) "5"
//     ["username"]=>
//     string(3) "two"
//     ["password"]=>
//     string(6) "123123"
//     ["salt"]=>
//     string(3) "bbb"
//   }
//   ……

// 空类测试
$u = new user2;
$stmt = $pdo->query('select * from zyblog_test_user limit 5', PDO::FETCH_INTO, $u);
foreach ($stmt as $row) {
    var_dump($row);
}

// object(user2)#2 (4) {
//     ["id"]=>
//     string(1) "5"
//     ["username"]=>
//     string(3) "two"
//     ["password"]=>
//     string(6) "123123"
//     ["salt"]=>
//     string(3) "bbb"
//   }
//   ……

在这段代码中,我们定义了两个类,user 类有完整的和数据库字段对应的属性,还定义了一个构造方法(后面会用到)。而 user2 类则是一个空的类。通过测试结果来看,类的属性对于 PDO 来说并不重要。它会默认创建数据库查询到的字段属性,并将它赋值给对象。那么假如我们定义了一个 const 常量属性并给予相同的字段名称呢?大家可以自己尝试一下。

对于 user 和 user2 来说,我们将它实例化了并传递给了 query() ,并且指定了结果集格式为 PDO::FETCH_INTO ,这样就实现了获取对象结果集的能力。但是 PDO 远比你想象的强大,我们还可以直接用类模板来获取查询结果集。

// 根据类返回指定对象
$stmt = $pdo->query('select * from zyblog_test_user limit 5', PDO::FETCH_CLASS, 'user', ['x1', 'x2']);
foreach ($stmt as $row) {
    var_dump($row);
}
// func_num_args: 2
// func_get_args: array(2) {
//   [0]=>
//   string(2) "x1"
//   [1]=>
//   string(2) "x2"
// }
// object(user)#4 (4) {
//   ["id"]=>
//   string(1) "5"
//   ["username"]=>
//   string(3) "two"
//   ["password"]=>
//   string(6) "123123"
//   ["salt"]=>
//   string(3) "bbb"
// }
// ……

query() 方法直接使用查询结果集模式为 PDO::FETCH_CLASS ,并传递一个类模板的名称,PDO 就会在当前代码中查找有没有对应的类模板,获得的每个结果都会实例化一次。在这里,我们又多了一个参数,最后一个参数是一个数组,并且给了两个元素。估计有不少小伙伴已经看出来了,这个参数是传递给类的构造方法的。记住,使用这个模式,每个元素都会实例化一次,结果集中的每个元素都是新创建的类(object(user2)#3,#号后面的数字是不同的对象句柄id),而 PDO::FETCH_INTO 则是以引用的形式为每个元素赋值(object(user2)#3,#号后面的数字是相同的对象句柄id)。也就是说,我们使用 PDO::FETCH_INTO 模式的时候,修改一个元素的值,其它的元素也会跟着改变,如果使用一个数组去记录遍历的元素值,最后数组的结果也会是相同的最后一个元素的内容。

$stmt = $pdo->query('select * from zyblog_test_user limit 5', PDO::FETCH_INTO, $u);
$resArr = [];
foreach ($stmt as $row) {
    var_dump($row);
    $resArr[] = $row;
}
$resArr[0]->id = 55555;
print_r($resArr);
// Array
// (
//     [0] => user2 Object
//         (
//             [id] => 55555
//             [username] => two
//             [password] => 123123
//             [salt] => bbb
//         )

//     [1] => user2 Object
//         (
//             [id] => 55555
//             [username] => two
//             [password] => 123123
//             [salt] => bbb
//         )

//     [2] => user2 Object
//         (
//             [id] => 55555
//             [username] => two
//             [password] => 123123
//             [salt] => bbb
//         )

//     [3] => user2 Object
//         (
//             [id] => 55555
//             [username] => two
//             [password] => 123123
//             [salt] => bbb
//         )

//     [4] => user2 Object
//         (
//             [id] => 55555
//             [username] => two
//             [password] => 123123
//             [salt] => bbb
//         )

// )

如何解决这个问题呢?最简单的方式就是在数组赋值的时候加个 clone 关键字呗!

查询结果集(指定字段)

最后轻松一点,我们看下 query() 方法还可以指定查询的某一个字段。

// 只返回第几个字段
$stmt = $pdo->query('select * from zyblog_test_user limit 5', PDO::FETCH_COLUMN, 2);
foreach ($stmt as $row) {
    var_dump($row);
}
// string(32) "bbff8283d0f90625015256b742b0e694"
// string(6) "123123"
// string(6) "123123"
// string(6) "123123"
// string(6) "123123"

增、删、改操作

除了查询之外的操作,我们也可以使用 exec() 方法来执行其他一些相应的 SQL 语句。

增加操作

$count = $pdo->exec("insert into zyblog_test_user(`username`, `password`, `salt`) value('akk', 'bkk', 'ckk')");
$id = $pdo->lastInsertId();

var_dump($count); // int(1)
var_dump($id); // string(3) "205"

exec() 返回的是影响的行数,如果我们执行这一条 SQL ,返回的就是成功添加了一行数据。如果要获得新增加数据的 id ,就要使用 lastInserId() 方法来获取。

$count = $pdo->exec("insert into zyblog_test_user(`username`, `password`, `salt`) value('akk', 'bkk', 'ckk', 'dkk')");
// Fatal error: Uncaught PDOException: SQLSTATE[21S01]: Insert value list does not match column list: 1136 Column count doesn't match value count at row 1

执行错误的 SQL 语句,就像根据 PDO::ATTR_ERRMODE 属性的设置来返回错误信息。我们在最上面的实例化 PDO 代码中指定了错误形式是异常处理模式,所以这里直接就会报 PDOException 异常。

修改操作

// 正常更新
$count = $pdo->exec("update zyblog_test_user set `username`='aakk' where id='{$id}'");

var_dump($count); // int(1)

// 数据不变更新
$count = $pdo->exec("update zyblog_test_user set `username`='aakk' where id='{$id}'");
var_dump($count); // int(0)

// 条件错误更新
$count = $pdo->exec("update zyblog_test_user set `username`='aakk' where id='123123123123'");
var_dump($count); // int(0)
echo '===============', PHP_EOL;

同样的,在执行更新操作的时候,exec() 返回的也是受影响的行数。很多小伙伴会以这个进行判断是否更新成功,但如果数据没有修改,那么它返回的将是 0 ,SQL 语句的执行是没有问题的,逻辑上其实也没有问题。比如我们在后台打开了某条数据查看,然后并不想更新任何内容就直接点了提交,这时候不应该出现更新失败的提示。也就是说,在前端判断更新操作的时候,需要判断字段是否都有改变,如果没有改变的话那么不应该提示更新失败。这一点是业务逻辑上的考虑问题,如果你认为这样也是更新失败的话,那么这么报错也没有问题,一切以业务形式为主。

删除操作

$count = $pdo->exec("delete from zyblog_test_user where id = '{$id}'");
var_dump($count); // int(1)

// 条件错误删除
$count = $pdo->exec("delete from zyblog_test_user where id = '5555555555'");
var_dump($count); // int(0)

删除操作需要注意的问题和更新操作是一样的,那就是同样的 exec() 只是返回影响行数的问题,不过相对于更新操作来说,没有受影响的行数那肯定是删除失败的,没有数据被删除。同样的,这个失败的提示也请根据业务情况来具体分析。

总结

不学不知道,一学吓一跳吧,简简单的一个 PDO 的创建和语句执行竟然有这么多的内容。对于我们的日常开发来说,掌握这些原理能够避免很多莫名其妙的问题,比如上面 exec() 只是返回影响行数在业务开发中如何判断操作是否成功的问题就很典型。好了,这只是第一篇,后面的学习不要落下了哦!

测试代码:

https://github.com/zhangyue0503/dev-blog/blob/master/php/202008/source/PHP%E4%B8%AD%E7%9A%84PDO%E6%93%8D%E4%BD%9C%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%80%EF%BC%89%E5%88%9D%E5%A7%8B%E5%8C%96PDO%E5%8F%8A%E5%8E%9F%E5%A7%8BSQL%E8%AF%AD%E5%8F%A5%E6%93%8D%E4%BD%9C.php

推荐学习:php视频教程

以上是三分鐘帶你了解PHP中的初始化PDO及原始SQL語句操作的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除