How to Avoid Using PHP Global Objects

Stop using `global` in PHP

The point against global variables is that they couple code very tightly. Your entire codebase is dependent on a) the variable name $config and b) the existence of that variable. If you want to rename the variable (for whatever reason), you have to do so everywhere throughout your codebase. You can also not use any piece of code that depends on the variable independently of it anymore.

Example with global variable:

require 'SomeClass.php';

$class = new SomeClass;
$class->doSomething();

Anywhere in the above lines you may get an error because the class or some code in SomeClass.php implicitly depends on a global variable $config. There's no indication of this whatsoever though just looking at the class. To solve this, you have to do this:

$config = array(...);

require 'SomeClass.php';

$class = new SomeClass;
$class->doSomething();

This code may still fail somewhere if you do not set the correct keys inside $config. Since it's not obvious what parts of the config array SomeClass needs or doesn't need and when it needs them, it's hard to recreate the correct environment for it to run correctly. It also creates conflicts if you happened to already have a variable $config used for something else wherever you want to use SomeClass.

So instead of creating implicit, invisible dependencies, inject all dependencies:

require 'SomeClass.php';

$arbitraryConfigVariableName = array(...);

$class = new SomeClass($arbitraryConfigVariableName);
$class->doSomething();

By passing the config array explicitly as a parameter, all the above problems are solved. It's as simple as handing the required information around inside your app. It also makes the structure and flow of the application and what talks to what much clearer. To get to this state if your application is currently a big ball of mud may take some restructuring.


The bigger your codebase gets, the more you have to decouple the individual parts from each other. If every part is dependent on every other part in your codebase, you simply cannot test, use or reuse any part of it individually. That simply devolves into chaos. To separate parts from each other, code them as classes or functions which take all their required data as parameters. That creates clean seams (interfaces) between different parts of your code.


Trying to tie your question together into one example:

require_once 'Database.php';
require_once 'ConfigManager.php';
require_once 'Log.php';
require_once 'Foo.php';

// establishes a database connection
$db = new Database('localhost', 'user', 'pass');

// loads the configuration from the database,
// the dependency on the database is explicit without `global`
$configManager = new ConfigManager;
$config = $configManager->loadConfigurationFromDatabase($db);

// creates a new logger which logs to the database,
// note that it reuses the same $db as earlier
$log = new Log($db);

// creates a new Foo instance with explicit configuration passed,
// which was loaded from the database (or anywhere else) earlier
$foo = new Foo($config);

// executes the conversion function, which has access to the configuration
// passed at instantiation time, and also the logger which we created earlier
$foo->conversion('foo', array('bar', 'baz'), $log);

I'll leave to implementation of the individual classes up as an exercise for the reader. When you try to implement them, you'll notice that they're very easy and clear to implement and do not require a single global. Every function and class gets all its necessary data passed in the form of function arguments. It should also be obvious that the above components can be plugged together in any other combination or that dependencies can easily be substituted for others. For example, the configuration does not need to come from the database at all, or the logger can log to a file instead of the database without Foo::conversion having to know about any of this.


Example implementation for ConfigManager:

class ConfigManager {

public function loadConfigurationFromDatabase(Database $db) {
$result = $db->query('SELECT ...');

$config = array();
while ($row = $result->fetchRow()) {
$config[$row['name']] = $row['value'];
}

return $config;
}

}

It's a very simple piece of code that doesn't even do much. You may ask why you'd want this as object oriented code. The point is that this makes using this code extremely flexible, since it isolates it perfectly from everything else. You give one database connection in, you get one array with a certain syntax back. Input → Output. Clear seams, clear interfaces, minimal, well defined responsibilities. You can do the same with a simple function.

The extra advantage an object has is that it even further decouples the code that calls loadConfigurationFromDatabase from any particular implementation of that function. If you'd just use a global function loadConfigurationFromDatabase(), you basically have the same problem again: that function needs to be defined when you try to call it and there are naming conflicts if you want to replace it with something else. By using an object, the critical part of the code moves here:

$config = $configManager->loadConfigurationFromDatabase($db);

You can substitute $configManager here for any other object that also has a method loadConfigurationFromDatabase. That's "duck typing". You don't care what exactly $configManager is, as long as it has a method loadConfigurationFromDatabase. If it walks like a duck and quacks like a duck, it is a duck. Or rather, if it has a loadConfigurationFromDatabase method and gives back a valid config array, it's some sort of ConfigManager. You have decoupled your code from one particular variable $config, from one particular loadConfigurationFromDatabase function and even from one particular ConfigManager. All parts can be changed and swapped out and replaced and loaded dynamically from anywhere, because the code does not depend on any one particular other piece.

The loadConfigurationFromDatabase method itself also does not depend on any one particular database connection, as long as it can call query on it and fetch results. The $db object being passed into it could be entirely fake and read its data from an XML file or anywhere else instead, as long as its interface still behaves the same.

