2011-12-24

Circular Reference


Intro



I just recently ran into something that had me stumbled for quite a while before it smacked me in the face
like a ton of bricks. It'd be better to explain with examples, so excuse me if you're reading this and you're
surprised to see some gobblygok and you're just looking for techno-gobblygok to look at, okay?



For those of you familiar with software languages such as C/C++, Java, and Basic, you may be familiar with this
concept of passing variables by reference and by value. For those of you that don't know, this will be good exposure,
and an awesome lesson to learn at an early stage to prevent hours of FaceDesking. :P


What's a Reference?



In many languages, you pass by reference by using the ampersand (&) operator. Like so:




function myAdd(&$var, $amount = 0){
$var += $amount;
}

$toAdd = 5;
$amount = 3;
var_dump(myAdd($toAdd, $amount));



Normally when you pass a variable in a function, you are passing the value of that variable to the function.
When you pass the reference to a variable, you are passing a pointer to the piece of memory that represents
that variable. So, instead of passing the value of "5" to the function, it's almost as if I'm passing the
variable $toAdd itself.



You can also return a varibale by reference. Doing so requires defining the ampersand in the function declaraction,
like so:



function &reference($val){
return $val;
}


In this function we set it to return a reference to the value that was passed to it. Not much of use, but references
have their moments to shine. They are [excessively in my opinion] used in the PEAR libraries.


Circular Reference



Now that we are all familiar with references, we can illustrate how they can be harmful to your application,
should you ever encounter them. If you use a circular reference in your code, you're going to encounter some
pretty interesting errors that won't lead you directly to the problem...




class Bootstrap{
public $config;
public function __construct(){
$this->config =& $GLOBALS['app']->config;
}

public function &run(){
return $this->config;
}
}

class Application{
public $config;

public function __construct(){
$GLOBALS['app'] =& $this; # Assign this by reference to the global application variable for easy access
$bootstrap = new Bootstrap();
$this->config = $bootstrap->run();
}

public function action(){
$User = new User();
$login = $User->Login();
if($login){
# User passed, reward them with a cookie :)
}else{
# Fail the user with a counter >.>
}
}
}

class User{
protected $_config;
public function __construct(){
$this->_config =& $GLOBALS['app']->config;
}

public function Login(){
# Attempt to login the user...
}
}



Can you spot the point of interest? About half of this code is a distraction ^_^
Well, first, you have to imagine this project being about 10K lines of code
longer, and involving much more as far as functionality and components are concerned, but this is about the gist
of it. Look closely at the chain of command here, when dealing with the references. $GLOBALS['app'] is given a
reference address to the Application class in its construct. Then, the construct creates an instance of the bootstrap
class. The bootstrap's construct is then created and calls its construct. In the construct of the bootstrap, it creates
a reference to the $GLOBALS['app']->config and puts that in $this->config. Then, when focus is given back to the
application's construct, it assigns a reference to the bootstrap's referenced Bootstrap::$config and puts that
in $this->config.



This is where circular reference is an issue, because Bootstrap::$config is a reference pointer to
$GLOBALS['app']->config which is a reference pointer to Application::$config, which is now a reference pointer
to Bootstrap::$config which is a reference pointer to $GLOBALS['app']->config which continues the cycle
endlessly. To break the chain, I removed an ampersand from Bootstrap::run. Took me a good minute to realize this,
but once I did, it stuck out like a red-hot nail that hadn't been hammered down yet.



Hope this saves someone else from hours of debugging...



No comments:

Post a Comment