在一个类中使用全局变量
我正在尝试创建一个分页类,并使用类之外的变量。
但它给我致命的错误“调用一个非对象的成员函数query()”。
这是索引文件:
$db = new DB_MySQL("localhost", "root", "", "test"); // connect to the database include_once("pagi.php"); $pagination = new pagi(); $records = $pagination->get_records("SELECT * FROM `table`");
这是pagi.php文件:
class pagi { public function get_records($q) { $x = $db->query($q); return $db->fetch($x); } }
是否有可能从类的外部使用这个变量,而不是在类中创建一个新的变量?
解决这个问题的正确方法是将数据库对象注入到其他类( 依赖注入 )中:
$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
类。 而如果你有多个紧密耦合的依赖呢? 现在你突然用你所谓的单元测试来测试几个类。 所以当使用依赖注入时,你可以很容易地切换到另一个数据库类,甚至是一个模拟的测试目的。 除了仅测试一个单元的好处(您不必担心由于依赖性而导致错误的结果),它也将确保您的测试快速完成。
有些人可能认为Singleton模式是访问数据库对象的正确方法,但是应该清楚,阅读了上述所有内容之后,单例模式基本上就是使事物成为global
另一种方式。 它可能看起来不同,但它具有与global
一样的特征,因此也有同样的问题。
虽然我同意依赖模型是很好的,但对于数据库,我个人使用一个静态连接,可用于数据库类的所有实例和创建实例,以便在需要时进行查询。 这里是一个例子:
<?php //define a database class class DB { //the static connection. //This is available to all instances of the class as the same connection. private static $_conn; //store the result available to all methods private $result; //store the last query available to all methods private $lastQuery; //static connection function. connects to the database and stores that connection statically. public static function connect($host, $user, $pass, $db){ self::$_conn = mysqli_connect($host, $user, $pass, $db); } //standard function for doing queries. uses the static connnection property. public function query($query){ $this->lastQuery = $query; $this->result = mysqli_query(self::$_conn, $query); //process result, return expected output. } } //create connection to the database, this connection will be used in all instances of DB class DB::connect('local', 'DB_USER', 'DB_PASS'); //create instance to query $test = new DB; //do query $test->query("SELECT * FROM TABLE"); //test function function foo(){ //create instance to use in this function $bar = new DB; //do query $bar->query("SELECT * FROM OTHER_TABLE"); //return results return $bar->fetchArray(); }
这样我就可以在任何函数,方法等内创建我想要的数据库的所有实例,并使用该类的本地实例来完成我所有的查询。 所有实例使用相同的连接。
有一点要注意的是,这只允许一个连接到数据库每个定义的类,但我只使用一个,所以这对我来说不是一个问题。
你可以将db-connection( $db
)添加到get_records
方法的调用中:
这里只有相关的代码行:
第一个文件:
$records = $pagination->get_records("SELECT * FROM `table`", $db);
第二个文件:
public function get_records($q, $db) {
到目前为止的其他答案肯定比使用全局更好,因为这会破坏你的封装(例如你需要在调用该方法之前定义该对象)。
在方法签名中强制执行或者不使用类更好。
使用单例模式并将DB_MySQL注入一个静态实例!