Alternative to Using Global Variable in PHP

You should return the value from the function:

Updated code, also fixed prepared query, and set an alias for the return columns.

<?php

function usertype()
{
include_once "../classes/sessionstart.php";
include_once "../config/dbconnect.php";

$select = $con->prepare("
SELECT user_usertype as `type`,
user_gender as `gender`
FROM tbl_user
WHERE user_id = :user_id LIMIT 1
");
$select->bindValue(':user_id', (int) $_SESSION['user_id'], PDO::PARAM_INT);
$select->execute();

return $select->fetch(PDO::FETCH_ASSOC);
}

?>

.

<?php
$usertype = usertype();
?>
<div>
<select class="searchpropertyinputs" name="user_usertype" id="user_usertype">
<?php if ($usertype['gender'] == "Male" && $usertype['type'] != "Marriage Bureau") { ?> <option value="Bride">Bride</option> <?php } ?>
<?php if ($usertype['gender'] == "Female" && $usertype['type'] != "Marriage Bureau") { ?> <option value="Groom">Groom</option> <?php } ?>
<?php if ($usertype['type'] == "Marriage Bureau") { ?>
<option value="" hidden>Bride or Groom</option>
<option value="Bride">Bride</option>
<option value="Groom">Groom</option>
<?php } ?>
</select>
</div>

You may also want to move out the includes to connect, session start, or you will have issues for future functions. So that should most likely lead you on to grouping functions into a user class, for example:

<?php

include_once "../classes/sessionstart.php";
include_once "../config/dbconnect.php";

class User {

public function __construct(PDO $con)
{
$this->con = $con;
}

public function type($user_id = 0)
{
$select = $this->con->prepare("
SELECT user_usertype as `type`,
user_gender as `gender`
FROM tbl_user
WHERE user_id = :user_id LIMIT 1
");
$select->bindValue(':user_id', (int) $user_id, PDO::PARAM_INT);
$select->execute();

return $select->fetch(PDO::FETCH_ASSOC);
}

//...
}

$user = new User($con);

$usertype = $user->type($_SESSION['user_id']);
?>

PHP Recursive Function - How To Avoid Using A Global Variable

You could return array result from the recursive call and array merge them at the parent level call.

<?php

function checkForChildren($uuid, $conn){
$familyArray = [];

$sql = "SELECT id, uuid, name FROM people WHERE parent = '".$uuid."'";
$result = mysqli_query($conn, $sql);

foreach ($result as $row){
$familyArray[] = [
'id' => $row['id'],
'uuid' => $row['uuid'],
'name' => $row['name']
];
$familyArray = array_merge($familyArray,checkForChildren($row['uuid'],$conn));
}

return $familyArray;
}

Update:

I would suggest you to have a look at the with recursive cte queries to avoid roundtrips via DB query calls to DB server.

Are global variables in PHP considered bad practice? If so, why?

When people talk about global variables in other languages it means something different to what it does in PHP. That's because variables aren't really global in PHP. The scope of a typical PHP program is one HTTP request. Session variables actually have a wider scope than PHP "global" variables because they typically encompass many HTTP requests.

Often (always?) you can call member functions in methods like preg_replace_callback() like this:

preg_replace_callback('!pattern!', array($obj, 'method'), $str);

See callbacks for more.

The point is that objects have been bolted onto PHP and in some ways lead to some awkwardness.

Don't concern yourself overly with applying standards or constructs from different languages to PHP. Another common pitfall is trying to turn PHP into a pure OOP language by sticking object models on top of everything.

Like anything else, use "global" variables, procedural code, a particular framework and OOP because it makes sense, solves a problem, reduces the amount of code you need to write or makes it more maintainable and easier to understand, not because you think you should.

Safe alternatives to PHP Globals (Good Coding Practices)

The alternative is called dependency injection. In a nutshell it means that you pass the data a function/class/object requires as parameters.

function showPage(Database $db, array &$output) {
...
}

$output['header']['log_out'] = "Log Out";
$db = new Database;

showPage($db, $output);

This is better for a number of reasons:

  • localizing/encapsulating/namespacing functionality (the function body has no implicit dependencies to the outside world anymore and vice versa, you can now rewrite either part without needing to rewrite the other as long as the function call doesn't change)
  • allows unit testing, since you can test functions in isolation without needing to setup a specific outside world
  • it's clear what a function is going to do to your code just by looking at the signature

PHP - why global variables are evil

I answer this question:

will the echoed $variable1 be displayed as what the respective users have input to their browser or it will override the first user's input by the last user's input?

The echoed $variable1 will be displayed as what the respective users have input to their browser.

PHP makes thread per request, so different requests have different variables (include global).

I check this post.

As said by JasonB,
PHP global variable has a very wide scope, and make code complex.



Related Topics



Leave a reply



Submit