Использование PDO try-catch в функциях

Я подумываю использовать 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 (мне еще многое предстоит узнать), поэтому (на данный момент) я действительно не вижу никаких недостатков или вещей, которые можно улучшить.

Прошу прощения, если это кажется неясным или слишком длинным. Заранее спасибо.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
32
0
76 614
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вот пара предостережений:

  • Этот код написан с учетом нескольких устаревших проблем, таких как ведение журнала базы данных и управление конфигурацией базы данных.
  • Я настоятельно рекомендую вам изучить существующее решение, прежде чем создавать собственное. Многие люди, начиная с самого начала, думают про себя, что не хотят использовать существующий фреймворк или библиотеку, потому что они слишком велики, требуют слишком много времени для обучения и т. не могу заявить достаточно категорично, что оставляю свой собственный фреймворк и классы-оболочки, чтобы перейти к фреймворку. Я собираюсь перейти на 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);          
    }
}
Ответ принят как подходящий

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

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

Чтобы повторить то, что говорит pd, мое использование настраиваемого класса ошибок просто регистрирует ошибку в базе данных и отправляет мне электронное письмо. Конечный пользователь никогда не видит трассировки стека или другого недружелюбного отношения. Вот почему в случае ошибки я возвращаю false, а затем проверяю возвращаемое значение моего запроса, чтобы определить, что сказать пользователю.

Noah Goodrich 08.11.2008 14:36

Если вы измените SELECT и / или другой запрос изменит базу данных, поэтому SELECT больше не будет работать: вы никогда не узнаете об этом, пока не протестируете свою страницу самостоятельно. Это контрпродуктивно. try / catch необходим для каждого запроса!

mgutt 25.02.2015 15:36

Другие вопросы по теме