2

下面我介绍了三个选项,用于在仅涉及单个连接时简化我的数据库访问(这通常是我工作的 Web 应用程序的情况)。

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

我想知道你认为哪一个是最好的,为什么。我不知道这些可能适合的任何设计模式的名称,很抱歉没有使用它们。如果使用 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);
        }
    }
}
4

2 回答 2

1

根据您上面的示例,我会说选项 1 是最好的 - 简单总是获胜,并且您可以根据方法以不同方式处理失败的连接(对于存储过程调用,您可能希望以不同于简单 SELECT 的方式失败,例如实例)。

于 2008-09-10T03:15:24.127 回答
1

从语义上讲,我认为选项 1 最有意义,如果您将 DB 视为资源,则 DB_Connectioin 是它使用的对象,但不一定是对象本身。

但是,我提醒您注意几件事。首先,不要让您的 DB 类具有所有静态方法,因为它会严重影响您测试代码的能力。考虑一个非常简单的控制容器反转,如下所示:

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 实例 - 同样,更改只在一个地方。

现在,如果您想独立于 DB 测试 Item,只需在 ResourceManager 中创建一个 setDb($db) 方法并使用它来设置假/模拟数据库(在这方面simplemock将为您提供很好的服务)。

其次 - 这是此设计简化的另一个修改 - 您可能不想让您的数据库连接一直保持打开状态,它最终可能会使用比需要更多的资源。

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

于 2008-09-10T11:48:50.477 回答