4

I was reading somewhere that using global variables is generally a bad idea but I am not sure what the alternative is...

I have this code now and I need to use global $config, $db in every single function. = copy & paste

class Layout {

   public static function render($file, $vars) {

     global $config, $mysql_db;

     mysqli_query($mysql_db, "SELECT * FROM users");
}
}

Is there a better way to do this or do I need to use global keyword or define globals? Because I will need things like mysql connection variables in every function...

yivi
  • 42,438
  • 18
  • 116
  • 138
Martin Zeltin
  • 2,496
  • 3
  • 18
  • 36

6 Answers6

3

Generally speaking, global variables are not good. There are some methods to avoid global variables.

Use a base class

class Base 
{
    protected $db, $config;
}

class Layout extends Base 
{
    public void foo() 
    {
        $this->db->query(...);
    }
}

Use namespace

namespace MyProject
{
    function DB()
    {
        ....
    }
}

class Layout
{
    public void foo() 
    {
        \MyProject\DB()->query(...);
    }
}

Use some frameworks, like Symfony, Laravel, etc (also you can consider some ORM-only frameworks)

The famous frameworks do good job.

shawn
  • 4,305
  • 1
  • 17
  • 25
  • It's less useful because the method is static – ArtisticPhoenix Apr 08 '18 at 07:18
  • Why do you want to keep that method static? `static` is against `OOP`. And, the method 2 (namespace) or the ORM frameworks can be used in `static` method. – shawn Apr 08 '18 at 07:43
  • Why does who want to keep the method static? Because I could care less And static is not against OOP or it wouldn't be part of Objects to begin with, just saying. It's not like the PHP developers just decided it would be fun to have it. That said it's not without it's pitfalls. – ArtisticPhoenix Apr 08 '18 at 07:50
  • @ArtisticPhoenix, like most popular languages, PHP is a hybrid, supporting more than one programming paradigm. Existence of a feature in the language could be to support any paradigm which the language supports. For example, Java includes procedural features, OO features, and functional features. – jaco0646 Apr 08 '18 at 14:30
  • @ArtisticPhoenix `against OOP` doesn't mean that it is evil. In fact, I use static methods in my OOP frameworks, too. But, if you have better choices when you are doing OO-Programming, why do you keep the not-so-good static method? – shawn Apr 08 '18 at 16:20
  • I'm not keeping anything, I don't know why you think it's my choice in fact the first thing in my answer says they should change that if possible. It's not my question, not my method, not my project, not my choice.... If I say to the OP, they have to change that's not true there are ways to do dependencies in static methods. All I can do is provide examples, I have no control over what they do. – ArtisticPhoenix Apr 08 '18 at 17:37
1

A common pattern is to pass any required classes into the constructor of a class, so this would look more like...

class Layout {
    private $config;
    private $mysql_db;

    public function __construct ( $config, $mysql_db ) {
        $this->config = $config;
        $this->mysql_db = $mysql_db;
    }

    public function render($file, $vars) {
         $this->mysql_db->query( "SELECT * FROM users");
    }
}

This is based around dependency injection (DI) which is common amongst frameworks and allows much more flexibility and control. First link from a search gives - http://php-di.org/doc/understanding-di.html.

Also note that I've changed the call to mysqli_query() to the object oriented interface query(), this requires you create the connection with something like new mysqli("localhost", "my_user", "my_password", "world"), but also makes the interface more consistent (you can change this back if you want)

This would then be created using

$layout = new Layout( $config, $mysql_db);
$layout->render($file, $vars);

This layout is core to a lot of frameworks and is also key when you want to undertake comprehensive testing as you can control the information being passed in and mock classes where needed.

Nigel Ren
  • 56,122
  • 11
  • 43
  • 55
1

Static variables in a class is one way to avoid them. Think of static variables in PHP as variables defined on the class rather the instance of the class. The singleton pattern then can grab the variable, or just reference the variable directly on the class. Alternatively, write a __construct method to accept in your incoming variables. Another approach is traits in PHP to help reduce on your copy pasting. Not saying traits are good practice, but they help avoid repeating yourself. Finally, there's almost always a way to abstract over the problem. Having your database connection in a method called render already violates the concept of separation of concerns, and the single responsibility principle. Dependency injection is probably my favorite way to solve the global issue. Find a framework or library that supports it. Lots of good choices out there. Either a framework like Laravel or some composer package that gets you the functionality you need will do.

Ultimater
  • 4,647
  • 2
  • 29
  • 43
  • Technically you don't put static properties in a singleton because the whole class is static. – ArtisticPhoenix Apr 08 '18 at 07:10
  • @ArtisticPhoenix I'm referring to static as a meaning of implementing the singleton pattern and trying to explain what's shown here: https://stackoverflow.com/a/1939269/466314 When the singleton pattern is implemented like this, I'm saying one can proceed with `$this->someProperty` kind of logic dealing with the instance. Then if needed outside the class, one can call `getInstance()` to get the same instance then call a method to grab the value of `$this->someProperty` such as `$myInstance->getSomeProperty()`. Accessing variables this way or just grabbing `someClass::someStatic` avoids globals – Ultimater Apr 08 '18 at 07:37
  • I'm quite familiar with what and how a singleton works, My point was that without any examples your wording is confusing to someone that is not.... I was just saying. – ArtisticPhoenix Apr 08 '18 at 07:49
  • @ArtisticPhoenix I'll agree the wording in my answer could use some work. It's covered in the comments here at least. – Ultimater Apr 08 '18 at 07:53
