Использование PDO try-catch в функциях - PullRequest
30 голосов
/ 07 ноября 2008

Я думаю об использовании 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 во всех функциях.

Мои вопросы:

  1. Как мне сделать это более эффективным? (например, не нужно повторять try..catch во всех функциях, и все же иметь возможность возвращать разные «сообщения об ошибках» для каждой из них)
  2. Это уже хорошая практика? Я все еще новичок в PDO и OOP (еще многое предстоит изучить), поэтому (на данный момент) я не вижу никаких недостатков или вещей, которые можно улучшить в этом.

Извините, если это кажется неясным или слишком длинным. Заранее спасибо.

Ответы [ 2 ]

40 голосов
/ 07 ноября 2008

Ваша реализация просто великолепна, и она будет отлично работать для большинства целей.

Нет необходимости помещать каждый запрос в блок 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 для получения информации о том, как это сделать.

3 голосов
/ 07 ноября 2008

Вот несколько предостережений:

  • Этот код написан с учетом нескольких унаследованных проблем, таких как ведение журнала базы данных и управление конфигурацией базы данных.
  • Я настоятельно рекомендую вам взглянуть на существующее решение, прежде чем создавать собственное. Многие начинают думать, что не хотят использовать существующую инфраструктуру или библиотеку, потому что они слишком большие, требуют слишком много времени для обучения и т. Д., Но после того, как я был одним из этих людей, я не могу сказать достаточно решительно, что я оставляю свои пользовательские рамки и классы-оболочки, чтобы перейти к структуре. Я с нетерпением жду переезда в Зенд, но есть несколько отличных вариантов.

О, я должен отметить, что этот пункт иллюстрирует, как можно обернуть одну функцию для обработки всей обработки исключений для ваших запросов. Теперь я не пишу блоки try try почти где-либо еще, потому что трассировка стека от запроса дает мне всю информацию, необходимую для устранения проблемы и ее устранения.

Вот моя текущая реализация класса-оболочки 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);          
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...