Лучший способ скрыть код подключения к БД в PHP5 для приложений, которым требуется только одно подключение? - PullRequest
2 голосов
/ 10 сентября 2008

Ниже я представляю три варианта для упрощения доступа к моей базе данных, когда используется только одно соединение (это часто относится к веб-приложениям, над которыми я работаю).

Общая идея состоит в том, чтобы сделать соединение с БД прозрачным, чтобы оно подключалось при первом выполнении моего сценария запроса, а затем оставалось подключенным до завершения сценария.

Я хотел бы знать, какой из них вы считаете лучшим и почему. Я не знаю названий каких-либо шаблонов проектирования, которые могли бы подойти, так что извините, что не использовал их. И если есть какой-нибудь лучший способ сделать это с PHP5, пожалуйста, поделитесь.

Чтобы дать краткое введение: существует класс DB_Connection, содержащий метод запроса. Это сторонний класс, который находится вне моего контроля, и интерфейс которого я упростил для целей данного примера. В каждом варианте я также предоставил пример модели для воображаемой таблицы «элементов» БД, чтобы дать некоторый контекст.

Вариант 3 предоставляет мне интерфейс, который мне нравится больше всего, но, к сожалению, он не практичен.

Я описал плюсы и минусы (которые я вижу) каждого в комментариях ниже.

В данный момент я склоняюсь к варианту 1, поскольку бремя ложится на мой класс-оболочку БД, а не на модели.

Все комментарии приветствуются!

Примечание. По какой-то причине в предварительном просмотре переполнения стека вместо символа подчеркивания отображается закодированная сущность HTML. Если сообщение приходит таким образом, примите это во внимание.

<?php

/**
 * This is the 3rd-party DB interface I'm trying to wrap.
 * I've simplified the interface to one method for this example.
 *
 * This class is used in each option below.
 */
class DB_Connection {
    public function &query($sql) { }
}

/**
 * OPTION 1
 *
 * Cons: Have to wrap every public DB_Connection method.
 * Pros: The model code is simple.
 */
class DB {
    private static $connection;
    private static function &getConnection() {
        if (!self::$connection) {
            self::$connection = new DB_Connection();
        }
        return self::$connection;
    }
    public static function &query($sql) {
        $dbh = self::getConnection();
        return $dbh->query($sql);
    }
}

class Item {
    public static function &getList() {
        return DB::query("SELECT * FROM items");
    }
}

/**
 * OPTION 2
 *
 * Pros: Don't have to wrap every DB_Connection function like in Option 1
 * Cons: Every function in the model is responsible for checking the connection
 */

class DB {
    protected static $connection = null;
    public function connect() {
        self::$connection = new DB_Connection();
    }
}

class Item extends DB {
    public static function &getList() {
        if (!self::$connection) $this->connect();
        return self::$connection->query("SELECT * FROM items");
    }
}

/**
 * OPTION 3
 *
 * Use magic methods
 *
 * Pros: Simple model code AND don't have to reimplement the DB_Connection interface
 * Cons: __callStatic requires PHP 5.3.0 and its args can't be passed-by-reference.
 */
class DB {
    private static $connection = null;

    public static function &getConnection() {
        if (!self::$connection) {
            self::$connection = new DB_Connection();
        }
        return self::$connection;
    }

    public static function __callStatic($name, $args) {
        if (in_array($name, get_class_methods('DB_Connection'))) {
            return call_user_func_array(
                array(self::getConnection(), $name), $args);
        }
    }
}

Ответы [ 2 ]

1 голос
/ 10 сентября 2008

Семантически говоря, я думаю, что вариант 1 имеет смысл, если вы рассматриваете БД как ресурс, тогда DB_Connectioin - это объект, который он использует, но не обязательно сам объект.

Однако, несколько вещей, против которых я вас предостерегаю. Во-первых, не устанавливайте в своем классе БД все статические методы, так как это сильно повлияет на вашу способность тестировать код. Вместо этого рассмотрим очень простую инверсию управляющего контейнера:

class DB {
    private $connection;
    public function &query($sql) {
        return $connection->query($sql);
    }
    public __construct(&$db_connection) {
        $this->connection = $db_connection;
    }
}

class Item {
    public function &getList() {
        return  ResourceManager::getDB()->query("SELECT * FROM items");
    }
}

class ResourceManager {
    private $db_connection;
    private function &getDbConnection() {
        if (!$this->connection) {
            $this->connection = new DB_Connection();
        }
        return $this->connection;
    }
    private $db;
    public static function getDB() {
        if(!$this->db) $this->db = new DB(getDbConnection());
    return $this->db;
}

Существуют значительные преимущества. Если вы не хотите, чтобы БД использовалась в качестве одиночного, вы просто вносите одно изменение в ResourceManager. Если вы решите, что это не должен быть синглтон - вы вносите изменения в одном месте. Если вы хотите вернуть другой экземпляр БД на основе некоторого контекста - опять же, изменение происходит только в одном месте.

Теперь, если вы хотите протестировать Item в изоляции от БД, просто создайте метод setDb ($ db) в ResourceManager и используйте его для установки поддельной / фиктивной базы данных ( simplemock хорошо вам подойдет в этом отношении ).

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

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

1 голос
/ 10 сентября 2008

Исходя из приведенных выше примеров, я бы сказал, что вариант 1 является лучшим - простота всегда выигрывает, и вы можете обработать сбойное соединение по-разному в зависимости от метода (вы можете захотеть по-разному завершить вызов хранимой процедуры, чем простой Например, SELECT).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...