25

我正在尝试创建一个分页类并使用类外部的变量。

但这给了我致命的错误“在非对象上调用成员函数 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);
    }

}

是否可以在类内部的类外部使用此变量,而无需在类内部创建一个新变量?

4

4 回答 4

69

解决这个问题的正确方法是将数据库对象注入另一个类(依赖注入):

$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);
    }

}

您选择哪种方法取决于具体情况。如果只有一个方法需要数据库的实例,您可以将其注入到方法中,否则我会将其注入到类的构造函数中。

另请注意,我已将您的课程从 重命名pagiPaginator。恕我直言,Paginator 是该类更好的名称,因为其他人(重新)查看您的代码很清楚。另请注意,我已将第一个字母设为大写。

我所做的另一件事是更改查询以选择您正在使用的字段,而不是使用“通配符” *。这与我更改类名的原因相同:人们(重新)查看您的代码将确切知道将检索哪些字段,而无需检查数据库和/或结果。

更新

因为答案引发了关于为什么我会采用依赖注入路线而不是声明对象的讨论,所以我想global澄清为什么我会在global关键字上使用依赖注入:当你有这样的方法时:

function get_records($q) {
    global $db;

    $x = $db->query($q);
    return $db->fetch($x);
}

当您在某处使用上述方法时,不清楚使用的类或方法取决于$db. 因此,它是一个隐藏的依赖项。上述情况不好的另一个原因是因为您将$db实例(因此是DB_MySQL)类与该方法/类紧密耦合。如果您需要在某个时候使用 2 个数据库怎么办。现在您必须检查所有代码才能更改global $dbglobal $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.

于 2012-08-12T15:29:58.180 回答
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 实例,并使用该类的本地实例来完成我的所有查询。所有实例都使用相同的连接。

One thing to note though is that this only allows for one connection to the database per defined class but I only use one so this isn't an issue for me.

于 2012-08-21T00:02:57.600 回答
3

您可以将 db-connection ( $db) 添加到方法的调用中get_records

这里只是相关的代码行:

第一个文件:

$records = $pagination->get_records("SELECT * FROM `table`", $db);

第二个文件:

public function get_records($q, $db) {
于 2012-08-12T15:19:21.960 回答
0

到目前为止,其他答案肯定比使用全局更可取,因为这会破坏您的封装(例如,您需要在调用该方法之前定义该对象)。

在方法签名中强制执行或不使用类要好得多。

于 2012-08-12T15:22:44.473 回答