web-dev-qa-db-fra.com

Utiliser des variables globales dans une classe

J'essaie de créer une classe de pagination et d'utiliser une variable extérieure à la classe.

Mais cela me donne l'erreur fatale "Appeler une fonction membre query () sur un non-objet".

C'est le fichier d'index:

$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`");

Et voici le fichier pagi.php:

class pagi {

    public function get_records($q) {
        $x = $db->query($q);
        return $db->fetch($x);
    }

}

Est-il possible d'utiliser cette variable de l'extérieur de la classe à l'intérieur de la classe, sans en créer une nouvelle à l'intérieur de la classe?

24
Marcoo

La bonne façon de résoudre ce problème serait d’injecter l’objet de base de données dans l’autre classe ( dépendance dépendance ):

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

}

Une autre solution consiste à injecter l'instance de la classe de base de données dans la méthode qui l'utilise:

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

}

Quelle que soit la méthode que vous choisissez dépend de la situation. Si une seule méthode nécessite une instance de la base de données, vous pouvez simplement l'injecter dans la méthode, sinon je l'injecterais dans le constructeur de la classe.

Notez également que j'ai renommé votre classe de pagi à Paginator. Paginator est un meilleur nom IMHO pour la classe car il est clair pour les autres personnes (ré) visualisant votre code. Notez également que j'ai fait la première lettre en majuscule.

Une autre chose que j'ai faite est de changer la requête pour sélectionner les champs que vous utilisez au lieu d'utiliser le "caractère générique" *. C’est pour la même raison que j’ai changé le nom de la classe: les personnes (ré) visualisant votre code sauront exactement quels champs seront récupérées sans vérifier la base de données et/ou le résultat.

Mise à jour

Parce que la réponse a donné lieu à une discussion sur les raisons pour lesquelles je choisirais la voie d'injection de dépendance au lieu de déclarer l'objet global, j'aimerais préciser pourquoi j'utiliserais l'injection de dépendance sur le mot clé global: Lorsque vous utilisez une méthode comme:

function get_records($q) {
    global $db;

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

Lorsque vous utilisez la méthode ci-dessus quelque part, il n'est pas clair que la classe ou la méthode utilisée dépend de $db. C'est donc une dépendance cachée. Une autre raison pour laquelle ce qui précède est mauvais est que vous avez étroitement couplé l'instance $db (donc le DB_MySQL) à cette méthode/classe. Que faire si vous devez utiliser 2 bases de données à un moment donné? Maintenant, vous devez passer par tout le code pour changer global $db en global $db2. Vous ne devriez jamais avoir besoin de changer votre code simplement pour passer à une autre base de données. Pour cette raison, vous ne devriez pas faire:

function get_records($q) {
    $db = new DB_MySQL("localhost", "root", "", "test");

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

Là encore, il s’agit d’une dépendance cachée qui couple étroitement la classe DB_MySQL à la méthode/classe. De ce fait, il est également impossible de tester correctement la classe Paginator. Au lieu de tester uniquement l'unité (la classe Paginator), vous testez également la classe DB_MySQL en même temps. Et si vous avez plusieurs dépendances étroitement couplées? Maintenant, vous testez soudainement plusieurs classes avec vos tests unitaires. Ainsi, lorsque vous utilisez l'injection de dépendance, vous pouvez facilement passer à une autre classe de base de données, voire à une classe fictive à des fins de test. Outre l'avantage de ne tester qu'une seule unité (vous n'avez pas à vous soucier d'obtenir des résultats erronés à cause de dépendances), cela garantira également que vos tests se termineront rapidement.

Certaines personnes peuvent penser que le modèle Singleton est le bon moyen d'accéder à un objet de base de données, mais cela devrait être clair: après avoir lu tout ce qui précède, un singleton est fondamentalement une autre façon de rendre les choses global. Cela peut paraître différent, mais il a exactement les mêmes caractéristiques et donc les mêmes problèmes que global.

67
PeeHaa

Bien que je convienne que le modèle de dépendance est Nice, pour la base de données, j'utilise personnellement une connexion statique disponible pour toutes les instances de la classe de base de données et les instances de création à interroger chaque fois que j'en ai besoin. Voici un exemple:

<?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();
}

De cette façon, je peux créer toutes les instances souhaitées de DB dans n'importe quelle fonction, méthode, etc., et utiliser cette instance locale de la classe pour effectuer toutes mes requêtes. Toutes les instances utilisent la même connexion.

Une chose à noter cependant est que cela ne permet qu’une connexion à la base de données par classe définie, mais je n’en utilise qu’une, ce n’est donc pas un problème pour moi.

6
Jonathan Kuhn

vous pouvez ajouter la connexion à la base de données ($db) à l'appel de la méthode get_records:

Voici seulement les lignes de code pertinentes:

Premier fichier:

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

Deuxième fichier:

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

Les autres réponses à ce jour sont définitivement préférables à l’utilisation d’une approche globale car cela ruinerait votre encapsulation (par exemple, vous auriez besoin de définir cet objet avant d’appeler cette méthode).

Il vaut bien mieux imposer cela dans la signature de la méthode ou ne pas utiliser de classe.

0
Colin Kroll