PDO 用法學習


 

PDO: php data object
數據庫訪問抽象層

基於驅動:
1、安裝擴展 php_pdo.dll
2、安裝驅動 php_pdo_mysql.dll

linux 編譯時參數:--with-pdo=mysql=/usr/local/mysql

三個類:
PDO類, 數據庫連接有關(連接、執行sql)
PDOStatement 處理結果集
PDOException 異常處理類
一些常量

相比mysqli方法少,常量多

dsn:data source name(數據源) 包括 主機、庫名、驅動名
#部分參數可以省略,參數沒有順序
mysql: #最短 會自動調取 php.in 中 mysql.default_host 參數信息

dsn示例:

mysql:host=localhost;port=3307;dbname=testdb
mysql:unix_socket=/tmp/mysql.sock;dbname=testdb

獲取pdo對象

 

$dsn = "mysql:host=127.0.0.1;port=3306;dbname=test";
$opts = array(PDO::ATTR_AUTOCOMMIT=>0, PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION, PDO::ATTR_AUTOCOMMIT=>0 #關閉自動提交, PDO::ATTR_TIMEOUT => 3 #設置超時時間);
$pdo = new PDO($dsn, 'root', '', $opts);
ee($pdo->getAttribute(PDO::ATTR_AUTOCOMMIT)); // setAttribute 可以設置屬性
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);  #設置獲取的方式 

 

執行sql語句: exec() 、query()、 perpare();

1、query用來處理有結果集的,如select, 返回 PDOStatement 對象,失敗返回false(當為 PDO::ERRMODE_SILENT,這也是默認的值)
2、exec用來處理有返回影響行數的(int),如  insert(插入的行數)、 delete(刪除的行數) 、update(和原數值不等才算), 失敗返回false (當為 PDO::ERRMODE_SILENT,這也是默認的值)

3、prepare 執行所有sql,可以完全替代 query,exec的功能

錯誤報告是針對執行的sql出錯時

PDO::ERRMODE_SILENT(0) :默認 不提示任何錯誤 ,連接時無論如何都會提示,只有在執行后面的方法時才會起作用
PDO::ERRMODE_WARNING(1) : 警告
PDO::ERRMODE_EXCEPTION(2):異常(推薦使用) 用try catch捕獲,也可以手動拋出異常 new PDOException($message, $code, $previous)

 

#exec用法
try {
    $sql = "insert into limove(`id`, `name`, `order`) values(null, 'sjk', 1),(null, 'sjk',2)";
    $rows = $pdo->exec($sql);  //影響的條數 2
    $pdo->lastInsertId(); //最后插入的id,有多條時返回的是第一條的id
} catch (Exception $e) {
    ee($pdo->errorInfo());
}

 

#query方法同樣也可以執行insert,delete 只是返回的結果集的格式
#同樣 lastInsertId 照樣也可以使用
$params = array (
                PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'' ,
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        );
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test;port=3306', 'root', '',$params);


$statement = "insert into card (id, json_str, f) values(null, 'ok', 2.3),(null, 'ok', 2.3)";

$flag = $pdo->query($statement);
ee($flag);

ee($pdo->lastInsertId());

PDOStatement Object
(
    [queryString] => insert into card (id, json_str, f) values(null, 'ok', 2.3),(null, 'ok', 2.3)
)

 

#query可以實現所有exec的功能
$params = array (
                PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'' ,
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        );
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test;port=3306', 'root', '',$params);


$statement = "insert into card. (id, json_str, f) values(null, 'ok', 2.3),(null, 'ok', 2.3)";
$statement = "select * from card";
$statement = "delete  from card where id = 625";
$statement = "update card set f=5 where id = 624";

$stmt = $pdo->query($statement); ee($stmt->rowCount());

 

 總結:

  1、query和exec都可以執行所有的sql語句,只是返回值不同而已。

  2、query可以實現所有exec的功能。

  3、當把select語句應用到 exec 時,總是返回 0

 注意:批量插入時,依次插入當遇到錯誤時后面的插入失敗,但是前面的會插入成功。

預處理語句(prepare)示例,sql只編譯一次,執行相同的sql效率會高。單個相比exec,query效率也高。

 

#prepare 在不恰當的位置調用用法可能會出異常

$params = array (
                PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'' ,
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        );
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test;port=3306', 'root', '',$params);
 

