我正在尝试创建一个分页类并使用类外的变量。
但它给了我致命的错误“在一个非对象上调用成员函数查询()”。
这是索引文件:
$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);
}
}
是否可以在类的类外部使用此变量,而无需在类中创建新的变量?
答案 0 :(得分:67)
解决这个问题的正确方法是将数据库对象注入另一个类(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
类。如果你有多个紧密耦合的依赖项怎么办?现在你突然用你所谓的单元测试来测试几个类。因此,在使用依赖注入时,您可以轻松切换到另一个数据库类,甚至是用于测试目的的模拟类。除了测试一个单元的好处(你不必担心由于依赖性而得到错误的结果),它还将确保你的测试能够快速完成。
有些人可能会认为Singleton模式是访问数据库对象的正确方法,但是应该很清楚,在阅读了上述所有内容之后,单例基本上只是另一种制作事物的方式global
。它可能看起来不同,但它具有完全相同的特征,因此与global
具有相同的问题。
答案 1 :(得分:6)
虽然我确实认为依赖模型很好,但对于数据库,我个人使用的静态连接可用于数据库类的所有实例,并且创建实例可以在需要时进行查询。这是一个例子:
<?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的所有实例,并使用该类的本地实例来执行所有查询。所有实例都使用相同的连接。
有一点需要注意的是,这只允许每个定义的类与数据库建立一个连接,但我只使用一个,所以这对我来说不是问题。
答案 2 :(得分:3)
您可以将db-connection($db
)添加到get_records
方法的调用中:
以下是相关的代码行:
第一档:
$records = $pagination->get_records("SELECT * FROM `table`", $db);
第二档:
public function get_records($q, $db) {
答案 3 :(得分:0)
到目前为止,其他答案肯定比使用全局更可取,因为这会破坏你的封装(例如,你需要在调用该方法之前定义该对象)。
最好在方法签名中强制执行,或者不使用类。
答案 4 :(得分:-1)
使用单例模式并将DB_MySQL注入静态实例!