2008-11-07 46 views
30

我正在考慮在我所有未來的webapp中使用PDO。目前(使用的是什麼我從SO學會爲止),我在我的網站來處理數據庫連接是一個Singleton類是這樣的:PDO try-catch函數的使用

class DB { 

    private static $instance = NULL; 
    private static $dsn  = "mysql:host=localhost;dbname=mydatabase;"; 
    private static $db_user = 'root'; 
    private static $db_pass = '0O0ooIl1'; 

    private function __construct() 
    { 

    } 
    private function __clone() 
    { 

    } 
    public static function getInstance() { 

     if (!self::$instance) 
     {   
      self::$instance = new PDO(self::$dsn, self::$db_user, self::$db_pass); 
      self::$instance-> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
     } 
     return self::$instance; 
    } 
} 

和其他文件(functions.php中)與特定內容功能看正是這樣一個:

function get_recent_activities() 
{  
    try 
    {  
     $db = DB::getInstance(); 
     // --prepare and execute query here, fetch the result-- 
     return $my_list_of_recent_activities; 
    } 
    catch (PDOException $e) 
    { 
     return "some fail-messages"; 
    } 
} 
... 

這意味着我必須重複try .. catch部分中的所有的功能。

我的問題是:

  1. 我應該如何做出更有效? (例如,不必在所有功能中重複try..catch,但仍然能夠在每一個功能上返回不同的「失敗消息」)
  2. 這是否已經是一種很好的做法?我還是PDO和OOP的新手(還有很多需要學習),所以(現在)我看不出任何缺點或可以改進的地方。

對不起,如果這似乎不清楚或太長。提前致謝。

回答

38

你的實現是蠻好的,它會很好地工作於大多數的目的。

沒有必要將每個查詢都放在try/catch塊中,事實上在大多數情況下,您實際上並不想這樣做。原因是如果查詢產生一個異常,這是一個像語法錯誤或數據庫問題這樣的致命問題的結果,並且這些問題不是你應該爲每個查詢所考慮的問題。

例如:

try { 
    $rs = $db->prepare('SELECT * FROM foo'); 
    $rs->execute(); 
    $foo = $rs->fetchAll(); 
} catch (Exception $e) { 
    die("Oh noes! There's an error in the query!"); 
} 

查詢這裏要麼正常工作或不工作。在一個生產系統中不會有任何規律的情況下,根本不會發生這種情況,所以它們不是你應該在這裏檢查的條件。這樣做實際上是適得其反的,因爲您的用戶會得到一個永遠不會改變的錯誤,並且您不會收到一條異常消息來提醒您該問題。

相反,只是這樣寫:

$rs = $db->prepare('SELECT * FROM foo'); 
$rs->execute(); 
$foo = $rs->fetchAll(); 

一般來說,你要捕獲和處理的查詢異常的惟一一次是當你想,如果查詢失敗做別的事情。例如:

// We're handling a file upload here. 
try { 
    $rs = $db->prepare('INSERT INTO files (fileID, filename) VALUES (?, ?)'); 
    $rs->execute(array(1234, '/var/tmp/file1234.txt')); 
} catch (Exception $e) { 
    unlink('/var/tmp/file1234.txt'); 
    throw $e; 
} 

你想寫日誌或通知您,發生在生產環境中的數據庫的錯誤,並顯示一個友好的錯誤信息給用戶,而不是異常跟蹤一個簡單的異常處理程序。有關如何做到這一點的信息,請參閱http://www.php.net/set-exception-handler

+0

第二個pd說的是,我對自定義錯誤類的使用只是將錯誤記錄到數據庫並向我發送一封電子郵件。最終用戶從不會看到堆棧跟蹤或其他不友好的情況。這就是爲什麼如果出現錯誤,我返回false,然後測試我的查詢的返回值,以確定告訴用戶什麼 – 2008-11-08 11:36:29

3

這裏有幾個需要注意的地方是:

  • 這段代碼被寫入需要幾個遺留問題考慮在內,如數據庫日誌和數據庫配置管理。
  • 我強烈建議您在構建自己的解決方案之前查看現有的解決方案。很多人在開始時覺得自己不想使用現有的框架或圖書館,因爲它們太大,需要太多時間來學習等,但在成爲這些人之一後不能足夠強調我將離開自定義框架和包裝類轉移到框架。我正在尋找轉移到Zend,但有很多優秀的選擇。

哦,我應該指出,這一點說明了如何包裝一個函數來處理查詢的所有異常處理。現在我幾乎不會在其他地方寫try catch塊,因爲查詢中的堆棧跟蹤爲我提供了調試問題並修復問題所需的所有信息。

這是我目前的PDO包裝類的實現:

class DB extends PDO 
{ 
    // Allows implementation of the singleton pattern -- ndg 5/24/2008 
    private static $instance; 

    // Public static variables for configuring the DB class for a particular database -- ndg 6/16/2008 
    public static $error_table; 
    public static $host_name; 
    public static $db_name; 
    public static $username; 
    public static $password; 
    public static $driver_options; 
    public static $db_config_path; 



