Я подумываю использовать PDO во всех своих будущих веб-приложениях. В настоящее время (используя то, что я до сих пор узнал от SO), на моем сайте для обработки соединения с базой данных есть класс 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";
}
}
...
Это означает, что я должен повторять часть try .. catch во всех функциях.
Мои вопросы:
try..catch во всех функциях, но при этом возможность возвращать разные «сообщения о сбое» для каждой из них)Прошу прощения, если это кажется неясным или слишком длинным. Заранее спасибо.






Вот пара предостережений:
О, я должен отметить, что этот пункт иллюстрирует, как можно обернуть одну функцию для обработки всей обработки исключений для ваших запросов. Я не пишу блоки 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);
}
}
Ваша реализация прекрасна и отлично подойдет для большинства целей.
Необязательно помещать каждый запрос в блок 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 для информации о том, как это сделать.
Если вы измените SELECT и / или другой запрос изменит базу данных, поэтому SELECT больше не будет работать: вы никогда не узнаете об этом, пока не протестируете свою страницу самостоятельно. Это контрпродуктивно. try / catch необходим для каждого запроса!
Чтобы повторить то, что говорит pd, мое использование настраиваемого класса ошибок просто регистрирует ошибку в базе данных и отправляет мне электронное письмо. Конечный пользователь никогда не видит трассировки стека или другого недружелюбного отношения. Вот почему в случае ошибки я возвращаю false, а затем проверяю возвращаемое значение моего запроса, чтобы определить, что сказать пользователю.