下面我提供了三个选项,用于在只涉及单个连接的情况下简化数据库访问(这通常适用于我所使用的Web应用程序)。
一般的想法是使数据库连接透明,以便在我的脚本第一次执行查询时连接,然后它保持连接直到脚本终止。
我想知道你认为哪一个最好,为什么。我不知道这些可能适合的任何设计模式的名称,对不使用它们感到抱歉。如果使用PHP5有任何更好的方式,请分享。
简要介绍一下:有一个包含查询方法的DB_Connection类。这是一个不受我控制的第三方类,我为此示例的目的简化了其界面。在每个选项中,我还为假想的DB“items”表提供了一个示例模型,以给出一些上下文。
选项3是为我提供我最喜欢的界面的选项,但不幸的是我觉得它不实用。
我已经在下面的评论栏中描述了各自的利弊(我可以看到)。
目前我倾向于选项1,因为负担被放在我的DB包装器类而不是模型上。
所有评论都赞赏!
注意:由于某种原因,Stack Overflow预览显示的是编码的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);
}
}
}
答案 0 :(得分:1)
基于上面的示例,我认为选项1是最好的 - 简单总是胜利,并且您可以根据方法处理失败的连接(您可能希望存储过程调用的失败方式不同于简单例如,SELECT)。
答案 1 :(得分:1)
从语义上讲,我认为选项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实例 - 同样,更改只在一个位置。
现在,如果你想在DB中独立测试Item,只需在ResourceManager中创建一个setDb($ db)方法,并用它来设置假/模拟数据库(simplemock将在这方面为你提供良好的服务)。
第二 - 这是此设计缓解的另一个修改 - 您可能不希望在整个时间内保持数据库连接打开,它最终可能会使用远远超出需要的资源。
最后,正如您所提到的那样,DB_Connection还没有显示其他方法,看起来它可能不仅仅用于维护连接。既然你说你无法控制它,我是否可以建议从你关注的方法中提取一个接口,并使MyDBConnection扩展实现你的接口的DB_Connection类。根据我的经验,这样的事情最终也会缓解一些痛苦。