PDO尝试捕获函数中的用法


PDO try-catch usage in functions

我正在考虑在我未来的所有Web应用程序中使用PDO。目前(使用我从目前为止学到的知识),我的站点中处理数据库连接的是一个Singleton类,如下所示:

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";
    }
}
...

这意味着我必须在所有函数中重复CCD_ 1部分。

我的问题是:

  1. 我应该如何提高效率?(例如,不必在所有函数中重复try..catch,但仍然可以在每个函数上返回不同的"失败消息")
  2. 这已经是一个很好的做法了吗?我还是PDO和OOP的新手(还有很多东西要学),所以(到目前为止),我真的看不到任何缺点或可以改进的地方

如果这似乎不清楚或太长,我很抱歉。提前谢谢。

您的实现很好,对于大多数目的来说都能很好地工作。

没有必要把每个查询都放在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有关如何做到这一点的信息。

这里有几个注意事项:

  • 编写此代码是为了考虑一些遗留问题,例如数据库日志记录和数据库配置管理
  • 我强烈建议您在构建自己的解决方案之前先查看现有的解决方案。很多人在刚开始的时候都会想,他们不想使用现有的框架或库,因为它们太大,需要太多时间来学习等等,但在成为这些人之后,我再怎么强调也不为过,我要离开我的自定义框架和包装器类,转而使用框架。我希望转会到曾德,但是有很多不错的选择

哦,我应该指出,这一点说明了如何包装单个函数来处理查询的所有异常处理。我现在几乎在其他地方都不写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);          
    }
}