// $sql = "insert into card (id, json_str, f) values(null, 'ok', 2.3),(null, 'ok', 2.3)";
// $sql = "select * from card";
$sql = "delete  from card where id = 625";
// $sql = "update card set f=5 where id = 624";
// $sql = "show create table card";
// $sql = "desc card";

// $stmt = $pdo->query($sql);
// ee($stmt->fetchAll());
// ee($stmt);

$stmt = $pdo->prepare($sql);
$stmt->execute();
 ee($stmt->rowCount());
ee($stmt->fetch(pdo::FETCH_ASSOC));


Fatal error:  Uncaught exception 'PDOException' with message 'SQLSTATE[HY000]: General error' in E:\wamp\www\test\song.php:27
Stack trace:
#0 E:\wamp\www\test\song.php(27): PDOStatement->fetch(2)
#1 {main}
  thrown in E:\wamp\www\test\song.php on line 27

 

 

$dsn = "mysql:host=127.0.0.1;port=3306;dbname=test";
$opts = array(PDO::ATTR_AUTOCOMMIT=>0, PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION, PDO::ATTR_AUTOCOMMIT=>0);
try {
    $pdo = new PDO($dsn, 'root', '', $opts);
}catch(PDOException $e){
    echo $e->getMessage();
}


/* pdo中有兩種占位符號
 *
*  ? 參數             --- 索引數組, 按索引順序使用
*  名子參數           ----關聯數組, 按名稱使用,和順序無關
*/


//准備好了一條語句,並入到服務器端,也已經編譯過來了,就差為它分配數據過來
//同樣適用於更新操作
$stmt=$pdo->prepare("insert into limove(`name`, `order`) values(:name,:order)");

//綁定參數,引用方式傳遞
$stmt->bindParam(":name", $name);
$stmt->bindParam(":order", $order);


// $stmt=$pdo->prepare("insert into limove(`name`, `order`) values(?, ?)"); //所有SQL都可執行

// //綁定參數,引用方式傳遞
// $stmt->bindParam(1, $name, PDO::PARAM_STR); #起始值為 1 
// $stmt->bindParam(2, $order, PDO::PARAM_INT);

#變量放到 bindParam 前后都可
$name="wwww";
$order = 1;

if($stmt->execute()){
    echo "執行成功";
    echo "最后插入的ID:".$pdo->lastInsertId();
}else{
    echo "執行失敗!";
}

 

還支持執行時綁定

    #無序方式
    $stmt=$pdo->prepare("insert into shops(name, price, num, desn) values(:name,:price, :num, :desn)"); 
    $stmt->execute(array(":price"=>77, ":name"=>"kkk3", ":num"=>"453", ":desn"=>"aaaaaa3"));
 $stmt->execute(array(":price"=>77, ":name"=>"kkk3", ":num"=>"453", ":desn"=>"aaaaaa3"));

    #有序方式
    $stmt=$pdo->prepare("insert into shops(name, price, num, desn) values(?, ?, ?, ?)"); //所有SQL都可執行
    $stmt->execute(array("myname1", 11.2, 55, "very good"));
    $stmt->execute(array("myname1", 11.2, 55, "very good"));

 

 

獲取結果

//獲取結果
$stmt = $pdo->prepare("select * from limove where `order` = :order");
$stmt->execute(array(':order'=>8));

//設置獲取的方式
$stmt->setFetchMode(PDO::FETCH_ASSOC);

$data = array();

//方式1
//$data = $stmt->fetchAll();

//方式2
while($row = $stmt->fetch(PDO::FETCH_ASSOC))
{
    $data[] = $row;
}
ee($data);

 

//bindColumn 把取出的值綁定到一個變量上
$stmt->bindColumn('name', $name);
$stmt->bindColumn(2, $sex); # 還可以根據順序綁定,可以混合使用

 

#所有的條數,select insert delete update
$stmp->rowCount();

 

 

 

事務舉例:

