web-dev-qa-db-ja.com

クラスでグローバル変数を使用する

ページネーションクラスを作成し、クラスの外部から変数を使用しようとしています。

しかし、それは致命的なエラー「オブジェクト以外のメンバー関数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);
    }

}

クラス内で新しい変数を作成せずに、クラス内のクラスの外側からこの変数を使用することは可能ですか?

25
Marcoo

これを解決する正しい方法は、データベースオブジェクトを他のクラスに注入することです( 依存性注入 ):

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

}

それを解決するもう1つの方法は、データベースクラスのインスタンスを、それを使用するメソッドに挿入することです。

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

}

どの方法を選択するかは、状況によって異なります。 1つのメソッドだけがデータベースのインスタンスを必要とする場合は、それをメソッドに注入するだけです。それ以外の場合は、クラスのコンストラクターに注入します。

また、クラス名をpagiからPaginatorに変更しました。 Paginatorは、他の人がコードを(再)表示するのが明らかであるため、クラスのIMHOというより良い名前です。また、最初の文字を大文字にしたことにも注意してください。

私が行ったもう1つのことは、クエリを変更して、「ワイルドカード」*を使用する代わりに、使用しているフィールドを選択することです。これは、クラス名を変更したのと同じ理由です。コードを(再)表示すると、データベースや結果を確認しなくても、取得されるフィールドが正確にわかります。

更新

オブジェクトglobalを宣言するのではなく、なぜ依存性注入ルートを使用するのかについての議論が生じたため、globalキーワードに対して依存性注入を使用する理由を明確にしたいと思います。あなたは次のような方法を持っています:

function get_records($q) {
    global $db;

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

上記のメソッドをどこかで使用している場合、クラスまたはメソッドが$dbに依存しているかどうかは明確ではありません。したがって、それは隠れた依存関係です。上記が悪いもう1つの理由は、$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クラスも同時にテストします。そして、複数の密結合の依存関係がある場合はどうなりますか?ここで、いわゆる単体テストを使用していくつかのクラスを突然テストしています。したがって、依存性注入を使用すると、簡単に別のデータベースクラスに切り替えたり、テスト目的でモック化したクラスに切り替えることもできます。 1つのユニットのみをテストすることの利点に加えて(依存関係が原因で誤った結果が得られることを心配する必要はありません)、テストが迅速に完了することも保証します。

シングルトンパターンがデータベースオブジェクトにアクセスする正しい方法であると考える人もいるかもしれませんが、上記のすべてを読んでいると、シングルトンは基本的にglobalを作成するもう1つの方法にすぎません。見た目は異なるかもしれませんが、特性はまったく同じなので、globalと同じ問題があります。

68
PeeHaa

依存関係モデルが適切であることに同意しますが、データベースの場合、個人的にはデータベースクラスのすべてのインスタンスで使用できる静的接続を使用し、インスタンスを作成して必要なときにクエリを実行します。次に例を示します。

<?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に必要なすべてのインスタンスを作成し、クラスのそのローカルインスタンスを使用してすべてのクエリを実行できます。すべてのインスタンスが同じ接続を使用します。

ただし、これは定義されたクラスごとにデータベースへの接続を1つしか許可しないが、私は1つしか使用しないので、これは私にとって問題ではないことに注意してください。

6
Jonathan Kuhn

db-connection($db)の呼び出しにget_records 方法:

以下は、関連するコード行のみです。

最初のファイル:

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

2番目のファイル:

public function get_records($q, $db) {
3
Marcel Hebing

これまでの他の答えは、カプセル化を台無しにするので、グローバルを使用するよりも間違いなく好ましいです(たとえば、そのメソッドを呼び出す前にそのオブジェクトを定義しておく必要があります)。

メソッドシグネチャでそれを強制するか、クラスを使用しない方がはるかに優れています。

0
Colin Kroll