解决这个问题的正确方法是将数据库对象注入另一个类(依赖注入):
$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 是该类更好的名称,因为其他人(重新)查看您的代码很清楚。另请注意,我已将第一个字母设为大写。
我所做的另一件事是更改查询以选择您正在使用的字段,而不是使用“通配符” *
。这与我更改类名的原因相同:人们(重新)查看您的代码将确切知道将检索哪些字段,而无需检查数据库和/或结果。
更新
因为答案引发了关于为什么我会采用依赖注入路线而不是声明对象的讨论,所以我想global
澄清为什么我会在global
关键字上使用依赖注入:当你有这样的方法时:
function get_records($q) {
global $db;
$x = $db->query($q);
return $db->fetch($x);
}
当您在某处使用上述方法时,不清楚使用的类或方法取决于$db
. 因此,它是一个隐藏的依赖项。上述情况不好的另一个原因是因为您将$db
实例(因此是DB_MySQL
)类与该方法/类紧密耦合。如果您需要在某个时候使用 2 个数据库怎么办。现在您必须检查所有代码才能更改global $db
为global $db2
. 您永远不需要更改代码来切换到另一个数据库。因此,您不应该这样做:
function get_records($q) {
$db = new DB_MySQL("localhost", "root", "", "test");
$x = $db->query($q);
return $db->fetch($x);
}
DB_MySQL
同样,这是一个隐藏的依赖关系,并将类与方法/类紧密耦合。因此,也不可能对Paginator
类进行正确的单元测试。您不仅要测试单元(Paginator
类),还要同时测试DB_MySQL
类。如果您有多个紧密耦合的依赖项怎么办?现在你突然用你所谓的单元测试来测试几个类。因此,当使用依赖注入时,您可以轻松地切换到另一个数据库类,甚至是用于测试目的的模拟类。除了只测试一个单元的好处(您不必担心由于依赖关系而得到错误的结果),它还将确保您的测试能够快速完成。
有些人可能认为单例模式是访问数据库对象的正确方法,但应该清楚,阅读以上所有内容后,单例基本上只是另一种制作方式global
。它可能看起来不同,但它具有完全相同的特征,因此与global
.