    function __construct($dsn="", $username="", $password="", $driver_options=array()) 
    { 
     if(isset(self::$db_config_path)) 
     { 
      try 
      { 
       if(!require_once self::$db_config_path) 
       { 
        throw new error('Failed to require file: ' . self::$db_config_path); 
       } 
      } 
      catch(error $e) 
      { 
       $e->emailAdmin(); 
      } 
     } 
     elseif(isset($_ENV['DB'])) 
     { 
      self::$db_config_path = 'config.db.php'; 

      try 
      { 
       if(!require_once self::$db_config_path) 
       { 
        throw new error('Failed to require file: ' . self::$db_config_path); 
       } 
      } 
      catch(error $e) 
      { 
       $e->emailAdmin(); 
      } 
     } 

     parent::__construct("mysql:host=" . self::$host_name . ";dbname=" .self::$db_name, self::$username, self::$password, self::$driver_options); 
     $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
     $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('QueryStatement', array($this))); 

     if(!isset(self::$error_table)) 
     { 
      self::$error_table = 'errorlog_rtab'; 
     } 
    } 

    /** 
    * Return a DB Connection Object 
    * 
    * @return DB 
    */ 
    public static function connect() 
    { 

     // New PDO Connection to be used in NEW development and MAINTENANCE development 
     try 
     { 
      if(!isset(self::$instance)) 
      { 
       if(!self::$instance = new DB()) 
       { 
        throw new error('PDO DB Connection failed with error: ' . self::errorInfo()); 
       } 
      } 

      return self::$instance; 
     } 
     catch(error $e) 
     { 
      $e->printErrMsg(); 
     } 
    } 

    /** 
    * Returns a QueryBuilder object which can be used to build dynamic queries 
    * 
    * @return QueryBuilder 
    * 
    */ 
    public function createQuery() 
    { 
     return new QueryBuilder(); 
    } 

    public function executeStatement($statement, $params = null, $FETCH_MODE = null) 
    { 
     if($FETCH_MODE == 'scalar') 
     { 
      return $this->executeScalar($statement, $params); 
     } 


     try { 
      try { 
       if(!empty($params)) 
       { 
        $stmt = $this->prepare($statement); 
        $stmt->execute($params); 
       } 
       else 
       { 
        $stmt = $this->query($statement); 
       } 
      } 
      catch(PDOException $pdo_error) 
      { 
       throw new error("Failed to execute query:\n" . $statement . "\nUsing Parameters:\n" . print_r($params, true) . "\nWith Error:\n" . $pdo_error->getMessage()); 
      } 
     } 
     catch(error $e) 
     { 
      $this->logDBError($e); 
      $e->emailAdmin(); 
      return false; 
     } 

     try 
     { 
      if($FETCH_MODE == 'all') 
      { 
       $tmp = $stmt->fetchAll(); 
      } 
      elseif($FETCH_MODE == 'column') 
      { 
       $arr = $stmt->fetchAll(); 

       foreach($arr as $key => $val) 
       { 
        foreach($val as $var => $value) 
        { 
         $tmp[] = $value; 
        } 
       }   
      } 
      elseif($FETCH_MODE == 'row') 
      { 
       $tmp = $stmt->fetch(); 
      } 
      elseif(empty($FETCH_MODE)) 
      { 
       return true; 
      } 
     } 
     catch(PDOException $pdoError) 
     { 
      return true; 
     } 

     $stmt->closeCursor(); 

     return $tmp; 

    } 

    public function executeScalar($statement, $params = null) 
    { 
     $stmt = $this->prepare($statement); 

     if(!empty($this->bound_params) && empty($params)) 
     { 
      $params = $this->bound_params; 
     } 

     try { 
      try { 
       if(!empty($params)) 
       { 
        $stmt->execute($params); 
       } 
       else 
       { 
         $stmt = $this->query($statement); 
       } 
      } 
      catch(PDOException $pdo_error) 
      { 
       throw new error("Failed to execute query:\n" . $statement . "\nUsing Parameters:\n" . print_r($params, true) . "\nWith Error:\n" . $pdo_error->getMessage()); 
      } 
     } 
     catch(error $e) 
     { 
      $this->logDBError($e); 
      $e->emailAdmin(); 
     } 

     $count = $stmt->fetchColumn(); 

     $stmt->closeCursor(); 

     //echo $count; 
     return $count;  
    } 

    protected function logDBError($e) 
    { 
     $error = $e->getErrorReport(); 

     $sql = " 
     INSERT INTO " . self::$error_table . " (message, time_date) 
     VALUES (:error, NOW())"; 

     $this->executeStatement($sql, array(':error' => $error)); 
    } 
} 

class QueryStatement extends PDOStatement 
{ 
    public $conn; 

    protected function __construct() 
    { 
     $this->conn = DB::connect(); 
     $this->setFetchMode(PDO::FETCH_ASSOC); 
    } 

    public function execute($bound_params = null) 
    { 
     return parent::execute($bound_params);   
    } 
} 
+0

謝謝,我明天早上再看看這個。 – andyk 2008-11-07 16:27:36