Use global variables in a class

前端 未结 4 1718
日久生厌
日久生厌 2020-11-22 13:36

I\'m trying to create a pagination class and use a variable from outside the class.

But it\'s giving me the fatal error \"Call to a member function query() on a non-

相关标签:
4条回答
  • 2020-11-22 13:43

    The correct way to solve this would be to inject the database object into the other class (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);
        }
    
    }
    

    Another way you could solve it is by injecting the instance of the database class into the method that uses it:

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

    Whichever method you choose depends on the situation. If only one method needs an instance of the database you can just inject it into the method, otherwise I would inject it into the constructor of the class.

    Also note that I have renamed your class from pagi to Paginator. Paginator is a better name IMHO for the class because it is clear for other people (re)viewing your code. Also note that I have made the first letter uppercase.

    Another thing I have done is changed the query to select the fields you are using instead of using the "wildcard" *. This is for the same reason I have changed the classname: People (re)viewing your code will know exactly what fields will be retrieved without checking the database and/or the result.

    Update

    Because answer gave rise to a discussion regarding why I would go the dependency injection route instead of declaring the object global, I would like to clarify why I would use dependency injection over the global keyword: When you have a method like:

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

    When you are using the above method somewhere it isn't clear that the class or method uses depends on $db. Hence it is a hidden dependency. Another reason why the above is bad is because you have tightly coupled the $db instance (thus the DB_MySQL) class to that method / class. What if you need to use 2 databases at some point. Now you would have to go through all code to change global $db to global $db2. You should never need to change your code just to switch to another database. For this reason, you should not do:

    function get_records($q) {
        $db = new DB_MySQL("localhost", "root", "", "test");
    
        $x = $db->query($q);
        return $db->fetch($x);
    }
    

    Again, this is a hidden dependency, and tightly couples the DB_MySQL class to the method / class. Because of this it is also impossible to properly unit test the Paginator class. Instead of testing only the unit (the Paginator class) you are also testing the DB_MySQL class at the same time. And what if you have multiple tightly coupled dependencies? Now you are suddenly testing several classes with your so called unit tests. So when using dependency injection you can easily switch to another database class, or even a mocked one for testing purposes. Besides the benefit of testing only one unit (you don't have to worry about getting wrong results because of dependencies) it will also make sure your tests will finish fast.

    Some people may think the Singleton pattern is the correct way to get access to a database object, but it should be clear, having read all of the above, a singleton is basically just another way of making things global. It might look different, but it has the exact same characteristics and hence the same problems as global.

    0 讨论(0)
  • 2020-11-22 13:49

    The other answers thus far are definitely preferable to using a global since that will ruin your encapsulation (eg you'd need to have that object defined prior to calling that method).

    It's much better to enforce that in the method signature or not use a class.

    0 讨论(0)
  • 2020-11-22 13:51

    Although I do agree that the dependency model is nice, for the database, I personally use a static connection that is available to all instances of the database class and the create instances to query whenever I need one. Here is an example:

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

    That way I can create all the instances I want of DB within any function, method...etc and use that local instance of the class to do all my queries. All instances use the same connection.

    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.

    0 讨论(0)
  • 2020-11-22 14:07

    you could add the db-connection ($db) to the call of the get_records method:

    Here are only the relevant lines of code:

    First file:

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

    Second file:

    public function get_records($q, $db) {
    
    0 讨论(0)
提交回复
热议问题