<?php
    try{
        $pdo=new PDO("mysql:host=localhost;dbname=xsphpdb", "root", "123456", array(PDO::ATTR_AUTOCOMMIT=>0)); #一定要關閉自動提交
        $pdo->setAttribute(PDO::ATTR_ERRMODE,  PDO::ERRMODE_EXCEPTION); #開啟異常模式
    }catch(PDOException $e){
        echo "數據庫連接失敗:".$e->getMessage();
        exit;
    }

    //執行SQL語句 exec() query()  prepare()
    //一是有結果集的query(), 執行select語句
    //exec()用來執行有影響行數的,update, delete insert, other
    //exec()返回的是影響的行數
    /*
     *
     * 事務處理
     *
     *   張三從李四那里買了一台 2000 元的電腦
     *
     *     從張三帳號中扣出 2000元
     *
     *     向李四賬號中加入 2000元
     *
     *     從商品表中減少一台電腦
     *
     *     MyIsAM  InnoDB
     *
     */

    try{
        $pdo->beginTransaction();
        
        $price=500;

        $sql="update zhanghao set price=price-{$price} where id=1";

        $affected_rows=$pdo->exec($sql);

        if(!$affected_rows)
            throw new PDOException("張三轉出失敗");

        $sql="update zhanghao set price=price+{$price} where id=3";

        $affected_rows=$pdo->exec($sql);
            
        if(!$affected_rows) #發現問題手動拋出異常
            throw new PDOException("向李四轉入失敗");

        echo "交易成功!";
        $pdo->commit();
    }catch(PDOException $e){
        echo $e->getMessage();
        $pdo->rollback(); //只要捕獲異常則回滾
    }
    
    //不管執行成功還是失敗最后都要在關閉自動提交
    $pdo->setAttribute(PDO::ATTR_AUTOCOMMIT, 1);
    
    //設置錯誤報告模式 ERRMODE_SILENT    ERRMODE_WARNING

 

 

user contribute comments 

/**
To avoid exposing your connection details should you fail to remember to catch any exception thrown by the PDO constructor you can use the following class to implicitly change the exception handler temporarily.
*/

Class SafePDO extends PDO {

    public static function exception_handler($e) {
        // Output the exception details
        die('Uncaught exception:'.$e->getMessage());
    }

    public function __construct($dsn, $username='', $password='', $driver_options=array()) {

        // Temporarily change the PHP exception handler while we . . .
        set_exception_handler(array(__CLASS__, 'exception_handler'));

        // . . . create a PDO object
        parent::__construct($dsn, $username, $password, $driver_options);

        // Change the exception handler back to whatever it was before
        restore_exception_handler();
    }

}

$dsn = 'mysql:host=127.0.0.1;dbname=tesst;port=3306';
$pdo = new SafePDO($dsn);

 

#占位符的無效使用
$stmt = $dbh->prepare("SELECT * FROM REGISTRY where name LIKE '%?%'");
$stmt->execute(array($_GET['name']));

// 占位符必須被用在整個值的位置
$stmt = $dbh->prepare("SELECT * FROM REGISTRY where name LIKE ?");
$stmt->execute(array("%$_GET[name]%"));

 

#開啟兩次失誤會報錯,下面解決了這個問題
class Database extends \\PDO 
{ 
    protected $transactionCounter = 0; 
    function beginTransaction() 
    { 
        if(!$this->transactionCounter++) 
            return parent::beginTransaction(); 
       return $this->transactionCounter >= 0; 
    } 

    function commit() 
    { 
       if(!--$this->transactionCounter) 
           return parent::commit(); 
       return $this->transactionCounter >= 0; 
    } 

    function rollback() 
    { 
        if($this->transactionCounter >= 0) 
        { 
            $this->transactionCounter = 0; 
            return parent::rollback(); 
        } 
        $this->transactionCounter = 0; 
        return false; 
    } 
//... 
} 

 

#模擬事務的嵌套使用,執行commit時會提交所有的事務
class Database extends PDO
{

    protected $transactionCount = 0;

    public function beginTransaction()
    {
        if (!$this->transactionCounter++) {
            return parent::beginTransaction();
        }
        $this->exec('SAVEPOINT trans'.$this->transactionCounter);
        return $this->transactionCounter >= 0;
    }

    public function commit()
    {
        if (!--$this->transactionCounter) {
            return parent::commit();
        }
        return $this->transactionCounter >= 0;
    }

    public function rollback()
    {
        if (--$this->transactionCounter) {
            $this->exec('ROLLBACK TO trans'.$this->transactionCounter + 1);
            return true;
        }
        return parent::rollback();
    }
    
}

 

 

