对于只需要一个连接的应用程序,在PHP5中隐藏数据库连接代码的最佳方法是?

时间:2020-03-05 18:50:29  来源:igfitidea点击:

下面,我提出了三个选项,用于在仅涉及单个连接时简化我的数据库访问(我正在使用的Web应用通常是这种情况)。

一般的想法是使数据库连接透明,以便在我的脚本第一次执行查询时它就连接,然后保持连接直到脚本终止。

我想知道我们认为哪一个最好,为什么。我不知道它们可能适合的任何设计模式的名称,非常抱歉不使用它们。而且,如果有使用PHP5进行此操作的更好方法,请分享。

简单介绍一下:有一个包含查询方法的DB_Connection类。这是我无法控制的第三方类,在本示例中,我已简化了其接口。在每个选项中,我还提供了一个虚构的数据库"项目"表的示例模型,以提供一些上下文。

选项3是为我提供最喜欢的界面的一种,但是不幸的是,我认为这并不实用。

我已经在下面的评论框中描述了它们的优缺点(可以看到)。

目前,我倾向于方法1,因为负担是由我的DB包装器类而不是模型承担的。

所有评论表示赞赏!

注意:由于某些原因,堆栈溢出预览显示的是编码的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);
        }
    }
}

解决方案

回答

根据上面的示例,我想说选项1是最好的简单性总能胜出,并且我们可以根据方法的不同来处理失败的连接(例如,与简单的SELECT相比,存储过程调用可能要以不同的方式失败) )。

回答

从语义上讲,我认为选项1最有意义,如果我们将DB视为资源,则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;
}

有很多好处。如果我们不希望将DB用作单例,则只需对ResourceManager进行一次修改。如果我们决定它不应该是单例,则可以在一处进行修改。如果要基于某个上下文再次返回不同的DB实例,则更改仅在一个地方。

现在,如果我们要在数据库中隔离测试Item,只需在ResourceManager中创建setDb($ db)方法并使用它来设置伪造/模拟数据库(简单模拟在这方面将为我们服务)。

其次,这是另一种修改,该设计使我们可能不想一直保持数据库连接一直处于打开状态,这最终可能会使用比所需更多的资源。

最后,正如我们提到的,DB_Connection还有其他未显示的方法,似乎它可能被用于不仅仅是维护连接。既然我们说我们无法控制它,那么我建议我们从其中提取我们确实关心的方法的接口,并使MyDBConnection扩展实现该接口的DB_Connection类。以我的经验,类似的事情最终也会减轻一些痛苦。