この記事では、PHP での初期化 PDO と独自の SQL ステートメントの操作について紹介します。一定の参考値があるので、困っている友達が参考になれば幸いです。
#PDO は、PHP でデータベースを操作するための事実上の標準になっています。現在のフレームワークやさまざまなクラス ライブラリを含め、すべてデータベース接続方法として PDO を使用します。基本的にmysqliを使ってデータベースを操作するのは、簡単なテストコードや小さな関数を書くときだけです。通常の mysql 拡張機能は廃止されたことに注意してください。
まず、PDO インスタンスがどのように初期化されるかを見てみましょう。
$dns = 'mysql:host=localhost;dbname=blog_test;port=3306;charset=utf8'; $pdo = new PDO($dns, 'root', '');
通常の状況では、PDO オブジェクトを直接インスタンス化するときに構築パラメータを渡すことで、PDO オブジェクトを取得できます。このようにして、データベースとの接続を確立しました。接続が失敗した場合、つまり、書き込まれたパラメータに問題がある場合、インスタンス化中に例外が直接報告されます。
PDO オブジェクトのパラメータには、DNS 情報、ユーザー名、パスワード、および PDO 接続のプロパティを設定できる別のパラメータが含まれます。その使用法については後ほど説明します。
PDO 構築パラメータの最初のパラメータは、DNS 文字列です。異なるパラメータの内容を区切るには、この文字列内でセミコロンを使用します。定義できる内容には次のものが含まれます:
DSN プレフィックス、これは接続するデータベースのタイプです。MySQL データベースは通常、mysql: を使用して直接定義されます。
host、接続アドレス、ここではローカル データベースに接続しています localhost
port、ポート番号、MySQL のデフォルトは 3306、 OK
dbname は記述しないでください。接続するデータベースの名前は
unix_socket です。MySQL Unix ソケット ファイルを指定できます
文字セット、接続文字セット
関数を使用して、現在の PHP 環境でサポートされているデータベース拡張機能を確認できます:
print_r(PDO::getAvailableDrivers());exit; // Array // ( // [0] => dblib // [1] => mysql // [2] => odbc // [3] => pgsql // [4] => sqlite // )
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 オブジェクトを返します。このオブジェクトをトラバースすることで、クエリされたデータの結果セットを取得できます。
コードでは、トラバースするために 2 つのメソッドを使用していますが、実際には、それらの効果は同じです。ここで注目したいのは、返されるデータの形式です。データが配列形式で返され、2 つの形式で返されることがわかります。1 つはデータベースによって定義されたキー名で、もう 1 つは添え字形式です。
実際、ほとんどの場合、データベース キー名のキーと値のペアの形式のデータのみが必要です。これを行うには 2 つの方法があります。1 つは、上で定義したデフォルトの PDO::ATTR_DEFAULT_FETCH_MODE 属性を持つ $pdo2 接続を直接使用する方法で、もう 1 つは、クエリ時に 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" // } // ……
上記のオブジェクトの形式で返される結果セット内のオブジェクトは、PHP のデフォルトのクラス タイプである stdClass タイプです。では、クラスを自分で定義し、クエリの完了後にその結果セットを直接生成できるでしょうか? 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视频教程
以上が3 分で、PHP の初期化 PDO と元の SQL ステートメントの操作について学びます。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。