解决这个问题的正确方法是将数据库对象注入到另一个类(
dependency injection)中:
$db = new DB_MySQL("localhost", "root", "", "test"); // connect to the database
include_once("pagi.php");
$pagination = new Paginator($db);
$records = $pagination->get_records("SELECT the, fields, you, want, to retrieve FROM `table`");
class Paginator
{
protected $db;
// Might be better to use some generic db interface as typehint when available
public function __construct(DB_MySQL $db)
{
$this->db = $db;
}
public function get_records($q) {
$x = $this->db->query($q);
return $this->db->fetch($x);
}
}
另一种解决方法是将数据库类的实例注入到使用它的方法中:
$db = new DB_MySQL("localhost", "root", "", "test"); // connect to the database
include_once("pagi.php");
$pagination = new Paginator();
$records = $pagination->get_records("SELECT the, fields, you, want, to retrieve FROM `table`", $db);
class Paginator
{
public function get_records($q, DB_MySQL $db) {
$x = $db->query($q);
return $db->fetch($x);
}
}
选择哪种方法取决于情况。如果只有一个方法需要一个数据库的实例,你可以将它注入到方法中,否则我将它注入该类的构造函数。
另请注意,我已将您的班级从pagi重新命名为Paginator。 Paginator是一个更好的名字IMHO的类,因为它是清楚的其他人(重新)查看你的代码。另请注意,我已经把第一个字母大写。
我所做的另一件事是更改查询以选择正在使用的字段,而不是使用“通配符”*。这是因为我改变了类名:同样的原因,人们(重新)查看你的代码将会知道什么字段将被检索而不检查数据库和/或结果。
更新
因为回答引起了一个讨论,为什么我会去依赖注入路由而不是声明对象全局,我想澄清为什么我会使用依赖注入超过全局关键字:当你有一个方法如:
function get_records($q) {
global $db;
$x = $db->query($q);
return $db->fetch($x);
}
当您在某处使用上述方法时,不清楚类或方法使用取决于$ db。因此它是隐藏的依赖。上述不良的另一个原因是因为您将$ db实例(因此DB_MySQL)类紧密耦合到该方法/类。如果您需要在某些时候使用2个数据库怎么办?现在,您必须通过所有代码将全局$ db更改为全局$ db2。您不应该只需要更改代码以切换到另一个数据库。因此,你不应该这样做:
function get_records($q) {
$db = new DB_MySQL("localhost", "root", "", "test");
$x = $db->query($q);
return $db->fetch($x);
}
再次,这是一个隐藏的依赖关系,并将DB_MySQL类紧密耦合到方法/类。因此,也不可能正确单元测试Paginator类。您也可以同时测试DB_MySQL类,而不是测试单元(Paginator类)。如果你有多个紧密耦合的依赖关系呢?现在你突然用你所谓的单元测试来测试几个类。因此,当使用依赖注入时,您可以轻松地切换到另一个数据库类,甚至是用于测试的嘲笑数据库类。除了仅测试一个单元的好处(您不必担心因依赖关系而导致错误的结果),它还将确保您的测试能够快速完成。
有些人可能会认为Singleton模式是访问数据库对象的正确方法,但是应该清楚的是,已经阅读了上述所有内容,单例基本上只是使全局变成另一种方式。它可能看起来不同,但它具有完全相同的特征,因此与全球相同的问题。