1

This is basic example how you can use it

create Database class with file name Database.php

<?php
class Database{

   private $con;

   function __construct() {
        $con = mysqli_connect("localhost","my_user","my_password","my_db");
   }

   function execute($sql){
        // Perform queries, but you should use prepared statemnet of mysqli for
        // sql injection 
        $execute = mysqli_query($con,$sql);
        return $execute;
   }
}
?>

And let say Render class with name Render.php

<?php
require_once('your_path/Database.php'); // write correct path of file
class Render{
    private $db;

    function __construct() {
        $db = new Database();
    }

    function test(){
        $result = $db->execute('SELECT * FROM users');
    }
}
?>
Ozal Zarbaliyev
  • 566
  • 6
  • 22
1

A lot of posters have given beating around the bush answers ... sorry guys ... it's true.

The common way to take care of it is with Dependency Injection. Taking your class

class Layout {

   public static function render($file, $vars) {

         global $config, $mysql_db;

         mysqli_query($mysql_db, "SELECT * FROM users");
    }
}

You have a dependency on $config, $mysql_db; Your class depends on them. Typically you would Inject these into the constructor like this.

 protected $Mysqli;
 protected $config;

 public function __construct(array $config, Mysqli $Mysqli){
     $this->config = $config;
     $this->Mysqli = $mysqli;
 }

The issue you have is that the method itself is static, so that bypasses the guarantee that the constructor was called. You can just call the method directly. If possible I would change this to not be static.

But if you insist on keeping it static, then there is a few common ways to solve this, and it depends what the rest of the class looks like. For these I will ignore $config (for the most part) it's not used and it's not clear how it's used or what it's used for (I'm assuming it's for the database). I will also use the Object interface to Mysqli instead of the procedural one.

The most obvious way it to stick them in the method call

public static function render($file, $vars, Mysqli $Mysqli) {
    $Mysqli->query("SELECT * FROM users");
}

You can check when the method is called and connect

protected static $Mysqli;

public static function connect(){
   //this has the obvious problem of getting the connection info.
   $config = require 'config.php';
   if(!is_array($config)) throw new Exception('Bad config..'); //etc.

   self::$Mysqli = new mysqli(
       $config['dbhost'],
       $config['dbuser'],
       $config['dbpass'],
       $config['dbname']
   );
}


public static function render($file, $vars) {
    if(!$Mysqli) self::connect();
    self::$Mysqli->query("SELECT * FROM users");
}

//config.php would look like this
<?php

     return [
        'dbname' => 'database',
        'dbuser' => 'username',
        'dbpass' => 'password',
        'dbhost' => 'host'
    ];

This works but it may not be Ideal because now your class is responsible for an external file config.php which if it doesn't exist will cause issues.

You can use a singleton pattern if this class is all static methods. This saves a reference to the class instance (it's self) in a static property. Then when you call getInstance it always returns the same instance, that is way it is called a singleton

class Layout {

    //saves a reference to self
    private static $instance;

    protected $Mysqli;

    final private function __construct(Mysqli $Mysqli){
       if(!$Mysqli) throw new Exception('Instance of Mysqli required');
       $this->Mysqli = $Mysqli;
    }

    final private function __clone(){}

    final private function __wakeup(){}

    public static function getInstance(Mysqli $Mysqli = null)
    {
        if (!self::$instance)
            self::$instance = new self($Mysqli);
        return self::$instance;
    }

    public function render($file, $vars) {
        self::$Mysqli->query("SELECT * FROM users");
    }

}

$Layout = Layout::getInstance();
$Layout->render($file, $vars);

The problem with this is the first call the Mysqli class (or config or whatever) is required, but on subsequent calls it's not. You may or may not know ahead of time that its the first call. But again you could load the config from a file in this example too.

You'll also notice a few methods are Final private this is to prevent overloading and calling them from outside the class.

I also have a trait for Singleton and Multiton (singleton container, multiple Singletons) that I put up on GitHub and Composer, you can find that here

https://github.com/ArtisticPhoenix/Pattern/

Each has it's advantages and disadvantages.

P.S. I just typed these all out on this page, so I didn't test any of them but the theory is sound....

ArtisticPhoenix
  • 21,464
  • 2
  • 24
  • 38
1

Problems with global variables

  • you will have trouble naming new global variables, accidentally overriding useful data in your formerly defined global variables
  • your code might keep unreasonably variables that you no longer need, with all the consequences

There is always an exception from the rule

However, it is better to avoid dogmatic extremism. Sometimes you will need global variables for some reason, but you will need to make sure that you have a very good reason to use global variables.

Alternative

The alternative for using global variables everywhere is to use private or protected class members, initializing the main values via a constructor, like:

class Layout {

   private $config;
   private $mysql_db;

    public function render($file, $vars) {

         mysqli_query($mysql_db, "SELECT * FROM users");
    }

    public function __construct($config, $mysql_db) {
        $this->config = $config;
        $this->mysql_db = $mysql_db;
    }

}

And then you can instantiate Layout via the new keyword and call render of its instance.

Further notes

Also, you can use namespaces so you might have several classes with the same name and I would like to mention local variables as well, inside functions.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175