对于只需要一个连接的应用程序,在PHP5中隐藏数据库连接代码的最佳方法是?
下面,我提出了三个选项,用于在仅涉及单个连接时简化我的数据库访问(我正在使用的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类。以我的经验,类似的事情最终也会减轻一些痛苦。