Hello World

Practical programming... and stuff...

Globals are evil

, ,

Globals are evil - do not use them. Quoting Martin Fowler,

remember that any global data is always guilty until proven innocent.



What is a global? A global is a variable that is accessible from anywhere in an application. For example, in PHP declaring any variable outside a function/class method will make it global.

But what about when I want to have some data globally available in my application?
One useful solution is to use the singleton pattern to create a storage class or something like that. We'll look at that later in this post.


So why exactly are globals evil?

Consider the following example:
<?php
//Lets imagine this is a template used on this page
$page = new Template();
$page->assign('message','hello');

//Lets include an another PHP file that does something
include 'mystuff.php';

//Output the template
$page->show('index.tpl');
?>


The code seems perfectly fine, right?
Here's mystuff.php:
<?php
//Alarm! mystuff.php is changing the $page variable!
//Imagine this is actually needed for something useful.
$page = 'I am nasty.';
?>


Guess what happens on the last line of the first file? Yep, an error. Because $page is declared in global scope, any included files will have it declared too. Changing it in the included file will effect the "parent" file too and you will end up with problems.

Now, the example is a bit exaggerated but it makes the point clear. This is a real problem.

So what can I do?


One option is to use functions or classes. Do not run much PHP code on the "base" level outside any function/method.

If the above example had been like this, it would've worked:
<?php
$page = new Template();
$page->assign('message','hello');

include 'mystuff.php';
mystuff();

$page->show('index.tpl');
?>


mystuff.php:
<?php
function mystuff()
{
  //This is no longer a problem since functions have their own variable scope.
  $page = 'I am nasty.';
}
?>


By doing a simple thing like this, you avoid the whole problem. This is because functions and classes have their own variable scope, which means that setting a variable $x inside a function will only make it visible inside the function itself. Also, if you had declared $x outside the function, it will not be visible inside the function or be affected by your $x declaration inside the function unless you use the global keyword.

You can use the global keyword to include globally declared variables to your function scope. Not recommended, though.


While this solves the problem of overwriting other variables, it doesn't help sharing certain variables through the code. Doing this can be useful, for example for a database connection or configuration settings, so let's look at...

The Singleton pattern and the Storage class


I mentioned the singleton pattern in my Design Patterns: The Factory -post, but I didn't explain it much, so let's look at it in more detail now.


At the basic level, the singleton pattern can be used to make sure there's only one instance of a class used at all times. It could also be used to regulate access to more instances, for example for load balancing, but let's not go into that since it doesn't really apply to PHP etc.

How the singleton pattern operates is simple. You declare the constructor private which stops anyone from instancing the class except the class itself, define a static variable that olds the instance and define a static function that will return an instance of the class.

Something like this:
class Storage
{
  private static $_instance = null;

  private function __construct()
  {
  }

  public static function getInstance()
  {
    if(self::$_instance == null)
      self::$_instance = new self;

    return self::$_instance;
  }
}

//doesn't work
$a = new Singleton();

//but this does
$b = Singleton::getInstance();


So the singleton is a very easy to use pattern. The getInstance method just makes sure there is an instance of the class and returns it. This way the instance's state can be easily persisted throughout the script.

Now, let's extend our Storage class so that it can be used for storing things.

First, let's add a variable that stores the values:
private $_data = array();


Then we need two functions: one for setting data and one for getting.
public function set($name,$value)
{
  $this->_data[$name] = $value;
}

public function get($name)
{
  if(isset($this->_data[$name])
    return $this->_data[$name];
  else
    return null;
}


This all goes in the storage class.

Now, it's very easy to store data globally without the problems using "plain" global variables have.
//Set some data
Storage::getInstance()->set('database',$dbHandle);

//Now we can get our db handle anywhere like this
$db = Storage::getInstance()->get('database');



Someone could argue that this is nothing more than a glorified global array. Well, that's what it is. It has some advantages though. One, you can't accidentally overwrite it. Two, since it's a class and the data is accessed through its methods, you can easily monitor who is accessing it, when and from where.

You could even go as far as implement a locking mechanism that write-protects the data inside the storage:
private $_locks = array();

public function set($name,$value)
{
  if(!isset($this->_locks[$name]))
    $this->_data[$name] = $value;
  else
    throw new Exception($name . ' is locked!');
}

public function lock($name)
{
  $this->_locks[$name] = true;
}

public function unlock($name)
{
  unset($this->_locks[$name]);
}

//Set some data and lock it
$s = Storage::getInstance();
$s->set('hello','world');
$s->lock('hello');

//this will throw an execption:
$s->set('hello','not world');


Conclusion


Global variables may be a quick way to share data in your apps, but it's not the safest way. Using a Storage class might seem pointless but it has several advantages such as having the possibility to monitor access to the data.

Go here for the full source code of the Storage-class implemented here.

Of course, this doesn't actually remove the global data. It's stored in a better way but it's still global. You should still consider if your data really needs to be globally accessible and if not, you should pass the data as parameters to functions instead of accessing it globally.

PHP and named pipesBrowser market share

Comments

Janizomg Tuesday, September 4, 2007 9:08:32 PM

Here is a real problem caused by globals:

I'm using SmartTemplate in a project as the template engine. For some reason I wasn't able to use two SmartTemplate objects in my code - I wanted to display a parent template which had some child template inside it.

I spent a good while going through the SmartTemplate source code, trying to figure out the source of the problem.

Then my eyes see global $_top; in the output function. It looks quite innocent; the $_top variable is being passed as the parameter to the function... or that's what it looks like. I took the liberty of deleting the whole line with the global declaration, and guess what! The problem was gone! And no side-effects at all!


There we see it. Don't use globals.

Unregistered user Tuesday, March 25, 2008 11:09:29 AM

eNMI writes: Why you don't use magic methods _get and _set?

Unregistered user Tuesday, March 29, 2011 4:54:58 AM

Анонимно writes: If you want to buy real estate, you will have to receive the home loans. Moreover, my sister always uses a commercial loan, which occurs to be the most rapid.

Write a comment

New comments have been disabled for this post.