/**
 * If you do not fetch all of the data in a result set before issuing your next call to PDO::query(), your call may fail. Call PDOStatement::closeCursor() to release the database resources associated with the PDOStatement object before issuing your next call to PDO::query().
 */
$dsn = 'mysql:host=127.0.0.1;dbname=test;port=3306';
$params = array (
                PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'' ,
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::MYSQL_ATTR_USE_BUFFERED_QUERY =>0, //@warn
        );
$pdo = new PDO($dsn, 'root', '',$params);


$sql_1 = "select * from card";

$stmt = $pdo->query($sql_1);
$one = $stmt->fetch();
ee($one);

// $stmt->closeCursor();

$sql_2 = "select * from card";

$stmt = $pdo->query($sql_2);
$all = $stmt->fetchAll();
ee($all);

 

/**
獲取select的條數,需要公用一個數據庫鏈接才行,會受到limit的影響
MySQL does not seem to return anything in rowCount for a select statement, but you can easily and efficiently get the row count as follows:

*/
class db extends PDO {
  public function last_row_count() {
    return $this->query("SELECT FOUND_ROWS()")->fetchColumn();
  }
}

$myDb = new db('mysql:host=myhost;dbname=mydb', 'login', 'password' );

Then, after running your query:

if ( $myDb->last_row_count() == 0 ) {
  echo "Do something!";
  }

 

更好的獲取條數

$db = new PDO(DSN...); 
$db->setAttribute(array(PDO::MYSQL_USE_BUFFERED_QUERY=>TRUE)); 
$rs  = $db->query('SELECT SQL_CALC_FOUND_ROWS * FROM table LIMIT 5,15'); 
$rs1 = $db->query('SELECT FOUND_ROWS()'); 
$rowCount = (int) $rs1->fetchColumn(); 

 

 

/*  使用一個數組的值執行一條含有 IN 子句的預處理語句 */
$params = array(1, 21, 63, 171);
/*  創建一個填充了和params相同數量占位符的字符串 */
$place_holders = implode(',', array_fill(0, count($params), '?'));

/*
    對於 $params 數組中的每個值,要預處理的語句包含足夠的未命名占位符 。
    語句被執行時, $params 數組中的值被綁定到預處理語句中的占位符。
    這和使用 PDOStatement::bindParam() 不一樣,因為它需要一個引用變量。
    PDOStatement::execute() 僅作為通過值綁定的替代。
*/
$sth = $dbh->prepare("SELECT id, name FROM contacts WHERE id IN ($place_holders)");
$sth->execute($params);

 

/**
$placeHolder
*/
data = ['a'=>'foo','b'=>'bar'];

$keys = array_keys($data);
$fields = '`'.implode('`, `',$keys).'`';

#here is my way 
$placeholder = substr(str_repeat('?,',count($keys),0,-1));

$pdo->prepare("INSERT INTO `baz`($fields) VALUES($placeholder)")->execute(array_values($data));

 

 

1、事務通常是通過把一批更改“積蓄”起來然后使之同時生效而實現的;這樣做的好處是可以大大地提供這些更改的效率。

2、當腳本結束或連接即將被關閉時,如果尚有一個未完成的事務,那么 PDO 將自動回滾該事務。這種安全措施有助於在腳本意外終止時避免出現不一致的情況——如果沒有顯式地提交事務,那么假設是某個地方出錯了,所以執行回滾來保證數據安全。

3、預處理語句 的好處
1、查詢僅需解析(或預處理)一次,但可以用相同或不同的參數執行多次。 2、提供給預處理語句的參數不需要用引號括起來,驅動程序會自動處理。如果應用程序只使用預處理語句,可以確保不會發生S QL 注入。(然而,如果查詢的其他部分是由未轉義的輸入來構建的,則仍存在 SQL 注入的風險)。

4、在事務中,lastInsertId 應該用在 commit之前,否則會得到 0

5、對於大多數數據庫,PDOStatement::rowCount() 不能返回受一條 SELECT 語句影響的行數。替代的方法是,使用 PDO::query() 來發出一條和原打算中的SELECT語句有相同條件表達式的 SELECT COUNT(*) 語句,然后用 PDOStatement::fetchColumn() 來取得返回的行數。這樣應用程序才能正確執行。

6、quote 增加引號

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM