解决这个问题的正确方法是将数据库对象注入另一个类(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 是该类更好的名称,因为其他人(重新)查看您的代码很清楚。另请注意,我已将第一个字母大写。
我所做的另一件事是更改查询以选择您正在使用的字段,而不是使用“通配符”*。这与我更改类名的原因相同:人们(重新)查看您的代码将确切知道将检索哪些字段,而无需检查数据库和/或结果。
更新
因为答案引发了关于为什么我会采用依赖注入路线而不是声明对象 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 存在相同的问题。